From abcdd8d367e8db430119f847f029e9cc94e62cb6 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Tue, 12 Sep 2023 15:06:51 +0200 Subject: [PATCH] DEV: FloatKit (#23312) This PR introduces three new UI elements to Discourse codebase through an addon called "FloatKit": - menu - tooltip - toast Simple cases can be express with an API similar to DButton: ```hbs ``` More complex cases can use blocks: ```hbs <:trigger> {{d-icon "check"}} {{i18n "foo.bar"}} <:content> Something ``` You can manually show a tooltip using the `tooltip` service: ```javascript const tooltipInstance = await this.tooltip.show( document.querySelector(".my-span"), options ) // and later manually close or destroy it tooltipInstance.close(); tooltipInstance.destroy(); // you can also just close any open tooltip through the service this.tooltip.close(); ``` The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service: ```javascript const tooltipInstance = this.tooltip.register( document.querySelector(".my-span"), options ) // when done you can destroy the instance to remove the listeners tooltipInstance.destroy(); ``` Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args: ```javascript const tooltipInstance = await this.tooltip.show( document.querySelector(".my-span"), { component: MyComponent, data: { foo: 1 } } ) ``` Menus are very similar to tooltips and provide the same kind of APIs: ```hbs
  • Foo
  • Bat
  • Baz
``` They also support blocks: ```hbs <:trigger> {{d-icon "plus"}} {{i18n "foo.bar"}} <:content>
  • Foo
  • Bat
  • Baz
``` You can manually show a menu using the `menu` service: ```javascript const menuInstance = await this.menu.show( document.querySelector(".my-span"), options ) // and later manually close or destroy it menuInstance.close(); menuInstance.destroy(); // you can also just close any open tooltip through the service this.menu.close(); ``` The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service: ```javascript const menuInstance = this.menu.register( document.querySelector(".my-span"), options ) // when done you can destroy the instance to remove the listeners menuInstance.destroy(); ``` Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args: ```javascript const menuInstance = await this.menu.show( document.querySelector(".my-span"), { component: MyComponent, data: { foo: 1 } } ) ``` Interacting with toasts is made only through the `toasts` service. A default component is provided (DDefaultToast) and can be used through dedicated service methods: - this.toasts.success({ ... }); - this.toasts.warning({ ... }); - this.toasts.info({ ... }); - this.toasts.error({ ... }); - this.toasts.default({ ... }); ```javascript this.toasts.success({ data: { title: "Foo", message: "Bar", actions: [ { label: "Ok", class: "btn-primary", action: (componentArgs) => { // eslint-disable-next-line no-alert alert("Closing toast:" + componentArgs.data.title); componentArgs.close(); }, } ] }, }); ``` You can also provide your own component: ```javascript this.toasts.show(MyComponent, { autoClose: false, class: "foo", data: { baz: 1 }, }) ``` Co-authored-by: Martin Brennan Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com> Co-authored-by: David Taylor Co-authored-by: Jarek Radosz --- .../app/components/admin-post-menu.gjs | 241 ++++++ .../app/components/bootstrap-mode-notice.hbs | 46 +- .../app/components/composer-editor.js | 3 +- .../app/components/create-topic-button.hbs | 29 +- .../discourse/app/components/d-modal.hbs | 1 + .../discourse/app/components/d-modal.js | 66 -- .../discourse/app/components/d-navigation.hbs | 6 +- .../discourse/app/components/d-popover.hbs | 8 - .../discourse/app/components/d-popover.js | 97 --- .../discourse/app/components/d-tooltip.gjs | 58 -- .../app/components/edit-category-settings.hbs | 5 +- .../{fast-edit.js => fast-edit.gjs} | 33 +- .../discourse/app/components/fast-edit.hbs | 19 - .../groups-form-interaction-fields.hbs | 10 +- .../components/modal/sidebar-section-form.hbs | 23 +- .../post-text-selection-toolbar.gjs | 253 +++++++ .../app/components/post-text-selection.gjs | 279 +++++++ .../discourse/app/components/quote-button.hbs | 71 -- .../discourse/app/components/quote-button.js | 442 ----------- .../app/components/sidebar/section.hbs | 17 +- .../discourse/app/components/summary-box.hbs | 13 +- .../app/components/topic-timeline.js | 3 +- .../discourse/app/components/user-info.hbs | 1 - .../app/components/user-status-message.hbs | 41 +- .../app/components/user-status-message.js | 14 +- .../app/components/user-tip-container.gjs | 40 + .../discourse/app/components/user-tip.js | 1 - .../javascripts/discourse/app/helpers/noop.js | 3 + .../discourse/app/helpers/unique-id.js | 10 + .../app/instance-initializers/d-popover.js | 17 - .../discourse/app/lib/d-popover.js | 79 +- .../discourse/app/lib/d-tooltip.js | 48 -- .../discourse/app/lib/discourse-location.js | 1 + .../discourse/app/lib/plugin-api.js | 27 +- .../app/lib/update-user-status-on-mention.js | 12 +- .../discourse/app/lib/user-status-message.js | 18 +- .../app/lib/user-status-on-autocomplete.js | 4 +- .../lib/virtual-element-from-text-range.js | 4 + .../javascripts/discourse/app/loader-shims.js | 2 +- .../app/mixins/card-contents-base.js | 74 +- .../discourse/app/modifiers/trap-tab.js | 73 ++ .../app/services/admin-post-menu-buttons.js | 10 + .../discourse/app/services/user-tips.js | 77 +- .../discourse/app/templates/application.hbs | 6 +- .../discourse/app/templates/topic.hbs | 2 +- .../discourse/app/widgets/header.js | 1 - .../discourse/app/widgets/post-admin-menu.js | 185 +---- .../discourse/app/widgets/post-cooked.js | 3 +- .../discourse/app/widgets/post-menu.js | 106 ++- .../javascripts/discourse/app/widgets/post.js | 4 - app/assets/javascripts/discourse/package.json | 3 +- .../tests/acceptance/page-publishing-test.js | 2 +- .../acceptance/post-inline-mentions-test.js | 6 +- .../integration/components/d-popover-test.js | 128 ---- .../integration/components/d-tooltip-test.js | 47 -- .../float-kit/d-button-tooltip-test.js | 26 + .../float-kit/d-default-toast-test.js | 86 +++ .../components/float-kit/d-menu-test.js | 186 +++++ .../components/float-kit/d-tooltip-test.js | 192 +++++ .../integration/components/user-info-test.js | 24 +- .../components/user-status-message-test.js | 83 +- .../components/widgets/post-test.js | 96 ++- app/assets/javascripts/float-kit/.npmrc | 1 + .../addon/components/d-button-tooltip.gjs | 8 + .../addon/components/d-default-toast.gjs | 56 ++ .../addon/components/d-float-body.gjs | 85 +++ .../addon/components/d-float-portal.gjs | 18 + .../addon/components/d-inline-float.gjs | 26 + .../addon/components/d-inline-menu.gjs | 27 + .../addon/components/d-inline-tooltip.gjs | 31 + .../float-kit/addon/components/d-menu.gjs | 104 +++ .../float-kit/addon/components/d-popover.gjs | 40 + .../float-kit/addon/components/d-toasts.gjs | 27 + .../float-kit/addon/components/d-tooltip.gjs | 109 +++ .../float-kit/addon/lib/constants.js | 76 ++ .../float-kit/addon/lib/d-menu-instance.js | 63 ++ .../float-kit/addon/lib/d-toast-instance.js | 51 ++ .../float-kit/addon/lib/d-tooltip-instance.js | 63 ++ .../float-kit/addon/lib/float-kit-instance.js | 198 +++++ .../float-kit/addon/lib/get-scroll-parent.js | 13 + .../float-kit/addon/lib/update-position.js | 93 +++ .../addon/modifiers/apply-floating-ui.js | 37 + .../addon/modifiers/close-on-click-outside.js | 38 + .../addon/modifiers/close-on-escape.js | 33 + .../float-kit/addon/services/menu.js | 120 +++ .../float-kit/addon/services/toasts.js | 113 +++ .../float-kit/addon/services/tooltip.js | 120 +++ app/assets/javascripts/float-kit/app/.gitkeep | 0 .../app/components/d-button-tooltip.js | 1 + .../app/components/d-default-toast.gjs | 1 + .../float-kit/app/components/d-inline-menu.js | 1 + .../app/components/d-inline-tooltip.js | 1 + .../float-kit/app/components/d-menu.js | 1 + .../float-kit/app/components/d-popover.js | 1 + .../float-kit/app/components/d-toasts.js | 1 + .../float-kit/app/components/d-tooltip.js | 1 + .../float-kit/app/lib/d-menu-instance.js | 1 + .../float-kit/app/lib/d-tooltip-instance.js | 1 + .../float-kit/app/services/menu.js | 1 + .../float-kit/app/services/toasts.js | 1 + .../float-kit/app/services/tooltip.js | 1 + .../float-kit/config/ember-cli-update.json | 20 + .../javascripts/float-kit/ember-cli-build.js | 27 + app/assets/javascripts/float-kit/index.js | 9 + .../javascripts/float-kit/jsconfig.json | 12 + app/assets/javascripts/float-kit/package.json | 49 ++ app/assets/javascripts/package.json | 1 + app/assets/javascripts/yarn.lock | 52 +- app/assets/stylesheets/color_definitions.scss | 3 + app/assets/stylesheets/common.scss | 3 +- .../stylesheets/common/base/_index.scss | 1 - .../stylesheets/common/base/d-popover.scss | 32 - .../stylesheets/common/base/search-menu.scss | 1 + .../common/base/sidebar-section.scss | 6 + .../stylesheets/common/base/sidebar.scss | 5 + .../stylesheets/common/base/topic-post.scss | 19 +- .../stylesheets/common/base/user-tips.scss | 56 +- .../stylesheets/common/components/_index.scss | 2 +- .../common/components/admin-post-menu.scss | 20 + .../common/components/buttons.scss | 54 ++ .../common/components/d-tooltip.scss | 12 - .../common/components/download-calendar.scss | 2 +- .../common/components/user-card.scss | 7 +- .../components/user-status-message.scss | 16 +- .../stylesheets/common/float-kit/_index.scss | 5 + .../common/float-kit/d-button-tooltip.scss | 12 + .../common/float-kit/d-default-toast.scss | 101 +++ .../stylesheets/common/float-kit/d-menu.scss | 91 +++ .../common/float-kit/d-toasts.scss | 48 ++ .../common/float-kit/d-tooltip.scss | 91 +++ .../desktop/components/user-card.scss | 8 - .../mobile/components/user-card.scss | 9 +- app/assets/stylesheets/vendor/svg-arrow.css | 1 - app/assets/stylesheets/vendor/tippy.css | 1 - docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md | 12 +- .../components/chat-composer-dropdown.hbs | 30 +- .../components/chat-composer-dropdown.js | 62 +- .../discourse/components/chat-composer.hbs | 7 - .../discourse/components/chat-composer.js | 2 +- .../components/chat-message-reaction.gjs | 104 +++ .../components/chat-message-reaction.hbs | 30 - .../components/chat-message-reaction.js | 156 ---- .../discourse/components/chat-message.gjs | 11 +- .../javascripts/discourse/helpers/noop.js | 5 - .../common/chat-composer-dropdown.scss | 27 +- .../stylesheets/common/chat-message.scss | 12 +- .../chat/spec/system/react_to_message_spec.rb | 3 - .../components/chat-channel-test.js | 28 +- .../initializers/discourse-local-dates.js | 48 +- .../common/discourse-local-dates.scss | 36 +- .../spec/system/local_dates_spec.rb | 46 +- .../discourse/components/dummy-component.gjs | 7 + .../components/sections/molecules/menus.hbs | 103 +++ .../components/sections/molecules/menus.js | 112 +++ .../sections/molecules/rich-tooltip.hbs | 10 - .../components/sections/molecules/toasts.hbs | 93 +++ .../components/sections/molecules/toasts.js | 70 ++ .../sections/molecules/tooltips.hbs | 95 +++ .../components/sections/molecules/tooltips.js | 112 +++ .../components/styleguide/component.hbs | 36 +- .../javascripts/discourse/lib/styleguide.js | 8 +- .../assets/stylesheets/styleguide.scss | 45 +- .../styleguide/config/locales/client.en.yml | 10 +- spec/system/page_objects/pages/topic.rb | 7 +- .../assets/svg-icons/discourse-additional.svg | 6 +- yarn.lock | 712 ++++++++++-------- 166 files changed, 5360 insertions(+), 2489 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/admin-post-menu.gjs delete mode 100644 app/assets/javascripts/discourse/app/components/d-popover.hbs delete mode 100644 app/assets/javascripts/discourse/app/components/d-popover.js delete mode 100644 app/assets/javascripts/discourse/app/components/d-tooltip.gjs rename app/assets/javascripts/discourse/app/components/{fast-edit.js => fast-edit.gjs} (56%) delete mode 100644 app/assets/javascripts/discourse/app/components/fast-edit.hbs create mode 100644 app/assets/javascripts/discourse/app/components/post-text-selection-toolbar.gjs create mode 100644 app/assets/javascripts/discourse/app/components/post-text-selection.gjs delete mode 100644 app/assets/javascripts/discourse/app/components/quote-button.hbs delete mode 100644 app/assets/javascripts/discourse/app/components/quote-button.js create mode 100644 app/assets/javascripts/discourse/app/components/user-tip-container.gjs create mode 100644 app/assets/javascripts/discourse/app/helpers/noop.js create mode 100644 app/assets/javascripts/discourse/app/helpers/unique-id.js delete mode 100644 app/assets/javascripts/discourse/app/instance-initializers/d-popover.js delete mode 100644 app/assets/javascripts/discourse/app/lib/d-tooltip.js create mode 100644 app/assets/javascripts/discourse/app/modifiers/trap-tab.js create mode 100644 app/assets/javascripts/discourse/app/services/admin-post-menu-buttons.js delete mode 100644 app/assets/javascripts/discourse/tests/integration/components/d-popover-test.js delete mode 100644 app/assets/javascripts/discourse/tests/integration/components/d-tooltip-test.js create mode 100644 app/assets/javascripts/discourse/tests/integration/components/float-kit/d-button-tooltip-test.js create mode 100644 app/assets/javascripts/discourse/tests/integration/components/float-kit/d-default-toast-test.js create mode 100644 app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js create mode 100644 app/assets/javascripts/discourse/tests/integration/components/float-kit/d-tooltip-test.js create mode 100644 app/assets/javascripts/float-kit/.npmrc create mode 100644 app/assets/javascripts/float-kit/addon/components/d-button-tooltip.gjs create mode 100644 app/assets/javascripts/float-kit/addon/components/d-default-toast.gjs create mode 100644 app/assets/javascripts/float-kit/addon/components/d-float-body.gjs create mode 100644 app/assets/javascripts/float-kit/addon/components/d-float-portal.gjs create mode 100644 app/assets/javascripts/float-kit/addon/components/d-inline-float.gjs create mode 100644 app/assets/javascripts/float-kit/addon/components/d-inline-menu.gjs create mode 100644 app/assets/javascripts/float-kit/addon/components/d-inline-tooltip.gjs create mode 100644 app/assets/javascripts/float-kit/addon/components/d-menu.gjs create mode 100644 app/assets/javascripts/float-kit/addon/components/d-popover.gjs create mode 100644 app/assets/javascripts/float-kit/addon/components/d-toasts.gjs create mode 100644 app/assets/javascripts/float-kit/addon/components/d-tooltip.gjs create mode 100644 app/assets/javascripts/float-kit/addon/lib/constants.js create mode 100644 app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js create mode 100644 app/assets/javascripts/float-kit/addon/lib/d-toast-instance.js create mode 100644 app/assets/javascripts/float-kit/addon/lib/d-tooltip-instance.js create mode 100644 app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js create mode 100644 app/assets/javascripts/float-kit/addon/lib/get-scroll-parent.js create mode 100644 app/assets/javascripts/float-kit/addon/lib/update-position.js create mode 100644 app/assets/javascripts/float-kit/addon/modifiers/apply-floating-ui.js create mode 100644 app/assets/javascripts/float-kit/addon/modifiers/close-on-click-outside.js create mode 100644 app/assets/javascripts/float-kit/addon/modifiers/close-on-escape.js create mode 100644 app/assets/javascripts/float-kit/addon/services/menu.js create mode 100644 app/assets/javascripts/float-kit/addon/services/toasts.js create mode 100644 app/assets/javascripts/float-kit/addon/services/tooltip.js create mode 100644 app/assets/javascripts/float-kit/app/.gitkeep create mode 100644 app/assets/javascripts/float-kit/app/components/d-button-tooltip.js create mode 100644 app/assets/javascripts/float-kit/app/components/d-default-toast.gjs create mode 100644 app/assets/javascripts/float-kit/app/components/d-inline-menu.js create mode 100644 app/assets/javascripts/float-kit/app/components/d-inline-tooltip.js create mode 100644 app/assets/javascripts/float-kit/app/components/d-menu.js create mode 100644 app/assets/javascripts/float-kit/app/components/d-popover.js create mode 100644 app/assets/javascripts/float-kit/app/components/d-toasts.js create mode 100644 app/assets/javascripts/float-kit/app/components/d-tooltip.js create mode 100644 app/assets/javascripts/float-kit/app/lib/d-menu-instance.js create mode 100644 app/assets/javascripts/float-kit/app/lib/d-tooltip-instance.js create mode 100644 app/assets/javascripts/float-kit/app/services/menu.js create mode 100644 app/assets/javascripts/float-kit/app/services/toasts.js create mode 100644 app/assets/javascripts/float-kit/app/services/tooltip.js create mode 100644 app/assets/javascripts/float-kit/config/ember-cli-update.json create mode 100644 app/assets/javascripts/float-kit/ember-cli-build.js create mode 100644 app/assets/javascripts/float-kit/index.js create mode 100644 app/assets/javascripts/float-kit/jsconfig.json create mode 100644 app/assets/javascripts/float-kit/package.json delete mode 100644 app/assets/stylesheets/common/base/d-popover.scss create mode 100644 app/assets/stylesheets/common/components/admin-post-menu.scss delete mode 100644 app/assets/stylesheets/common/components/d-tooltip.scss create mode 100644 app/assets/stylesheets/common/float-kit/_index.scss create mode 100644 app/assets/stylesheets/common/float-kit/d-button-tooltip.scss create mode 100644 app/assets/stylesheets/common/float-kit/d-default-toast.scss create mode 100644 app/assets/stylesheets/common/float-kit/d-menu.scss create mode 100644 app/assets/stylesheets/common/float-kit/d-toasts.scss create mode 100644 app/assets/stylesheets/common/float-kit/d-tooltip.scss delete mode 100644 app/assets/stylesheets/vendor/svg-arrow.css delete mode 100644 app/assets/stylesheets/vendor/tippy.css create mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.gjs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.hbs delete mode 100644 plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.js delete mode 100644 plugins/chat/assets/javascripts/discourse/helpers/noop.js create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/dummy-component.gjs create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/menus.hbs create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/menus.js delete mode 100644 plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/rich-tooltip.hbs create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/toasts.hbs create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/toasts.js create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/tooltips.hbs create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/tooltips.js diff --git a/app/assets/javascripts/discourse/app/components/admin-post-menu.gjs b/app/assets/javascripts/discourse/app/components/admin-post-menu.gjs new file mode 100644 index 00000000000..91edb191c3f --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/admin-post-menu.gjs @@ -0,0 +1,241 @@ +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; +import DButton from "discourse/components/d-button"; +import { fn } from "@ember/helper"; +import and from "truth-helpers/helpers/and"; +import or from "truth-helpers/helpers/or"; +import not from "truth-helpers/helpers/not"; +import { action } from "@ember/object"; +import concatClass from "discourse/helpers/concat-class"; + +export default class AdminPostMenu extends Component { + @service currentUser; + @service siteSettings; + @service store; + @service adminPostMenuButtons; + + + + get reviewUrl() { + return `/review?topic_id=${this.args.data.transformedPost.id}&status=all`; + } + + get extraButtons() { + return this.adminPostMenuButtons.callbacks + .map((callback) => { + return callback(this.args.data.transformedPost); + }) + .filter(Boolean); + } + + @action + async topicAction(actionName) { + await this.args.close(); + + try { + await this.args.data[actionName]?.(); + } catch (error) { + // eslint-disable-next-line no-console + console.error(`Unknown error while attempting \`${actionName}\`:`, error); + } + + await this.args.data.scheduleRerender(); + } + + @action + async extraAction(button) { + await this.args.close(); + await button.action(this.args.data.post); + await this.args.data.scheduleRerender(); + } +} diff --git a/app/assets/javascripts/discourse/app/components/bootstrap-mode-notice.hbs b/app/assets/javascripts/discourse/app/components/bootstrap-mode-notice.hbs index 8c1aab46f29..d4f80b8f439 100644 --- a/app/assets/javascripts/discourse/app/components/bootstrap-mode-notice.hbs +++ b/app/assets/javascripts/discourse/app/components/bootstrap-mode-notice.hbs @@ -1,20 +1,28 @@ - - {{#if this.showUserTip}} - - {{else}} - -
-
- {{i18n "user_tips.admin_guide.title"}} + + <:button> + + {{#if this.showUserTip}} + + {{/if}} + + + + <:tooltip> + {{#unless this.showUserTip}} + +
+
+ {{i18n "user_tips.admin_guide.title"}} +
+
+ {{i18n "user_tips.admin_guide.content_no_url"}} +
-
- {{i18n "user_tips.admin_guide.content_no_url"}} -
-
- - {{/if}} - \ No newline at end of file + + {{/unless}} + + \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js index cee2e5f20d8..b98eeaf22ae 100644 --- a/app/assets/javascripts/discourse/app/components/composer-editor.js +++ b/app/assets/javascripts/discourse/app/components/composer-editor.js @@ -42,6 +42,7 @@ import { initUserStatusHtml, renderUserStatusHtml, } from "discourse/lib/user-status-on-autocomplete"; +import { getOwner } from "discourse-common/lib/get-owner"; // original string `![image|foo=bar|690x220, 50%|bar=baz](upload://1TjaobgKObzpU7xRMw2HuUc87vO.png "image title")` // group 1 `image|foo=bar` @@ -223,7 +224,7 @@ export default Component.extend(ComposerUploadUppy, { categoryId: this.topic?.category_id || this.composer?.categoryId, includeGroups: true, }).then((result) => { - initUserStatusHtml(result.users); + initUserStatusHtml(getOwner(this), result.users); return result; }); }, diff --git a/app/assets/javascripts/discourse/app/components/create-topic-button.hbs b/app/assets/javascripts/discourse/app/components/create-topic-button.hbs index 5b6e7b826b9..34bdcd2c512 100644 --- a/app/assets/javascripts/discourse/app/components/create-topic-button.hbs +++ b/app/assets/javascripts/discourse/app/components/create-topic-button.hbs @@ -1,11 +1,22 @@ {{#if this.canCreateTopic}} - - {{yield}} + + <:button> + + + <:tooltip> + {{#if @disabled}} + + {{/if}} + + {{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/d-modal.hbs b/app/assets/javascripts/discourse/app/components/d-modal.hbs index d91888fe2f7..e067b5f2c29 100644 --- a/app/assets/javascripts/discourse/app/components/d-modal.hbs +++ b/app/assets/javascripts/discourse/app/components/d-modal.hbs @@ -20,6 +20,7 @@ {{did-insert this.setupListeners}} {{will-destroy this.cleanupListeners}} {{on "mouseup" this.handleMouseUp}} + {{trap-tab (hash preventScroll=false)}} > {{/if}} diff --git a/app/assets/javascripts/discourse/app/components/post-text-selection-toolbar.gjs b/app/assets/javascripts/discourse/app/components/post-text-selection-toolbar.gjs new file mode 100644 index 00000000000..d36e8630a89 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/post-text-selection-toolbar.gjs @@ -0,0 +1,253 @@ +import { ajax } from "discourse/lib/ajax"; +import { postUrl, setCaretPosition } from "discourse/lib/utilities"; +import Sharing from "discourse/lib/sharing"; +import { action } from "@ember/object"; +import { getAbsoluteURL } from "discourse-common/lib/get-url"; +import { inject as service } from "@ember/service"; +import FastEditModal from "discourse/components/modal/fast-edit"; +import Component from "@glimmer/component"; +import concatClass from "discourse/helpers/concat-class"; +import DButton from "discourse/components/d-button"; +import { fn } from "@ember/helper"; +import PluginOutlet from "discourse/components/plugin-outlet"; +import FastEdit from "discourse/components/fast-edit"; +import { on } from "@ember/modifier"; +import { tracked } from "@glimmer/tracking"; +import { modifier } from "ember-modifier"; + +export function fixQuotes(str) { + // u+201c, u+201d = “ ” + // u+2018, u+2019 = ‘ ’ + return str.replace(/[\u201C\u201D]/g, '"').replace(/[\u2018\u2019]/g, "'"); +} + +export default class PostTextSelectionToolbar extends Component { + + + @service currentUser; + @service modal; + @service site; + @service siteSettings; + @service appEvents; + + @tracked isFastEditing = false; + + appEventsListeners = modifier(() => { + this.appEvents.on("quote-button:edit", this, "toggleFastEdit"); + + return () => { + this.appEvents.off("quote-button:edit", this, "toggleFastEdit"); + }; + }); + + get topic() { + return this.args.data.topic; + } + + get quoteState() { + return this.args.data.quoteState; + } + + get post() { + return this.topic.postStream.findLoadedPost( + this.args.data.quoteState.postId + ); + } + + get quoteSharingEnabled() { + return ( + this.site.desktopView && + this.quoteSharingSources.length > 0 && + !this.topic.invisible && + !this.topic.category?.read_restricted && + (this.siteSettings.share_quote_visibility === "all" || + (this.siteSettings.share_quote_visibility === "anonymous" && + !this.currentUser)) + ); + } + + get quoteSharingSources() { + return Sharing.activeSources( + this.siteSettings.share_quote_buttons, + this.siteSettings.login_required || this.topic.isPrivateMessage + ); + } + + get quoteSharingShowLabel() { + return this.quoteSharingSources.length > 1; + } + + get shareUrl() { + return getAbsoluteURL( + postUrl(this.topic.slug, this.topic.id, this.post.post_number) + ); + } + + get embedQuoteButton() { + const canCreatePost = this.topic.details.can_create_post; + const canReplyAsNewTopic = this.topic.details.can_reply_as_new_topic; + + return ( + (canCreatePost || canReplyAsNewTopic) && + this.currentUser?.get("user_option.enable_quoting") + ); + } + + @action + trapEvents(event) { + event.stopPropagation(); + } + + @action + async closeFastEdit() { + this.isFastEditing = false; + await this.args.data.hideToolbar(); + } + + @action + async toggleFastEdit() { + if (this.args.data.supportsFastEdit) { + if (this.site.desktopView) { + this.isFastEditing = !this.isFastEditing; + } else { + this.modal.show(FastEditModal, { + model: { + initialValue: this.args.data.quoteState.buffer, + post: this.post, + }, + }); + this.args.data.hideToolbar(); + } + } else { + const result = await ajax(`/posts/${this.post.id}`); + + if (this.isDestroying || this.isDestroyed) { + return; + } + + let bestIndex = 0; + const rows = result.raw.split("\n"); + + // selecting even a part of the text of a list item will include + // "* " at the beginning of the buffer, we remove it to be able + // to find it in row + const buffer = fixQuotes( + this.args.data.quoteState.buffer.split("\n")[0].replace(/^\* /, "") + ); + + rows.some((row, index) => { + if (row.length && row.includes(buffer)) { + bestIndex = index; + return true; + } + }); + + this.args.data.editPost(this.post); + + document + .querySelector("#reply-control") + ?.addEventListener("transitionend", () => { + const textarea = document.querySelector(".d-editor-input"); + if (!textarea || this.isDestroyed || this.isDestroying) { + return; + } + + // best index brings us to one row before as slice start from 1 + // we add 1 to be at the beginning of next line, unless we start from top + setCaretPosition( + textarea, + rows.slice(0, bestIndex).join("\n").length + (bestIndex > 0 ? 1 : 0) + ); + + // ensures we correctly scroll to caret and reloads composer + // if we do another selection/edit + textarea.blur(); + textarea.focus(); + }); + + this.args.data.hideToolbar(); + return; + } + } + + @action + share(source) { + Sharing.shareSource(source, { + url: this.shareUrl, + title: this.topic.title, + quote: window.getSelection().toString(), + }); + } +} diff --git a/app/assets/javascripts/discourse/app/components/post-text-selection.gjs b/app/assets/javascripts/discourse/app/components/post-text-selection.gjs new file mode 100644 index 00000000000..8165bde932d --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/post-text-selection.gjs @@ -0,0 +1,279 @@ +import { + selectedNode, + selectedRange, + selectedText, +} from "discourse/lib/utilities"; +import { INPUT_DELAY } from "discourse-common/config/environment"; +import { action } from "@ember/object"; +import { bind } from "discourse-common/utils/decorators"; +import discourseDebounce from "discourse-common/lib/debounce"; +import toMarkdown from "discourse/lib/to-markdown"; +import escapeRegExp from "discourse-common/utils/escape-regexp"; +import virtualElementFromTextRange from "discourse/lib/virtual-element-from-text-range"; +import { inject as service } from "@ember/service"; +import Component from "@glimmer/component"; +import { modifier } from "ember-modifier"; +import PostTextSelectionToolbar from "discourse/components/post-text-selection-toolbar"; +import { cancel } from "@ember/runloop"; +import discourseLater from "discourse-common/lib/later"; + +function getQuoteTitle(element) { + const titleEl = element.querySelector(".title"); + if (!titleEl) { + return; + } + + const titleLink = titleEl.querySelector("a:not(.back)"); + if (titleLink) { + return titleLink.textContent.trim(); + } + + return titleEl.textContent.trim().replace(/:$/, ""); +} + +export function fixQuotes(str) { + // u+201c, u+201d = “ ” + // u+2018, u+2019 = ‘ ’ + return str.replace(/[\u201C\u201D]/g, '"').replace(/[\u2018\u2019]/g, "'"); +} + +export default class PostTextSelection extends Component { + + + @service appEvents; + @service capabilities; + @service currentUser; + @service site; + @service siteSettings; + @service menu; + + prevSelection; + + runLoopHandlers = modifier(() => { + return () => { + cancel(this.selectionChangeHandler); + cancel(this.holdingMouseDownHandle); + }; + }); + + documentListeners = modifier(() => { + document.addEventListener("mousedown", this.mousedown, { passive: true }); + document.addEventListener("mouseup", this.mouseup, { passive: true }); + document.addEventListener("selectionchange", this.selectionchange); + + return () => { + document.removeEventListener("mousedown", this.mousedown); + document.removeEventListener("mouseup", this.mouseup); + document.removeEventListener("selectionchange", this.selectionchange); + }; + }); + + appEventsListeners = modifier(() => { + this.appEvents.on("quote-button:quote", this, "insertQuote"); + + return () => { + this.appEvents.off("quote-button:quote", this, "insertQuote"); + }; + }); + + willDestroy() { + super.willDestroy(...arguments); + + this.menuInstance?.destroy(); + cancel(this.selectionChangedHandler); + } + + @bind + async hideToolbar() { + this.args.quoteState.clear(); + await this.menuInstance?.close(); + } + + @bind + async selectionChanged() { + let supportsFastEdit = this.canEditPost; + const selection = window.getSelection(); + + if (selection.isCollapsed) { + return; + } + + // ensure we selected content inside 1 post *only* + let postId; + for (let r = 0; r < selection.rangeCount; r++) { + const range = selection.getRangeAt(r); + const selectionStart = + range.startContainer.nodeType === Node.ELEMENT_NODE + ? range.startContainer + : range.startContainer.parentElement; + const ancestor = + range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE + ? range.commonAncestorContainer + : range.commonAncestorContainer.parentElement; + + if (!selectionStart.closest(".cooked")) { + return await this.hideToolbar(); + } + + postId ||= ancestor.closest(".boxed, .reply")?.dataset?.postId; + + if (!ancestor.closest(".contents") || !postId) { + return await this.hideToolbar(); + } + } + + const _selectedElement = + selectedNode().nodeType === Node.ELEMENT_NODE + ? selectedNode() + : selectedNode().parentElement; + const _selectedText = selectedText(); + const cooked = + _selectedElement.querySelector(".cooked") || + _selectedElement.closest(".cooked"); + + // computing markdown takes a lot of time on long posts + // this code attempts to compute it only when we can't fast track + let opts = { + full: + selectedRange().startOffset > 0 + ? false + : _selectedText === toMarkdown(cooked.innerHTML), + }; + + for ( + let element = _selectedElement; + element && element.tagName !== "ARTICLE"; + element = element.parentElement + ) { + if (element.tagName === "ASIDE" && element.classList.contains("quote")) { + opts.username = element.dataset.username || getQuoteTitle(element); + opts.post = element.dataset.post; + opts.topic = element.dataset.topic; + break; + } + } + + const quoteState = this.args.quoteState; + quoteState.selected(postId, _selectedText, opts); + + if (this.canEditPost) { + const regexp = new RegExp(escapeRegExp(quoteState.buffer), "gi"); + const matches = cooked.innerHTML.match(regexp); + const non_ascii_regex = /[^\x00-\x7F]/; + + if ( + quoteState.buffer.length === 0 || + quoteState.buffer.includes("|") || // tables are too complex + quoteState.buffer.match(/\n/g) || // linebreaks are too complex + matches?.length > 1 || // duplicates are too complex + non_ascii_regex.test(quoteState.buffer) // non-ascii chars break fast-edit + ) { + supportsFastEdit = false; + } else if (matches?.length === 1) { + supportsFastEdit = true; + } + } + + // avoid hard loops in quote selection unconditionally + // this can happen if you triple click text in firefox + if (this.menuInstance?.expanded && this.prevSelection === _selectedText) { + return; + } + + this.prevSelection = _selectedText; + + // on Desktop, shows the button at the beginning of the selection + // on Mobile, shows the button at the end of the selection + const { isIOS, isAndroid, isOpera } = this.capabilities; + const showAtEnd = this.site.isMobileDevice || isIOS || isAndroid || isOpera; + const options = { + component: PostTextSelectionToolbar, + inline: true, + placement: showAtEnd ? "bottom-start" : "top-start", + fallbackPlacements: showAtEnd + ? ["bottom-end", "top-start"] + : ["bottom-start"], + offset: showAtEnd ? 25 : 3, + trapTab: false, + data: { + canEditPost: this.canEditPost, + editPost: this.args.editPost, + supportsFastEdit, + topic: this.args.topic, + quoteState, + insertQuote: this.insertQuote, + hideToolbar: this.hideToolbar, + }, + }; + + this.menuInstance?.destroy(); + + this.menuInstance = await this.menu.show( + virtualElementFromTextRange(), + options + ); + } + + @bind + onSelectionChanged() { + const { isIOS, isWinphone, isAndroid } = this.capabilities; + const wait = isIOS || isWinphone || isAndroid ? INPUT_DELAY : 100; + this.selectionChangedHandler = discourseDebounce( + this, + this.selectionChanged, + wait + ); + } + + @bind + async mousedown() { + this.isMousedown = true; + this.holdingMouseDown = false; + this.holdingMouseDownHandler = discourseLater(() => { + this.holdingMouseDown = true; + }, 100); + } + + @bind + async mouseup() { + this.prevSelection = null; + this.isMousedown = false; + + if (this.holdingMouseDown) { + this.onSelectionChanged(); + } + } + + @bind + selectionchange() { + cancel(this.selectionChangeHandler); + this.selectionChangeHandler = discourseLater(() => { + if (!this.isMousedown) { + this.onSelectionChanged(); + } + }, 100); + } + + get post() { + return this.args.topic.postStream.findLoadedPost( + this.args.quoteState.postId + ); + } + + get canEditPost() { + return this.siteSettings.enable_fast_edit && this.post?.can_edit; + } + + @action + async insertQuote() { + await this.args.selectText(); + await this.hideToolbar(); + } +} diff --git a/app/assets/javascripts/discourse/app/components/quote-button.hbs b/app/assets/javascripts/discourse/app/components/quote-button.hbs deleted file mode 100644 index bb4ab12704e..00000000000 --- a/app/assets/javascripts/discourse/app/components/quote-button.hbs +++ /dev/null @@ -1,71 +0,0 @@ -
-
- {{#if this.embedQuoteButton}} - - {{/if}} - - {{#if this.canEditPost}} - - {{/if}} - - {{#if this.quoteSharingEnabled}} - - {{#if this.quoteSharingShowLabel}} - - {{/if}} - - - {{#each this.quoteSharingSources as |source|}} - - {{/each}} - - - - - {{/if}} -
- -
- {{#if this.displayFastEditInput}} - - {{/if}} - - -
-
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/quote-button.js b/app/assets/javascripts/discourse/app/components/quote-button.js deleted file mode 100644 index f6d449bb2a6..00000000000 --- a/app/assets/javascripts/discourse/app/components/quote-button.js +++ /dev/null @@ -1,442 +0,0 @@ -import Component from "@glimmer/component"; -import { tracked } from "@glimmer/tracking"; -import { ajax } from "discourse/lib/ajax"; -import { - postUrl, - selectedNode, - selectedRange, - selectedText, - setCaretPosition, -} from "discourse/lib/utilities"; -import { INPUT_DELAY } from "discourse-common/config/environment"; -import Sharing from "discourse/lib/sharing"; -import { action } from "@ember/object"; -import { bind } from "discourse-common/utils/decorators"; -import discourseDebounce from "discourse-common/lib/debounce"; -import { getAbsoluteURL } from "discourse-common/lib/get-url"; -import { next, schedule } from "@ember/runloop"; -import toMarkdown from "discourse/lib/to-markdown"; -import escapeRegExp from "discourse-common/utils/escape-regexp"; -import { createPopper } from "@popperjs/core"; -import virtualElementFromTextRange from "discourse/lib/virtual-element-from-text-range"; -import { inject as service } from "@ember/service"; -import FastEditModal from "discourse/components/modal/fast-edit"; - -function getQuoteTitle(element) { - const titleEl = element.querySelector(".title"); - if (!titleEl) { - return; - } - - const titleLink = titleEl.querySelector("a:not(.back)"); - if (titleLink) { - return titleLink.textContent.trim(); - } - - return titleEl.textContent.trim().replace(/:$/, ""); -} - -export function fixQuotes(str) { - // u+201c, u+201d = “ ” - // u+2018, u+2019 = ‘ ’ - return str.replace(/[\u201C\u201D]/g, '"').replace(/[\u2018\u2019]/g, "'"); -} - -export default class QuoteButton extends Component { - @service appEvents; - @service capabilities; - @service currentUser; - @service modal; - @service site; - @service siteSettings; - - @tracked visible = false; - @tracked animated = false; - @tracked isFastEditable = false; - @tracked displayFastEditInput = false; - @tracked fastEditInitialSelection; - - isMouseDown = false; - reselected = false; - prevSelection; - element; - popper; - popperPlacement = "top-start"; - popperOffset = [0, 3]; - - @bind - hideButton() { - this.args.quoteState.clear(); - - this.visible = false; - this.animated = false; - this.isFastEditable = false; - this.displayFastEditInput = false; - this.fastEditInitialSelection = null; - - this.teardownSelectionListeners(); - } - - selectionChanged() { - if (this.displayFastEditInput) { - this.textRange = virtualElementFromTextRange(); - return; - } - - const selection = window.getSelection(); - if (selection.isCollapsed) { - if (this.visible) { - this.hideButton(); - } - return; - } - - // ensure we selected content inside 1 post *only* - let postId; - for (let r = 0; r < selection.rangeCount; r++) { - const range = selection.getRangeAt(r); - const selectionStart = - range.startContainer.nodeType === Node.ELEMENT_NODE - ? range.startContainer - : range.startContainer.parentElement; - const ancestor = - range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE - ? range.commonAncestorContainer - : range.commonAncestorContainer.parentElement; - - if (!selectionStart.closest(".cooked")) { - return; - } - - postId ||= ancestor.closest(".boxed, .reply")?.dataset?.postId; - - if (!ancestor.closest(".contents") || !postId) { - if (this.visible) { - this.hideButton(); - } - return; - } - } - - const _selectedElement = - selectedNode().nodeType === Node.ELEMENT_NODE - ? selectedNode() - : selectedNode().parentElement; - const _selectedText = selectedText(); - const cooked = - _selectedElement.querySelector(".cooked") || - _selectedElement.closest(".cooked"); - - // computing markdown takes a lot of time on long posts - // this code attempts to compute it only when we can't fast track - let opts = { - full: - selectedRange().startOffset > 0 - ? false - : _selectedText === toMarkdown(cooked.innerHTML), - }; - - for ( - let element = _selectedElement; - element && element.tagName !== "ARTICLE"; - element = element.parentElement - ) { - if (element.tagName === "ASIDE" && element.classList.contains("quote")) { - opts.username = element.dataset.username || getQuoteTitle(element); - opts.post = element.dataset.post; - opts.topic = element.dataset.topic; - break; - } - } - - const quoteState = this.args.quoteState; - quoteState.selected(postId, _selectedText, opts); - this.visible = quoteState.buffer.length > 0; - - if (this.canEditPost) { - const regexp = new RegExp(escapeRegExp(quoteState.buffer), "gi"); - const matches = cooked.innerHTML.match(regexp); - const non_ascii_regex = /[^\x00-\x7F]/; - - if ( - quoteState.buffer.length === 0 || - quoteState.buffer.includes("|") || // tables are too complex - quoteState.buffer.match(/\n/g) || // linebreaks are too complex - matches?.length > 1 || // duplicates are too complex - non_ascii_regex.test(quoteState.buffer) // non-ascii chars break fast-edit - ) { - this.isFastEditable = false; - this.fastEditInitialSelection = null; - } else if (matches?.length === 1) { - this.isFastEditable = true; - this.fastEditInitialSelection = quoteState.buffer; - } - } - - // avoid hard loops in quote selection unconditionally - // this can happen if you triple click text in firefox - if (this.prevSelection === _selectedText) { - return; - } - - this.prevSelection = _selectedText; - - // on Desktop, shows the button at the beginning of the selection - // on Mobile, shows the button at the end of the selection - const isMobileDevice = this.site.isMobileDevice; - const { isIOS, isAndroid, isOpera } = this.capabilities; - const showAtEnd = isMobileDevice || isIOS || isAndroid || isOpera; - - if (showAtEnd) { - this.popperPlacement = "bottom-start"; - this.popperOffset = [0, 25]; - } - - // change the position of the button - schedule("afterRender", () => { - if (!this.element || this.isDestroying || this.isDestroyed) { - return; - } - - this.textRange = virtualElementFromTextRange(); - this.setupSelectionListeners(); - - this.popper = createPopper(this.textRange, this.element, { - placement: this.popperPlacement, - modifiers: [ - { - name: "computeStyles", - options: { - adaptive: false, - }, - }, - { - name: "offset", - options: { - offset: this.popperOffset, - }, - }, - ], - }); - - if (!this.animated) { - // We only enable CSS transitions after the initial positioning - // otherwise the button can appear to fly in from off-screen - next(() => (this.animated = true)); - } - }); - } - - @bind - updateRect() { - this.textRange?.updateRect(); - } - - setupSelectionListeners() { - document.body.addEventListener("mouseup", this.updateRect); - window.addEventListener("scroll", this.updateRect); - document.scrollingElement.addEventListener("scroll", this.updateRect); - } - - teardownSelectionListeners() { - document.body.removeEventListener("mouseup", this.updateRect); - window.removeEventListener("scroll", this.updateRect); - document.scrollingElement.removeEventListener("scroll", this.updateRect); - } - - @bind - onSelectionChanged() { - const { isWinphone, isAndroid } = this.capabilities; - const wait = isWinphone || isAndroid ? INPUT_DELAY : 25; - discourseDebounce(this, this.selectionChanged, wait); - } - - @bind - mousedown(e) { - this.prevSelection = null; - this.isMouseDown = true; - this.reselected = false; - - // prevents fast-edit input event from triggering mousedown - if (e.target.classList.contains("fast-edit-input")) { - return; - } - - if (!e.target.closest(".quote-button, .create, .share, .reply-new")) { - this.hideButton(); - } - } - - @bind - mouseup(e) { - // prevents fast-edit input event from triggering mouseup - if (e.target.classList.contains("fast-edit-input")) { - return; - } - - this.prevSelection = null; - this.isMouseDown = false; - this.onSelectionChanged(); - } - - @bind - selectionchange() { - if (!this.isMouseDown && !this.reselected) { - this.onSelectionChanged(); - } - } - - @action - didInsert(element) { - this.element = element; - - document.addEventListener("mousedown", this.mousedown); - document.addEventListener("mouseup", this.mouseup); - document.addEventListener("selectionchange", this.selectionchange); - - this.appEvents.on("quote-button:quote", this, "insertQuote"); - this.appEvents.on("quote-button:edit", this, "toggleFastEditForm"); - } - - willDestroy() { - super.willDestroy(...arguments); - this.popper?.destroy(); - - document.removeEventListener("mousedown", this.mousedown); - document.removeEventListener("mouseup", this.mouseup); - document.removeEventListener("selectionchange", this.selectionchange); - - this.appEvents.off("quote-button:quote", this, "insertQuote"); - this.appEvents.off("quote-button:edit", this, "toggleFastEditForm"); - this.teardownSelectionListeners(); - } - - get post() { - return this.args.topic.postStream.findLoadedPost( - this.args.quoteState.postId - ); - } - - get quoteSharingEnabled() { - return ( - this.site.desktopView && - this.quoteSharingSources.length > 0 && - !this.args.topic.invisible && - !this.args.topic.category?.read_restricted && - (this.siteSettings.share_quote_visibility === "all" || - (this.siteSettings.share_quote_visibility === "anonymous" && - !this.currentUser)) - ); - } - - get quoteSharingSources() { - return Sharing.activeSources( - this.siteSettings.share_quote_buttons, - this.siteSettings.login_required || this.args.topic.isPrivateMessage - ); - } - - get quoteSharingShowLabel() { - return this.quoteSharingSources.length > 1; - } - - get shareUrl() { - return getAbsoluteURL( - postUrl(this.args.topic.slug, this.args.topic.id, this.post.post_number) - ); - } - - get embedQuoteButton() { - const canCreatePost = this.args.topic.details.can_create_post; - const canReplyAsNewTopic = this.args.topic.details.can_reply_as_new_topic; - - return ( - (canCreatePost || canReplyAsNewTopic) && - this.currentUser?.get("user_option.enable_quoting") - ); - } - - get canEditPost() { - return this.siteSettings.enable_fast_edit && this.post?.can_edit; - } - - @action - async insertQuote() { - await this.args.selectText(); - this.hideButton(); - } - - @action - async toggleFastEditForm() { - if (this.isFastEditable) { - if (this.site.desktopView) { - this.displayFastEditInput = !this.displayFastEditInput; - } else { - this.modal.show(FastEditModal, { - model: { - initialValue: this.fastEditInitialSelection, - post: this.post, - }, - }); - this.hideButton(); - } - - return; - } - - const result = await ajax(`/posts/${this.post.id}`); - - if (this.isDestroying || this.isDestroyed) { - return; - } - - let bestIndex = 0; - const rows = result.raw.split("\n"); - - // selecting even a part of the text of a list item will include - // "* " at the beginning of the buffer, we remove it to be able - // to find it in row - const buffer = fixQuotes( - this.args.quoteState.buffer.split("\n")[0].replace(/^\* /, "") - ); - - rows.some((row, index) => { - if (row.length && row.includes(buffer)) { - bestIndex = index; - return true; - } - }); - - this.args.editPost(this.post); - - document - .querySelector("#reply-control") - ?.addEventListener("transitionend", () => { - const textarea = document.querySelector(".d-editor-input"); - if (!textarea || this.isDestroyed || this.isDestroying) { - return; - } - - // best index brings us to one row before as slice start from 1 - // we add 1 to be at the beginning of next line, unless we start from top - setCaretPosition( - textarea, - rows.slice(0, bestIndex).join("\n").length + (bestIndex > 0 ? 1 : 0) - ); - - // ensures we correctly scroll to caret and reloads composer - // if we do another selection/edit - textarea.blur(); - textarea.focus(); - }); - } - - @action - share(source) { - Sharing.shareSource(source, { - url: this.shareUrl, - title: this.args.topic.title, - quote: window.getSelection().toString(), - }); - } -} diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/section.hbs index 5ec274885a1..af578be224f 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/section.hbs +++ b/app/assets/javascripts/discourse/app/components/sidebar/section.hbs @@ -20,13 +20,18 @@ {{@headerLinkText}} + {{#if @indicatePublic}} - - {{d-icon "globe"}} - {{d-icon "shield-alt"}} - {{i18n "sidebar.sections.global_section"}} - - + + {{d-icon "shield-alt"}}{{i18n + "sidebar.sections.global_section" + }} + {{/if}} diff --git a/app/assets/javascripts/discourse/app/components/summary-box.hbs b/app/assets/javascripts/discourse/app/components/summary-box.hbs index 5da801b60dc..d8053f2dfcc 100644 --- a/app/assets/javascripts/discourse/app/components/summary-box.hbs +++ b/app/assets/javascripts/discourse/app/components/summary-box.hbs @@ -50,12 +50,15 @@

{{i18n "summary.summarized_on" date=this.summarizedOn}} - - {{d-icon "info-circle"}} - + + + <:trigger> + {{d-icon "info-circle"}} + + <:content> {{i18n "summary.model_used" model=this.summarizedBy}} - - + +

{{#if this.outdated}} diff --git a/app/assets/javascripts/discourse/app/components/topic-timeline.js b/app/assets/javascripts/discourse/app/components/topic-timeline.js index 92c4f6d545a..6477331c7a1 100644 --- a/app/assets/javascripts/discourse/app/components/topic-timeline.js +++ b/app/assets/javascripts/discourse/app/components/topic-timeline.js @@ -52,7 +52,7 @@ export default class TopicTimeline extends Component { } @bind - addUserTip(element) { + addUserTip() { if (!this.currentUser) { return; } @@ -62,7 +62,6 @@ export default class TopicTimeline extends Component { titleText: I18n.t("user_tips.topic_timeline.title"), contentText: I18n.t("user_tips.topic_timeline.content"), reference: document.querySelector("div.timeline-scrollarea-wrapper"), - appendTo: element, placement: "left", }); } diff --git a/app/assets/javascripts/discourse/app/components/user-info.hbs b/app/assets/javascripts/discourse/app/components/user-info.hbs index 812ccd20ce9..bca19c789a2 100644 --- a/app/assets/javascripts/discourse/app/components/user-info.hbs +++ b/app/assets/javascripts/discourse/app/components/user-info.hbs @@ -34,7 +34,6 @@ {{/if}} diff --git a/app/assets/javascripts/discourse/app/components/user-status-message.hbs b/app/assets/javascripts/discourse/app/components/user-status-message.hbs index 630c42a8bbe..92844d381bd 100644 --- a/app/assets/javascripts/discourse/app/components/user-status-message.hbs +++ b/app/assets/javascripts/discourse/app/components/user-status-message.hbs @@ -1,25 +1,26 @@ {{#if @status}} - - {{emoji @status.emoji skipTitle=true}} - {{#if @showDescription}} - + + <:trigger> + {{emoji @status.emoji skipTitle=true}} + {{#if @showDescription}} + + {{@status.description}} + + {{/if}} + + <:content> + {{emoji @status.emoji skipTitle=true}} + {{@status.description}} - {{/if}} - {{#if this.showTooltip}} - -
- {{emoji @status.emoji skipTitle=true}} - - {{@status.description}} - - {{#if this.until}} -
- {{this.until}} -
- {{/if}} + {{#if this.until}} +
+ {{this.until}}
- - {{/if}} - + {{/if}} + + {{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/user-status-message.js b/app/assets/javascripts/discourse/app/components/user-status-message.js index 18934c14907..6f857f562e5 100644 --- a/app/assets/javascripts/discourse/app/components/user-status-message.js +++ b/app/assets/javascripts/discourse/app/components/user-status-message.js @@ -1,21 +1,19 @@ -import Component from "@ember/component"; -import { computed } from "@ember/object"; +import Component from "@glimmer/component"; import { until } from "discourse/lib/formatter"; +import { inject as service } from "@ember/service"; export default class UserStatusMessage extends Component { - tagName = ""; - showTooltip = true; + @service currentUser; - @computed("status.ends_at") get until() { - if (!this.status.ends_at) { - return null; + if (!this.args.status.ends_at) { + return; } const timezone = this.currentUser ? this.currentUser.user_option?.timezone : moment.tz.guess(); - return until(this.status.ends_at, timezone, this.currentUser?.locale); + return until(this.args.status.ends_at, timezone, this.currentUser?.locale); } } diff --git a/app/assets/javascripts/discourse/app/components/user-tip-container.gjs b/app/assets/javascripts/discourse/app/components/user-tip-container.gjs new file mode 100644 index 00000000000..258f1b5a06d --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/user-tip-container.gjs @@ -0,0 +1,40 @@ +import { htmlSafe } from "@ember/template"; +import Component from "@glimmer/component"; +import DButton from "discourse/components/d-button"; +import { action } from "@ember/object"; + +export default class UserTipContainer extends Component { + + + get safeHtmlContent() { + return htmlSafe(this.args.data.contentHtml); + } + + @action + handleDismiss(_, event) { + event.preventDefault(); + this.args.close(); + this.args.data.onDismiss(); + } +} diff --git a/app/assets/javascripts/discourse/app/components/user-tip.js b/app/assets/javascripts/discourse/app/components/user-tip.js index fdf78b8e0cc..aabfebec11a 100644 --- a/app/assets/javascripts/discourse/app/components/user-tip.js +++ b/app/assets/javascripts/discourse/app/components/user-tip.js @@ -37,7 +37,6 @@ export default class UserTip extends Component { buttonIcon, reference: (selector && element.parentElement.querySelector(selector)) || element, - appendTo: element.parentElement, placement, onDismiss, }); diff --git a/app/assets/javascripts/discourse/app/helpers/noop.js b/app/assets/javascripts/discourse/app/helpers/noop.js new file mode 100644 index 00000000000..b09b41e3c6b --- /dev/null +++ b/app/assets/javascripts/discourse/app/helpers/noop.js @@ -0,0 +1,3 @@ +export default function noop() { + return () => {}; +} diff --git a/app/assets/javascripts/discourse/app/helpers/unique-id.js b/app/assets/javascripts/discourse/app/helpers/unique-id.js new file mode 100644 index 00000000000..a1b3cef924c --- /dev/null +++ b/app/assets/javascripts/discourse/app/helpers/unique-id.js @@ -0,0 +1,10 @@ +// https://github.com/emberjs/ember.js/blob/master/packages/@ember/-internals/glimmer/lib/helpers/unique-id.ts +export default function uniqueId() { + return ([3e7] + -1e3 + -4e3 + -2e3 + -1e11).replace( + /[0-3]/g, + (a) => + /* eslint-disable no-bitwise */ + ((a * 4) ^ ((Math.random() * 16) >> (a & 2))).toString(16) + /* eslint-enable no-bitwise */ + ); +} diff --git a/app/assets/javascripts/discourse/app/instance-initializers/d-popover.js b/app/assets/javascripts/discourse/app/instance-initializers/d-popover.js deleted file mode 100644 index 2d11e739323..00000000000 --- a/app/assets/javascripts/discourse/app/instance-initializers/d-popover.js +++ /dev/null @@ -1,17 +0,0 @@ -import { showPopover } from "discourse/lib/d-popover"; - -export default { - initialize() { - ["click", "mouseover"].forEach((eventType) => { - document.addEventListener(eventType, (e) => { - if (e.target.dataset.tooltip || e.target.dataset.popover) { - showPopover(e, { - interactive: false, - content: (reference) => - reference.dataset.tooltip || reference.dataset.popover, - }); - } - }); - }); - }, -}; diff --git a/app/assets/javascripts/discourse/app/lib/d-popover.js b/app/assets/javascripts/discourse/app/lib/d-popover.js index 42bea264b69..7a10d17ad14 100644 --- a/app/assets/javascripts/discourse/app/lib/d-popover.js +++ b/app/assets/javascripts/discourse/app/lib/d-popover.js @@ -1,74 +1,13 @@ -import tippy from "tippy.js"; -import { iconHTML } from "discourse-common/lib/icon-library"; +import deprecated from "discourse-common/lib/deprecated"; -export const hideOnEscapePlugin = { - name: "hideOnEscape", - - defaultValue: true, - - fn({ hide }) { - function onKeyDown(event) { - if (event.keyCode === 27) { - hide(); - } - } - - return { - onShow() { - document.addEventListener("keydown", onKeyDown); - }, - onHide() { - document.removeEventListener("keydown", onKeyDown); - }, - }; - }, -}; - -export function isPopoverShown(event) { - const instance = event.target._tippy; - return instance?.state.isShown; +export function showPopover() { + deprecated("`showPopover` is deprecated. Use tooltip service instead.", { + id: "show-popover", + }); } -// legacy, shouldn't be needed with setup -export function hidePopover(event) { - const instance = event.target._tippy; - - if (instance?.state.isShown) { - instance.hide(); - } -} - -// legacy, setup() should be used -export function showPopover(event, options = {}) { - const instance = event.target._tippy ?? setup(event.target, options); - - if (instance.state.isShown) { - instance.hide(); - } else { - instance.show(); - } -} - -// target is the element that triggers the display of the popover -// options accepts all tippy.js options as defined in their documentation -// https://atomiks.github.io/tippyjs/v6/all-props/ -export default function setup(target, options) { - const tippyOptions = Object.assign( - { - arrow: iconHTML("tippy-rounded-arrow"), - content: options.textContent || options.htmlContent, - allowHTML: options?.htmlContent?.length, - trigger: "mouseenter click", - hideOnClick: true, - zIndex: 1400, - plugins: [hideOnEscapePlugin], - touch: ["hold", 500], - }, - options - ); - - // legacy support delete tippyOptions.textContent; - delete tippyOptions.htmlContent; - - return tippy(target, tippyOptions); +export function hidePopover() { + deprecated("`hidePopover` is deprecated. Use tooltip service instead.", { + id: "hide-popover", + }); } diff --git a/app/assets/javascripts/discourse/app/lib/d-tooltip.js b/app/assets/javascripts/discourse/app/lib/d-tooltip.js deleted file mode 100644 index 5ef2a65022b..00000000000 --- a/app/assets/javascripts/discourse/app/lib/d-tooltip.js +++ /dev/null @@ -1,48 +0,0 @@ -import tippy from "tippy.js"; -import { bind } from "discourse-common/utils/decorators"; -import discourseDebounce from "discourse-common/lib/debounce"; - -export class DTooltip { - #tippyInstance; - - constructor(target, content) { - this.#tippyInstance = this.#initTippy(target, content); - if (this.#hasTouchCapabilities()) { - window.addEventListener("scroll", this.onScroll); - } - } - - destroy() { - if (this.#hasTouchCapabilities()) { - window.removeEventListener("scroll", this.onScroll); - } - this.#tippyInstance.destroy(); - } - - @bind - onScroll() { - discourseDebounce(() => this.#tippyInstance.hide(), 10); - } - - #initTippy(target, content) { - return tippy(target, { - interactive: false, - content, - trigger: this.#hasTouchCapabilities() ? "click" : "mouseenter", - theme: "d-tooltip", - arrow: false, - placement: "bottom-start", - onTrigger: this.#stopPropagation, - onUntrigger: this.#stopPropagation, - }); - } - - #hasTouchCapabilities() { - return navigator.maxTouchPoints > 1 || "ontouchstart" in window; - } - - #stopPropagation(instance, event) { - event.preventDefault(); - event.stopPropagation(); - } -} diff --git a/app/assets/javascripts/discourse/app/lib/discourse-location.js b/app/assets/javascripts/discourse/app/lib/discourse-location.js index 39a18a7fbd3..4d408100cf3 100644 --- a/app/assets/javascripts/discourse/app/lib/discourse-location.js +++ b/app/assets/javascripts/discourse/app/lib/discourse-location.js @@ -2,6 +2,7 @@ import EmberObject from "@ember/object"; import { defaultHomepage } from "discourse/lib/utilities"; import { guidFor } from "@ember/object/internals"; import { withoutPrefix } from "discourse-common/lib/get-url"; + let popstateFired = false; const supportsHistoryState = window.history && "state" in window.history; const popstateCallbacks = []; diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js index 76e076fcc36..d8724884755 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -134,7 +134,8 @@ import { addBeforeAuthCompleteCallback } from "discourse/instance-initializers/a // based on Semantic Versioning 2.0.0. Please update the changelog at // docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version // using the format described at https://keepachangelog.com/en/1.0.0/. -export const PLUGIN_API_VERSION = "1.11.0"; + +export const PLUGIN_API_VERSION = "1.12.0"; // This helper prevents us from applying the same `modifyClass` over and over in test mode. function canModify(klass, type, resolverName, changes) { @@ -640,6 +641,30 @@ class PluginApi { addButton(name, callback); } + /** + * Add a new button in the post admin menu. + * + * Example: + * + * ``` + * api.addPostAdminMenuButton((name, attrs) => { + * return { + * action: () => { + * alert('You clicked on the coffee button!'); + * }, + * icon: 'coffee', + * className: 'hot-coffee', + * title: 'coffee.title', + * }; + * }); + * ``` + **/ + addPostAdminMenuButton(name, callback) { + this.container + .lookup("service:admin-post-menu-buttons") + .addButton(name, callback); + } + /** * Remove existing button below a post with your plugin. * diff --git a/app/assets/javascripts/discourse/app/lib/update-user-status-on-mention.js b/app/assets/javascripts/discourse/app/lib/update-user-status-on-mention.js index a52afb06fb3..2a1e4c1f1b9 100644 --- a/app/assets/javascripts/discourse/app/lib/update-user-status-on-mention.js +++ b/app/assets/javascripts/discourse/app/lib/update-user-status-on-mention.js @@ -1,22 +1,24 @@ import { UserStatusMessage } from "discourse/lib/user-status-message"; +import { guidFor } from "@ember/object/internals"; -let userStatusMessages = []; +const userStatusMessages = {}; -export function updateUserStatusOnMention(mention, status) { +export function updateUserStatusOnMention(owner, mention, status) { removeStatus(mention); if (status) { - const userStatusMessage = new UserStatusMessage(status); - userStatusMessages.push(userStatusMessage); + const userStatusMessage = new UserStatusMessage(owner, status); + userStatusMessages[guidFor(mention)] = userStatusMessage; mention.appendChild(userStatusMessage.html); } } export function destroyUserStatusOnMentions() { - userStatusMessages.forEach((instance) => { + Object.values(userStatusMessages).forEach((instance) => { instance.destroy(); }); } function removeStatus(mention) { + userStatusMessages[guidFor(mention)]?.destroy(); mention.querySelector("span.user-status-message")?.remove(); } diff --git a/app/assets/javascripts/discourse/app/lib/user-status-message.js b/app/assets/javascripts/discourse/app/lib/user-status-message.js index 2148c036a39..a3ba98f36ce 100644 --- a/app/assets/javascripts/discourse/app/lib/user-status-message.js +++ b/app/assets/javascripts/discourse/app/lib/user-status-message.js @@ -1,19 +1,27 @@ -import { DTooltip } from "discourse/lib/d-tooltip"; import { emojiUnescape } from "discourse/lib/text"; import { escapeExpression } from "discourse/lib/utilities"; import { until } from "discourse/lib/formatter"; import User from "discourse/models/user"; +import { setOwner } from "@ember/application"; +import { inject as service } from "@ember/service"; export class UserStatusMessage { - #dTooltip; + @service tooltip; - constructor(status, opts) { + html = null; + content = null; + + constructor(owner, status, opts) { + setOwner(this, owner); this.html = this.#statusHtml(status, opts); - this.#dTooltip = new DTooltip(this.html, this.#tooltipHtml(status)); + this.content = this.#tooltipHtml(status); + this.tooltipInstance = this.tooltip.register(this.html, { + content: this.content, + }); } destroy() { - this.#dTooltip.destroy(); + this.tooltipInstance.destroy(); } #emojiHtml(emojiName) { diff --git a/app/assets/javascripts/discourse/app/lib/user-status-on-autocomplete.js b/app/assets/javascripts/discourse/app/lib/user-status-on-autocomplete.js index b416aa7ab3f..1facca9cbda 100644 --- a/app/assets/javascripts/discourse/app/lib/user-status-on-autocomplete.js +++ b/app/assets/javascripts/discourse/app/lib/user-status-on-autocomplete.js @@ -2,11 +2,11 @@ import { UserStatusMessage } from "discourse/lib/user-status-message"; let userStatusMessages = []; -export function initUserStatusHtml(users) { +export function initUserStatusHtml(owner, users) { (users || []).forEach((user, index) => { if (user.status) { user.index = index; - const userStatusMessage = new UserStatusMessage(user.status, { + const userStatusMessage = new UserStatusMessage(owner, user.status, { showDescription: true, }); user.statusHtml = userStatusMessage.html; diff --git a/app/assets/javascripts/discourse/app/lib/virtual-element-from-text-range.js b/app/assets/javascripts/discourse/app/lib/virtual-element-from-text-range.js index c2ada2c96b0..f2e499d2078 100644 --- a/app/assets/javascripts/discourse/app/lib/virtual-element-from-text-range.js +++ b/app/assets/javascripts/discourse/app/lib/virtual-element-from-text-range.js @@ -19,6 +19,10 @@ class VirtualElementFromTextRange { return this.rect; } + getClientRects() { + return this.range.getClientRects(); + } + get clientWidth() { return this.rect.width; } diff --git a/app/assets/javascripts/discourse/app/loader-shims.js b/app/assets/javascripts/discourse/app/loader-shims.js index 92cf12d4f97..de672af9b20 100644 --- a/app/assets/javascripts/discourse/app/loader-shims.js +++ b/app/assets/javascripts/discourse/app/loader-shims.js @@ -8,6 +8,7 @@ loaderShim("@ember-compat/tracked-built-ins", () => importSync("@ember-compat/tracked-built-ins") ); loaderShim("@popperjs/core", () => importSync("@popperjs/core")); +loaderShim("@floating-ui/dom", () => importSync("@floating-ui/dom")); loaderShim("@uppy/aws-s3", () => importSync("@uppy/aws-s3")); loaderShim("@uppy/aws-s3-multipart", () => importSync("@uppy/aws-s3-multipart") @@ -27,6 +28,5 @@ loaderShim("ember-modifier", () => importSync("ember-modifier")); loaderShim("handlebars", () => importSync("handlebars")); loaderShim("js-yaml", () => importSync("js-yaml")); loaderShim("message-bus-client", () => importSync("message-bus-client")); -loaderShim("tippy.js", () => importSync("tippy.js")); loaderShim("virtual-dom", () => importSync("virtual-dom")); loaderShim("xss", () => importSync("xss")); diff --git a/app/assets/javascripts/discourse/app/mixins/card-contents-base.js b/app/assets/javascripts/discourse/app/mixins/card-contents-base.js index 08c9ba26eee..218245c26fb 100644 --- a/app/assets/javascripts/discourse/app/mixins/card-contents-base.js +++ b/app/assets/javascripts/discourse/app/mixins/card-contents-base.js @@ -7,7 +7,6 @@ import { inject as service } from "@ember/service"; import { wantsNewWindow } from "discourse/lib/intercept-click"; import { bind } from "discourse-common/utils/decorators"; import discourseLater from "discourse-common/lib/later"; -import { createPopper } from "@popperjs/core"; import { headerOffset } from "discourse/lib/offset-calculator"; const DEFAULT_SELECTOR = "#main-outlet"; @@ -24,6 +23,7 @@ export function resetCardClickListenerSelector() { export default Mixin.create({ router: service(), + menu: service(), elementId: null, //click detection added for data-{elementId} triggeringLinkClass: null, //the classname where this card should appear @@ -39,7 +39,7 @@ export default Mixin.create({ post: null, isDocked: false, - _popperReference: null, + _menuInstance: null, _show(username, target, event) { // No user card for anon @@ -85,7 +85,6 @@ export default Mixin.create({ this.appEvents.trigger("user-card:show", { username }); this._showCallback(username, $(target)).then((user) => { this.appEvents.trigger("user-card:after-show", { user }); - this._positionCard($(target), event); }); // We bind scrolling on mobile after cards are shown to hide them if user scrolls @@ -189,57 +188,35 @@ export default Mixin.create({ return this._show($target.text().replace(/^@/, ""), $target); }, - _positionCard(target, event) { - this._popperReference?.destroy(); - - schedule("afterRender", () => { + _positionCard(target) { + schedule("afterRender", async () => { if (!target) { return; } + const avatarOverflowSize = 44; if (this.site.desktopView) { - const avatarOverflowSize = 44; - this._popperReference = createPopper(target[0], this.element, { - placement: "right", - modifiers: [ - { - name: "preventOverflow", - options: { - padding: { - top: headerOffset() + avatarOverflowSize, - right: 10, - bottom: 10, - left: 10, - }, - }, - }, - { name: "eventListeners", enabled: false }, - { name: "offset", options: { offset: [10, 10] } }, - ], + this._menuInstance = await this.menu.show(target[0], { + content: this.element, + autoUpdate: false, + identifier: "card", + padding: { + top: 10 + avatarOverflowSize + headerOffset(), + right: 10, + bottom: 10, + left: 10, + }, }); } else { - this._popperReference = createPopper(target[0], this.element, { - modifiers: [ - { name: "eventListeners", enabled: false }, - { - name: "computeStyles", - enabled: true, - fn({ state }) { - // mimics our modal top of the screen positioning - state.styles.popper = { - ...state.styles.popper, - position: "fixed", - left: `${ - (window.innerWidth - state.rects.popper.width) / 2 - }px`, - top: "10%", - transform: "translateY(-10%)", - }; - - return state; - }, - }, - ], + this._menuInstance = await this.menu.show(target[0], { + content: this.element, + strategy: "fixed", + identifier: "card", + computePosition: (content) => { + content.style.left = "10px"; + content.style.right = "10px"; + content.style.top = 10 + avatarOverflowSize + "px"; + }, }); } @@ -261,11 +238,12 @@ export default Mixin.create({ @bind _hide() { if (!this.visible) { - $(this.element).css({ left: -9999, top: -9999 }); if (this.site.mobileView) { $(".card-cloak").addClass("hidden"); } } + + this._menuInstance?.destroy(); }, _close() { diff --git a/app/assets/javascripts/discourse/app/modifiers/trap-tab.js b/app/assets/javascripts/discourse/app/modifiers/trap-tab.js new file mode 100644 index 00000000000..a7cd6b8c01e --- /dev/null +++ b/app/assets/javascripts/discourse/app/modifiers/trap-tab.js @@ -0,0 +1,73 @@ +import Modifier from "ember-modifier"; +import { registerDestructor } from "@ember/destroyable"; +import { bind } from "discourse-common/utils/decorators"; + +const FOCUSABLE_ELEMENTS = + 'details:not(.is-disabled) summary, [autofocus], a, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])'; + +export default class TrapTabModifier extends Modifier { + element = null; + + constructor(owner, args) { + super(owner, args); + registerDestructor(this, (instance) => instance.cleanup()); + } + + modify(element, [options]) { + this.preventScroll = options?.preventScroll ?? true; + this.orignalElement = element; + this.element = element.querySelector(".modal-inner-container") || element; + this.orignalElement.addEventListener("keydown", this.trapTab); + + // on first trap we don't allow to focus modal-close + // and apply manual focus only if we don't have any autofocus element + const autofocusedElement = this.element.querySelector("[autofocus]"); + + if (!autofocusedElement || document.activeElement !== autofocusedElement) { + // if there's not autofocus, or the activeElement, is not the autofocusable element + // attempt to focus the first of the focusable elements or just the modal-body + // to make it possible to scroll with arrow down/up + ( + autofocusedElement || + this.element.querySelector( + FOCUSABLE_ELEMENTS + ", button:not(.modal-close)" + ) || + this.element.querySelector(".modal-body") + )?.focus({ + preventScroll: this.preventScroll, + }); + } + } + + @bind + trapTab(event) { + if (event.key !== "Tab") { + return; + } + + const focusableElements = FOCUSABLE_ELEMENTS + ", button:enabled"; + const firstFocusableElement = this.element.querySelector(focusableElements); + const focusableContent = this.element.querySelectorAll(focusableElements); + + const lastFocusableElement = focusableContent[focusableContent.length - 1]; + + if (event.shiftKey) { + if (document.activeElement === firstFocusableElement) { + lastFocusableElement?.focus(); + event.preventDefault(); + } + } else { + if (document.activeElement === lastFocusableElement) { + event.preventDefault(); + + ( + this.element.querySelector(".modal-close") || firstFocusableElement + )?.focus({ preventScroll: this.preventScroll }); + } + } + } + + cleanup() { + this.orignalElement.removeEventListener("keydown", this.trapTab); + } +} diff --git a/app/assets/javascripts/discourse/app/services/admin-post-menu-buttons.js b/app/assets/javascripts/discourse/app/services/admin-post-menu-buttons.js new file mode 100644 index 00000000000..90c39c19135 --- /dev/null +++ b/app/assets/javascripts/discourse/app/services/admin-post-menu-buttons.js @@ -0,0 +1,10 @@ +import Service from "@ember/service"; +import { tracked } from "@glimmer/tracking"; + +export default class AdminPostMenuButtons extends Service { + @tracked callbacks = []; + + addButton(callback) { + this.callbacks.push(callback); + } +} diff --git a/app/assets/javascripts/discourse/app/services/user-tips.js b/app/assets/javascripts/discourse/app/services/user-tips.js index 9de6f7e8b8d..6ac5c5a5945 100644 --- a/app/assets/javascripts/discourse/app/services/user-tips.js +++ b/app/assets/javascripts/discourse/app/services/user-tips.js @@ -1,16 +1,20 @@ -import Service from "@ember/service"; +import { getOwner } from "discourse-common/lib/get-owner"; +import Service, { inject as service } from "@ember/service"; import { isTesting } from "discourse-common/config/environment"; import { iconHTML } from "discourse-common/lib/icon-library"; import I18n from "I18n"; import { escape } from "pretty-text/sanitizer"; -import tippy from "tippy.js"; import isElementInViewport from "discourse/lib/is-element-in-viewport"; import discourseLater from "discourse-common/lib/later"; import { cancel } from "@ember/runloop"; +import DTooltipInstance from "float-kit/lib/d-tooltip-instance"; +import UserTipContainer from "discourse/components/user-tip-container"; -const TIPPY_DELAY = 500; +const DELAY = 500; export default class UserTips extends Service { + @service tooltip; + #instances = new Map(); /** @@ -20,7 +24,6 @@ export default class UserTips extends Service { * @param {string} [options.buttonLabel] * @param {string} [options.buttonIcon] * @param {string} [options.placement] - * @param {Element} [options.appendTo] * @param {string} [options.content] * @param {string} [options.contentText] * @param {string} [options.titleText] @@ -51,42 +54,19 @@ export default class UserTips extends Service { this.#instances.set( options.id, - tippy(options.reference, { - hideOnClick: false, - trigger: "manual", - theme: "user-tip", - zIndex: "", // reset z-index to use inherited value from the parent - duration: TIPPY_DELAY, - - arrow: iconHTML("tippy-rounded-arrow"), + new DTooltipInstance(getOwner(this), options.reference, { + identifier: "user-tip", + interactive: true, + closeOnScroll: false, + closeOnClickOutside: false, placement: options.placement, - appendTo: options.appendTo, - - interactive: true, // for buttons in content - allowHTML: true, - - content: - options.content || - `
-
${escape(options.titleText)}
-
${ - options.contentHtml || escape(options.contentText) - }
-
- -
-
`, - - onCreate(tippyInstance) { - // Used to set correct z-index property on root tippy element - tippyInstance.popper.classList.add("user-tip"); - - tippyInstance.popper - .querySelector(".btn") - .addEventListener("click", (event) => { - options.onDismiss?.(); - event.preventDefault(); - }); + component: UserTipContainer, + data: { + titleText: escape(options.titleText), + contentHtml: options.contentHtml || null, + contentText: options.contentText ? escape(options.contentText) : null, + onDismiss: options.onDismiss, + buttonText, }, }) ); @@ -95,7 +75,7 @@ export default class UserTips extends Service { } hideTip(userTipId, force = false) { - // Tippy instances are not destroyed immediately because sometimes there + // Instances are not destroyed immediately because sometimes their // user tip is recreated immediately. This happens when Ember components // are re-rendered because a parent component has changed @@ -113,7 +93,7 @@ export default class UserTips extends Service { this.#destroyInstance(this.#instances.get(userTipId)); this.#instances.delete(userTipId); this.showNextTip(); - }, TIPPY_DELAY); + }, DELAY); } } @@ -127,7 +107,7 @@ export default class UserTips extends Service { showNextTip() { // Return early if a user tip is already visible and it is in viewport for (const tip of this.#instances.values()) { - if (tip.state.isVisible && isElementInViewport(tip.reference)) { + if (tip.expanded && isElementInViewport(tip.trigger)) { return; } } @@ -135,8 +115,9 @@ export default class UserTips extends Service { // Otherwise, try to find a user tip in the viewport let visibleTip; for (const tip of this.#instances.values()) { - if (isElementInViewport(tip.reference)) { + if (isElementInViewport(tip.trigger)) { visibleTip = tip; + break; } } @@ -177,20 +158,18 @@ export default class UserTips extends Service { #showInstance(instance) { if (isTesting()) { - instance.show(); + this.tooltip.show(instance); } else if (!instance.showTimer) { instance.showTimer = discourseLater(() => { instance.showTimer = null; - if (!instance.state.isDestroyed) { - instance.show(); - } - }, TIPPY_DELAY); + this.tooltip.show(instance); + }, DELAY); } } #hideInstance(instance) { cancel(instance.showTimer); instance.showTimer = null; - instance.hide(); + this.tooltip.close(instance); } } diff --git a/app/assets/javascripts/discourse/app/templates/application.hbs b/app/assets/javascripts/discourse/app/templates/application.hbs index 0a604ff204d..60ba0ca5faa 100644 --- a/app/assets/javascripts/discourse/app/templates/application.hbs +++ b/app/assets/javascripts/discourse/app/templates/application.hbs @@ -105,4 +105,8 @@ {{#if this.showFooterNav}} {{/if}} - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/templates/topic.hbs b/app/assets/javascripts/discourse/app/templates/topic.hbs index c63b1a8bd56..ff0f3a90501 100644 --- a/app/assets/javascripts/discourse/app/templates/topic.hbs +++ b/app/assets/javascripts/discourse/app/templates/topic.hbs @@ -593,7 +593,7 @@
{{/if}} - { - b.secondaryAction = "closeAdminMenu"; - contents.push(this.attach("post-admin-menu-button", b)); - } - ); - - return h("ul", contents); - }, - - clickOutside() { - this.sendWidgetAction("closeAdminMenu"); - }, -}); +// placeholder for now +export default createWidget("post-admin-menu", {}); diff --git a/app/assets/javascripts/discourse/app/widgets/post-cooked.js b/app/assets/javascripts/discourse/app/widgets/post-cooked.js index 1bf2993d4df..d2433493f0c 100644 --- a/app/assets/javascripts/discourse/app/widgets/post-cooked.js +++ b/app/assets/javascripts/discourse/app/widgets/post-cooked.js @@ -13,6 +13,7 @@ import { destroyUserStatusOnMentions, updateUserStatusOnMention, } from "discourse/lib/update-user-status-on-mention"; +import { getOwner } from "discourse-common/lib/get-owner"; let _beforeAdoptDecorators = []; let _afterAdoptDecorators = []; @@ -396,7 +397,7 @@ export default class PostCooked { const mentions = postElement.querySelectorAll(`a.mention[href="${href}"]`); mentions.forEach((mention) => { - updateUserStatusOnMention(mention, user.status); + updateUserStatusOnMention(getOwner(this._post()), mention, user.status); }); } diff --git a/app/assets/javascripts/discourse/app/widgets/post-menu.js b/app/assets/javascripts/discourse/app/widgets/post-menu.js index 97570e78d16..21671d93ae9 100644 --- a/app/assets/javascripts/discourse/app/widgets/post-menu.js +++ b/app/assets/javascripts/discourse/app/widgets/post-menu.js @@ -12,6 +12,9 @@ import { } from "discourse/models/bookmark"; import { isTesting } from "discourse-common/config/environment"; import DeleteTopicDisallowedModal from "discourse/components/modal/delete-topic-disallowed"; +import RenderGlimmer from "discourse/widgets/render-glimmer"; +import { hbs } from "ember-cli-htmlbars"; +import AdminPostMenu from "discourse/components/admin-post-menu"; const LIKE_ACTION = 2; const VIBRATE_DURATION = 5; @@ -403,11 +406,13 @@ registerButton("admin", (attrs) => { if (!attrs.canManage && !attrs.canWiki && !attrs.canEditStaffNotes) { return; } + return { action: "openAdminMenu", title: "post.controls.admin", className: "show-post-admin-menu", icon: "wrench", + sendActionEvent: true, }; }); @@ -464,7 +469,7 @@ function _replaceButton(buttons, find, replace) { export default createWidget("post-menu", { tagName: "section.post-menu-area.clearfix", - services: ["modal"], + services: ["modal", "menu"], settings: { collapseButtons: true, @@ -477,27 +482,67 @@ export default createWidget("post-menu", { collapsed: true, likedUsers: [], readers: [], - adminVisible: false, }; }, buildKey: (attrs) => `post-menu-${attrs.id}`, attachButton(name) { - let buttonAtts = buildButton(name, this); + let buttonAttrs = buildButton(name, this); + + if (buttonAttrs?.component) { + return [ + new RenderGlimmer( + this, + buttonAttrs.tagName, + hbs`<@data.component + @permanentlyDeletePost={{@data.permanentlyDeletePost}} + @lockPost={{@data.lockPost}} + @unlockPost={{@data.unlockPost}} + @grantBadge={{@data.grantBadge}} + @rebakePost={{@data.rebakePost}} + @toggleWiki={{@data.toggleWiki}} + @changePostOwner={{@data.changePostOwner}} + @changeNotice={{@data.changeNotice}} + @togglePostType={{@data.togglePostType}} + @unhidePost={{@data.unhidePost}} + @showPagePublish={{@data.showPagePublish}} + @post={{@data.post}} + @transformedPost={{@data.transformedPost}} + @scheduleRerender={{@data.scheduleRerender}} + />`, + { + component: buttonAttrs.component, + transformedPost: this.attrs, + post: this.findAncestorModel(), + permanentlyDeletePost: () => + this.sendWidgetAction("permanentlyDeletePost"), + lockPost: () => this.sendWidgetAction("lockPost"), + unlockPost: () => this.sendWidgetAction("unlockPost"), + grantBadge: () => this.sendWidgetAction("grantBadge"), + rebakePost: () => this.sendWidgetAction("rebakePost"), + toggleWiki: () => this.sendWidgetAction("toggleWiki"), + changePostOwner: () => this.sendWidgetAction("changePostOwner"), + changeNotice: () => this.sendWidgetAction("changeNotice"), + togglePostType: () => this.sendWidgetAction("togglePostType"), + scheduleRerender: () => this.scheduleRerender(), + } + ), + ]; + } // If the button is replaced via the plugin API, we need to render the // replacement rather than a button - if (buttonAtts?.replaced) { - return this.attach(buttonAtts.name, buttonAtts.attrs); + if (buttonAttrs?.replaced) { + return this.attach(buttonAttrs.name, buttonAttrs.attrs); } - if (buttonAtts) { - let button = this.attach(this.settings.buttonType, buttonAtts); - if (buttonAtts.before) { - let before = this.attachButton(buttonAtts.before); + if (buttonAttrs) { + let button = this.attach(this.settings.buttonType, buttonAttrs); + if (buttonAttrs.before) { + let before = this.attachButton(buttonAttrs.before); return h("div.double-button", [before, button]); - } else if (buttonAtts.addContainer) { + } else if (buttonAttrs.addContainer) { return h("div.double-button", [button]); } @@ -590,18 +635,18 @@ export default createWidget("post-menu", { } if (shouldAddButton && builder) { - const buttonAtts = builder( + const buttonAttrs = builder( attrs, this.state, this.siteSettings, this.settings, this.currentUser ); - if (buttonAtts) { - const { position, beforeButton, afterButton } = buttonAtts; - delete buttonAtts.position; + if (buttonAttrs) { + const { position, beforeButton, afterButton } = buttonAttrs; + delete buttonAttrs.position; - let button = this.attach(this.settings.buttonType, buttonAtts); + let button = this.attach(this.settings.buttonType, buttonAttrs); const content = []; if (beforeButton) { @@ -666,9 +711,6 @@ export default createWidget("post-menu", { ]; postControls.push(h("div.actions", controlsButtons)); - if (state.adminVisible) { - postControls.push(this.attach("post-admin-menu", attrs)); - } const contents = [ h( @@ -728,12 +770,28 @@ export default createWidget("post-menu", { return contents; }, - openAdminMenu() { - this.state.adminVisible = true; - }, - - closeAdminMenu() { - this.state.adminVisible = false; + openAdminMenu(event) { + this.menu.show(event.target, { + identifier: "admin-post-menu", + component: AdminPostMenu, + data: { + scheduleRerender: this.scheduleRerender.bind(this), + transformedPost: this.attrs, + post: this.findAncestorModel(), + permanentlyDeletePost: () => + this.sendWidgetAction("permanentlyDeletePost"), + lockPost: () => this.sendWidgetAction("lockPost"), + unlockPost: () => this.sendWidgetAction("unlockPost"), + grantBadge: () => this.sendWidgetAction("grantBadge"), + rebakePost: () => this.sendWidgetAction("rebakePost"), + toggleWiki: () => this.sendWidgetAction("toggleWiki"), + changePostOwner: () => this.sendWidgetAction("changePostOwner"), + changeNotice: () => this.sendWidgetAction("changeNotice"), + togglePostType: () => this.sendWidgetAction("togglePostType"), + unhidePost: () => this.sendWidgetAction("unhidePost"), + showPagePublish: () => this.sendWidgetAction("showPagePublish"), + }, + }); }, showDeleteTopicModal() { diff --git a/app/assets/javascripts/discourse/app/widgets/post.js b/app/assets/javascripts/discourse/app/widgets/post.js index 446f1c0d6e6..1ca5f3a4f74 100644 --- a/app/assets/javascripts/discourse/app/widgets/post.js +++ b/app/assets/javascripts/discourse/app/widgets/post.js @@ -991,13 +991,9 @@ export default createWidget("post", { this.currentUser.showUserTip({ id: "post_menu", - titleText: I18n.t("user_tips.post_menu.title"), contentText: I18n.t("user_tips.post_menu.content"), - reference, - appendTo: reference?.closest(".post-controls"), - placement: "top", }); }, diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json index d801e695d8a..3fdc1255e25 100644 --- a/app/assets/javascripts/discourse/package.json +++ b/app/assets/javascripts/discourse/package.json @@ -40,6 +40,7 @@ "@embroider/core": "^3.2.1", "@embroider/macros": "^1.13.1", "@embroider/webpack": "^3.1.5", + "@floating-ui/dom": "^1.5.0", "@glimmer/component": "^1.1.2", "@glimmer/tracking": "^1.1.2", "@popperjs/core": "^2.11.8", @@ -98,10 +99,10 @@ "qunit-dom": "^2.0.0", "sass": "^1.66.1", "select-kit": "1.0.0", + "float-kit": "1.0.0", "sinon": "^15.2.0", "source-map": "^0.7.4", "terser": "^5.19.4", - "tippy.js": "^6.3.7", "truth-helpers": "1.0.0", "util": "^0.12.5", "virtual-dom": "^2.1.1", diff --git a/app/assets/javascripts/discourse/tests/acceptance/page-publishing-test.js b/app/assets/javascripts/discourse/tests/acceptance/page-publishing-test.js index cc77008f4f8..c1c5dec3487 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/page-publishing-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/page-publishing-test.js @@ -28,7 +28,7 @@ acceptance("Page Publishing", function (needs) { await visit("/t/internationalization-localization/280"); await click(".topic-post:nth-of-type(1) button.show-more-actions"); await click(".topic-post:nth-of-type(1) button.show-post-admin-menu"); - await click(".topic-post:nth-of-type(1) .publish-page"); + await click(".publish-page"); await fillIn(".publish-slug", "bad-slug"); assert.ok(!exists(".valid-slug")); diff --git a/app/assets/javascripts/discourse/tests/acceptance/post-inline-mentions-test.js b/app/assets/javascripts/discourse/tests/acceptance/post-inline-mentions-test.js index fb854f4cee5..d2de555fbe9 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/post-inline-mentions-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/post-inline-mentions-test.js @@ -162,8 +162,8 @@ acceptance("Post inline mentions – user status tooltip", function (needs) { ends_at: null, }; - async function mouseEnter(selector) { - await triggerEvent(query(selector), "mouseenter"); + async function mouseMove(selector) { + await triggerEvent(selector, "mousemove"); } test("shows user status tooltip", async function (assert) { @@ -177,7 +177,7 @@ acceptance("Post inline mentions – user status tooltip", function (needs) { "user status is shown" ); - await mouseEnter(".user-status-message"); + await mouseMove(".user-status-message"); const statusTooltip = document.querySelector( ".user-status-message-tooltip" ); diff --git a/app/assets/javascripts/discourse/tests/integration/components/d-popover-test.js b/app/assets/javascripts/discourse/tests/integration/components/d-popover-test.js deleted file mode 100644 index 804a24bdcf2..00000000000 --- a/app/assets/javascripts/discourse/tests/integration/components/d-popover-test.js +++ /dev/null @@ -1,128 +0,0 @@ -import { module, test } from "qunit"; -import { setupRenderingTest } from "discourse/tests/helpers/component-test"; -import { click, render, triggerKeyEvent } from "@ember/test-helpers"; -import { exists, query } from "discourse/tests/helpers/qunit-helpers"; -import { hbs } from "ember-cli-htmlbars"; -import { - hidePopover, - isPopoverShown, - showPopover, -} from "discourse/lib/d-popover"; - -module("Integration | Component | d-popover", function (hooks) { - setupRenderingTest(hooks); - - test("show/hide popover from lib", async function (assert) { - let showCallCount = 0; - let hideCallCount = 0; - - this.set("onButtonClick", (_, event) => { - if (isPopoverShown(event)) { - hidePopover(event); - hideCallCount++; - } else { - // Note: we need to override the default `trigger` and `hideOnClick` - // settings in order to completely control showing / hiding the tip - // via showPopover / hidePopover. Otherwise tippy's event listeners - // will compete with those created in this test (on DButton). - showPopover(event, { - content: "test", - duration: 0, - trigger: "manual", - hideOnClick: false, - }); - showCallCount++; - } - }); - - await render(hbs` - - `); - - assert.notOk(document.querySelector("div[data-tippy-root]")); - - await click(".btn"); - assert.strictEqual( - document.querySelector("div[data-tippy-root]").innerText.trim(), - "test" - ); - - await click(".btn"); - - assert.notOk(document.querySelector("div[data-tippy-root]")); - - assert.strictEqual(showCallCount, 1, "showPopover was invoked once"); - assert.strictEqual(hideCallCount, 1, "hidePopover was invoked once"); - }); - - test("show/hide popover from component", async function (assert) { - await render(hbs` - - -
    -
  • foo
  • -
  • -
-
- `); - - assert.notOk(exists(".d-popover.is-expanded")); - assert.notOk(exists(".test")); - - await click(".trigger"); - - assert.ok(exists(".d-popover.is-expanded")); - assert.strictEqual(query(".test").innerText.trim(), "foo"); - - await click(".closer"); - assert.notOk(exists(".d-popover.is-expanded")); - }); - - test("using options with component", async function (assert) { - await render(hbs` - - - - `); - - await click(".btn"); - assert.strictEqual(query(".tippy-content").innerText.trim(), "bar"); - }); - - test("d-popover component accepts a block", async function (assert) { - await render(hbs` - - - - `); - - assert.ok(exists(".d-icon-chevron-down")); - - await click(".btn"); - assert.ok(exists(".d-icon-chevron-up")); - }); - - test("d-popover component accepts a class property", async function (assert) { - await render(hbs``); - - assert.ok(exists(".d-popover.foo")); - }); - - test("d-popover component closes on escape key", async function (assert) { - await render(hbs` - - - - `); - - await click(".btn"); - assert.ok(exists(".d-popover.is-expanded")); - - await triggerKeyEvent(document, "keydown", "Escape"); - assert.notOk(exists(".d-popover.is-expanded")); - }); -}); diff --git a/app/assets/javascripts/discourse/tests/integration/components/d-tooltip-test.js b/app/assets/javascripts/discourse/tests/integration/components/d-tooltip-test.js deleted file mode 100644 index d775130913a..00000000000 --- a/app/assets/javascripts/discourse/tests/integration/components/d-tooltip-test.js +++ /dev/null @@ -1,47 +0,0 @@ -import { module, test } from "qunit"; -import { render, triggerEvent } from "@ember/test-helpers"; -import { setupRenderingTest } from "discourse/tests/helpers/component-test"; -import { hbs } from "ember-cli-htmlbars"; -import { query } from "discourse/tests/helpers/qunit-helpers"; - -async function mouseenter() { - await triggerEvent(query("button"), "mouseenter"); -} - -module("Integration | Component | d-tooltip", function (hooks) { - setupRenderingTest(hooks); - - test("doesn't show tooltip if it wasn't expanded", async function (assert) { - await render(hbs` - - `); - assert.notOk(document.querySelector("[data-tippy-root]")); - }); - - test("it shows tooltip on mouseenter", async function (assert) { - await render(hbs` - - `); - - await mouseenter(); - assert.ok( - document.querySelector("[data-tippy-root]"), - "the tooltip is added to the page" - ); - assert.equal( - document - .querySelector("[data-tippy-root] .tippy-content") - .textContent.trim(), - "Tooltip text", - "the tooltip content is correct" - ); - }); -}); diff --git a/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-button-tooltip-test.js b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-button-tooltip-test.js new file mode 100644 index 00000000000..2fee66c0d29 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-button-tooltip-test.js @@ -0,0 +1,26 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import { render } from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; + +module( + "Integration | Component | FloatKit | d-button-tooltip", + function (hooks) { + setupRenderingTest(hooks); + + test("default", async function (assert) { + await render(hbs` + + <:button> + + + <:tooltip> + + + `); + + assert.dom(".btn").exists(); + assert.dom("[data-trigger]").exists(); + }); + } +); diff --git a/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-default-toast-test.js b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-default-toast-test.js new file mode 100644 index 00000000000..2c79af679ed --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-default-toast-test.js @@ -0,0 +1,86 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import { render } from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; +import DToastInstance from "float-kit/lib/d-toast-instance"; + +module( + "Integration | Component | FloatKit | d-default-toast", + function (hooks) { + setupRenderingTest(hooks); + + test("icon", async function (assert) { + this.toast = new DToastInstance(this, { data: { icon: "check" } }); + + await render(hbs``); + + assert.dom(".fk-d-default-toast__icon-container .d-icon-check").exists(); + }); + + test("no icon", async function (assert) { + this.toast = new DToastInstance(this, {}); + + await render(hbs``); + + assert.dom(".fk-d-default-toast__icon-container").doesNotExist(); + }); + + test("title", async function (assert) { + this.toast = new DToastInstance(this, { data: { title: "Title" } }); + + await render(hbs``); + + assert + .dom(".fk-d-default-toast__title") + .hasText(this.toast.options.data.title); + }); + + test("no title", async function (assert) { + this.toast = new DToastInstance(this, {}); + + await render(hbs``); + + assert.dom(".fk-d-default-toast__title").doesNotExist(); + }); + + test("message", async function (assert) { + this.toast = new DToastInstance(this, { data: { message: "Message" } }); + + await render(hbs``); + + assert + .dom(".fk-d-default-toast__message") + .hasText(this.toast.options.data.message); + }); + + test("no message", async function (assert) { + this.toast = new DToastInstance(this, {}); + + await render(hbs``); + + assert.dom(".fk-d-default-toast__message").doesNotExist(); + }); + + test("actions", async function (assert) { + this.toast = new DToastInstance(this, { + data: { + actions: [ + { + label: "cancel", + icon: "times", + class: "btn-danger", + action: () => {}, + }, + ], + }, + }); + + await render(hbs``); + + assert + .dom(".fk-d-default-toast__actions .btn.btn-danger") + .exists() + .hasText("cancel"); + }); + } +); diff --git a/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js new file mode 100644 index 00000000000..af526d3377d --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js @@ -0,0 +1,186 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import { + click, + find, + render, + triggerEvent, + triggerKeyEvent, +} from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; +import DDefaultToast from "float-kit/components/d-default-toast"; + +module("Integration | Component | FloatKit | d-menu", function (hooks) { + setupRenderingTest(hooks); + + async function open() { + await triggerEvent(".fk-d-menu__trigger", "click"); + } + + test("@label", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-menu__trigger").containsText("label"); + }); + + test("@icon", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-menu__trigger .d-icon-check").exists(); + }); + + test("@content", async function (assert) { + await render( + hbs`` + ); + await open(); + + assert.dom(".fk-d-menu").hasText("content"); + }); + + test("-expanded class", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-menu__trigger").doesNotHaveClass("-expanded"); + + await open(); + + assert.dom(".fk-d-menu__trigger").hasClass("-expanded"); + }); + + test("trigger id attribute", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-menu__trigger").hasAttribute("id"); + }); + + test("@identifier", async function (assert) { + await render( + hbs`` + ); + + assert.dom(".fk-d-menu__trigger").hasAttribute("data-identifier", "tip"); + + await open(); + + assert.dom(".fk-d-menu").hasAttribute("data-identifier", "tip"); + }); + + test("aria-expanded attribute", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-menu__trigger").hasAttribute("aria-expanded", "false"); + + await open(); + + assert.dom(".fk-d-menu__trigger").hasAttribute("aria-expanded", "true"); + }); + + test("<:trigger>", async function (assert) { + await render( + hbs`<:trigger>label` + ); + + assert.dom(".fk-d-menu__trigger").containsText("label"); + }); + + test("<:content>", async function (assert) { + await render( + hbs`<:content>content` + ); + + await open(); + + assert.dom(".fk-d-menu").containsText("content"); + }); + + test("content role attribute", async function (assert) { + await render(hbs``); + + await open(); + + assert.dom(".fk-d-menu").hasAttribute("role", "dialog"); + }); + + test("@component", async function (assert) { + this.component = DDefaultToast; + + await render( + hbs`` + ); + + await open(); + + assert.dom(".fk-d-menu").containsText("content"); + + await click(".fk-d-menu .btn"); + + assert.dom(".fk-d-menu").doesNotExist(); + }); + + test("content aria-labelledby attribute", async function (assert) { + await render(hbs``); + + await open(); + + assert.strictEqual( + document.querySelector(".fk-d-menu__trigger").id, + document.querySelector(".fk-d-menu").getAttribute("aria-labelledby") + ); + }); + + test("@closeOnEscape", async function (assert) { + await render( + hbs`` + ); + await open(); + await triggerKeyEvent(document.activeElement, "keydown", "Escape"); + + assert.dom(".fk-d-menu").doesNotExist(); + + await render( + hbs`` + ); + await open(); + await triggerKeyEvent(document.activeElement, "keydown", "Escape"); + + assert.dom(".fk-d-menu").exists(); + }); + + test("@closeOnClickOutside", async function (assert) { + await render( + hbs`test` + ); + await open(); + await click(".test"); + + assert.dom(".fk-d-menu").doesNotExist(); + + await render( + hbs`test` + ); + await open(); + await click(".test"); + + assert.dom(".fk-d-menu").exists(); + }); + + test("@maxWidth", async function (assert) { + await render( + hbs`` + ); + await open(); + + assert.ok( + find(".fk-d-menu").getAttribute("style").includes("max-width: 20px;") + ); + }); + + test("applies position", async function (assert) { + await render(hbs``); + await open(); + + assert.dom(".fk-d-menu").hasAttribute("style", /left: /); + assert.ok(find(".fk-d-menu").getAttribute("style").includes("top: ")); + }); +}); diff --git a/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-tooltip-test.js b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-tooltip-test.js new file mode 100644 index 00000000000..1aa9241e8f9 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-tooltip-test.js @@ -0,0 +1,192 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import { + click, + find, + render, + triggerEvent, + triggerKeyEvent, +} from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; +import DDefaultToast from "float-kit/components/d-default-toast"; + +module("Integration | Component | FloatKit | d-tooltip", function (hooks) { + setupRenderingTest(hooks); + + async function hover() { + await triggerEvent(".fk-d-tooltip__trigger", "mousemove"); + } + + test("@label", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-tooltip__label").hasText("label"); + }); + + test("@icon", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-tooltip__icon .d-icon-check").exists(); + }); + + test("@content", async function (assert) { + await render( + hbs`` + ); + await hover(); + + assert.dom(".fk-d-tooltip").hasText("content"); + }); + + test("-expanded class", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-tooltip__trigger").doesNotHaveClass("-expanded"); + + await hover(); + + assert.dom(".fk-d-tooltip__trigger").hasClass("-expanded"); + }); + + test("trigger role attribute", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-tooltip__trigger").hasAttribute("role", "button"); + }); + + test("trigger id attribute", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-tooltip__trigger").hasAttribute("id"); + }); + + test("@identifier", async function (assert) { + await render( + hbs`` + ); + + assert.dom(".fk-d-tooltip__trigger").hasAttribute("data-identifier", "tip"); + + await hover(); + + assert.dom(".fk-d-tooltip").hasAttribute("data-identifier", "tip"); + }); + + test("aria-expanded attribute", async function (assert) { + await render(hbs``); + + assert.dom(".fk-d-tooltip__trigger").hasAttribute("aria-expanded", "false"); + + await hover(); + + assert.dom(".fk-d-tooltip__trigger").hasAttribute("aria-expanded", "true"); + }); + + test("<:trigger>", async function (assert) { + await render( + hbs`<:trigger>label` + ); + + assert.dom(".fk-d-tooltip__trigger").hasText("label"); + }); + + test("<:content>", async function (assert) { + await render( + hbs`<:content>content` + ); + + await hover(); + + assert.dom(".fk-d-tooltip").hasText("content"); + }); + + test("content role attribute", async function (assert) { + await render(hbs``); + + await hover(); + + assert.dom(".fk-d-tooltip").hasAttribute("role", "tooltip"); + }); + + test("@component", async function (assert) { + this.component = DDefaultToast; + + await render( + hbs`` + ); + + await hover(); + + assert.dom(".fk-d-tooltip").containsText("content"); + + await click(".fk-d-tooltip .btn"); + + assert.dom(".fk-d-tooltip").doesNotExist(); + }); + + test("content aria-labelledby attribute", async function (assert) { + await render(hbs``); + + await hover(); + + assert.strictEqual( + document.querySelector(".fk-d-tooltip__trigger").id, + document.querySelector(".fk-d-tooltip").getAttribute("aria-labelledby") + ); + }); + + test("@closeOnEscape", async function (assert) { + await render( + hbs`` + ); + await hover(); + await triggerKeyEvent(document.activeElement, "keydown", "Escape"); + + assert.dom(".fk-d-tooltip").doesNotExist(); + + await render( + hbs`` + ); + await hover(); + await triggerKeyEvent(document.activeElement, "keydown", "Escape"); + + assert.dom(".fk-d-tooltip").exists(); + }); + + test("@closeOnClickOutside", async function (assert) { + await render( + hbs`test` + ); + await hover(); + await click(".test"); + + assert.dom(".fk-d-tooltip").doesNotExist(); + + await render( + hbs`test` + ); + await hover(); + await click(".test"); + + assert.dom(".fk-d-tooltip").exists(); + }); + + test("@maxWidth", async function (assert) { + await render( + hbs`` + ); + await hover(); + + assert.ok( + find(".fk-d-tooltip").getAttribute("style").includes("max-width: 20px;") + ); + }); + + test("applies position", async function (assert) { + await render(hbs``); + await hover(); + + assert.ok(find(".fk-d-tooltip").getAttribute("style").includes("left: ")); + assert.ok(find(".fk-d-tooltip").getAttribute("style").includes("top: ")); + }); +}); diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js index 57092a42b57..f44601c14bd 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js @@ -118,31 +118,17 @@ module("Integration | Component | user-info", function (hooks) { .exists(); }); - test("doesn't show status tooltip by default", async function (assert) { - this.currentUser.name = "Evil Trout"; - this.currentUser.status = { emoji: "tooth", description: "off to dentist" }; - - await render( - hbs`` - ); - await triggerEvent(query(".user-status-message"), "mouseenter"); - - assert.notOk( - document.querySelector("[data-tippy-root] .user-status-message-tooltip") - ); - }); - test("shows status tooltip if enabled", async function (assert) { this.currentUser.name = "Evil Trout"; this.currentUser.status = { emoji: "tooth", description: "off to dentist" }; await render( - hbs`` + hbs`` ); - await triggerEvent(query(".user-status-message"), "mouseenter"); + await triggerEvent(query(".user-status-message"), "mousemove"); - assert.ok( - document.querySelector("[data-tippy-root] .user-status-message-tooltip") - ); + assert + .dom("[data-content][data-identifier='user-status-message-tooltip']") + .exists(); }); }); diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-status-message-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-status-message-test.js index 9cf52c8fcd0..aa8b965aaf9 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/user-status-message-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/user-status-message-test.js @@ -5,7 +5,7 @@ import { hbs } from "ember-cli-htmlbars"; import { exists, fakeTime, query } from "discourse/tests/helpers/qunit-helpers"; async function mouseenter() { - await triggerEvent(query(".user-status-message"), "mouseenter"); + await triggerEvent(query(".user-status-message"), "mousemove"); } module("Integration | Component | user-status-message", function (hooks) { @@ -27,11 +27,6 @@ module("Integration | Component | user-status-message", function (hooks) { assert.ok(exists("img.emoji[alt='tooth']"), "the status emoji is shown"); }); - test("it doesn't render status description by default", async function (assert) { - await render(hbs``); - assert.notOk(exists(".user-status-message-description")); - }); - test("it renders status description if enabled", async function (assert) { await render(hbs` `); - assert.equal( - query(".user-status-message-description").innerText.trim(), - "off to dentist" - ); + assert + .dom('[data-trigger][data-identifier="user-status-message-tooltip"]') + .containsText("off to dentist"); }); test("it shows the until TIME on the tooltip if status will expire today", async function (assert) { @@ -53,15 +47,14 @@ module("Integration | Component | user-status-message", function (hooks) { ); this.status.ends_at = "2100-02-01T12:30:00.000Z"; - await render(hbs``); - - await mouseenter(); - assert.equal( - document - .querySelector("[data-tippy-root] .user-status-tooltip-until") - .textContent.trim(), - "Until: 12:30 PM" + await render( + hbs`` ); + await mouseenter(); + + assert + .dom('[data-content][data-identifier="user-status-message-tooltip"]') + .containsText("Until: 12:30 PM"); }); test("it shows the until DATE on the tooltip if status will expire tomorrow", async function (assert) { @@ -72,15 +65,14 @@ module("Integration | Component | user-status-message", function (hooks) { ); this.status.ends_at = "2100-02-02T12:30:00.000Z"; - await render(hbs``); - - await mouseenter(); - assert.equal( - document - .querySelector("[data-tippy-root] .user-status-tooltip-until") - .textContent.trim(), - "Until: Feb 2" + await render( + hbs`` ); + await mouseenter(); + + assert + .dom('[data-content][data-identifier="user-status-message-tooltip"]') + .containsText("Until: Feb 2"); }); test("it doesn't show until datetime on the tooltip if status doesn't have expiration date", async function (assert) { @@ -91,32 +83,27 @@ module("Integration | Component | user-status-message", function (hooks) { ); this.status.ends_at = null; - await render(hbs``); - - await mouseenter(); - assert.notOk( - document.querySelector("[data-tippy-root] .user-status-tooltip-until") + await render( + hbs`` ); + await mouseenter(); + + assert + .dom( + '[data-content][data-identifier="user-status-message-tooltip"] .user-status-tooltip-until' + ) + .doesNotExist(); }); test("it shows tooltip by default", async function (assert) { - await render(hbs``); - await mouseenter(); - - assert.ok( - document.querySelector("[data-tippy-root] .user-status-message-tooltip") - ); - }); - - test("it doesn't show tooltip if disabled", async function (assert) { await render( - hbs`` + hbs`` ); await mouseenter(); - assert.notOk( - document.querySelector("[data-tippy-root] .user-status-message-tooltip") - ); + assert + .dom('[data-content][data-identifier="user-status-message-tooltip"]') + .exists(); }); test("doesn't blow up with an anonymous user", async function (assert) { @@ -125,7 +112,9 @@ module("Integration | Component | user-status-message", function (hooks) { await render(hbs``); - assert.dom(".user-status-message").exists(); + assert + .dom('[data-trigger][data-identifier="user-status-message-tooltip"]') + .exists(); }); test("accepts a custom css class", async function (assert) { @@ -135,6 +124,8 @@ module("Integration | Component | user-status-message", function (hooks) { hbs`` ); - assert.dom(".user-status-message.foo").exists(); + assert + .dom('[data-trigger][data-identifier="user-status-message-tooltip"].foo') + .exists(); }); }); diff --git a/app/assets/javascripts/discourse/tests/integration/components/widgets/post-test.js b/app/assets/javascripts/discourse/tests/integration/components/widgets/post-test.js index 3ad97ae96c9..d9bea1d80ea 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/widgets/post-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/widgets/post-test.js @@ -564,13 +564,21 @@ module("Integration | Component | Widget | post", function (hooks) { test("show admin menu", async function (assert) { this.set("args", { canManage: true }); - await render(hbs``); + await render( + hbs`` + ); - assert.ok(!exists(".post-admin-menu")); + assert + .dom("[data-content][data-identifier='admin-post-menu']") + .doesNotExist(); await click(".post-menu-area .show-post-admin-menu"); - assert.strictEqual(count(".post-admin-menu"), 1, "it shows the popup"); + + assert.dom("[data-content][data-identifier='admin-post-menu']").exists(); + await click(".post-menu-area"); - assert.ok(!exists(".post-admin-menu"), "clicking outside clears the popup"); + assert + .dom("[data-content][data-identifier='admin-post-menu']") + .doesNotExist("clicking outside clears the popup"); }); test("permanently delete topic", async function (assert) { @@ -578,13 +586,17 @@ module("Integration | Component | Widget | post", function (hooks) { this.set("permanentlyDeletePost", () => (this.deleted = true)); await render( - hbs`` + hbs`` ); await click(".post-menu-area .show-post-admin-menu"); - await click(".post-admin-menu .permanently-delete"); + await click( + "[data-content][data-identifier='admin-post-menu'] .permanently-delete" + ); assert.ok(this.deleted); - assert.ok(!exists(".post-admin-menu"), "also hides the menu"); + assert + .dom("[data-content][data-identifier='admin-post-menu']") + .doesNotExist("also hides the menu"); }); test("permanently delete post", async function (assert) { @@ -593,12 +605,18 @@ module("Integration | Component | Widget | post", function (hooks) { await render(hbs` + `); await click(".post-menu-area .show-post-admin-menu"); - await click(".post-admin-menu .permanently-delete"); + + await click( + "[data-content][data-identifier='admin-post-menu'] .permanently-delete" + ); assert.ok(this.deleted); - assert.ok(!exists(".post-admin-menu"), "also hides the menu"); + assert + .dom("[data-content][data-identifier='admin-post-menu']") + .doesNotExist("also hides the menu"); }); test("toggle moderator post", async function (assert) { @@ -608,29 +626,18 @@ module("Integration | Component | Widget | post", function (hooks) { await render(hbs` + `); await click(".post-menu-area .show-post-admin-menu"); - await click(".post-admin-menu .toggle-post-type"); + await click( + "[data-content][data-identifier='admin-post-menu'] .toggle-post-type" + ); assert.ok(this.toggled); - assert.ok(!exists(".post-admin-menu"), "also hides the menu"); - }); - - test("toggle moderator post", async function (assert) { - this.currentUser.set("moderator", true); - this.set("args", { canManage: true }); - this.set("togglePostType", () => (this.toggled = true)); - - await render(hbs` - - `); - - await click(".post-menu-area .show-post-admin-menu"); - await click(".post-admin-menu .toggle-post-type"); - - assert.ok(this.toggled); - assert.ok(!exists(".post-admin-menu"), "also hides the menu"); + assert + .dom("[data-content][data-identifier='admin-post-menu']") + .doesNotExist("also hides the menu"); }); test("rebake post", async function (assert) { @@ -639,27 +646,41 @@ module("Integration | Component | Widget | post", function (hooks) { await render(hbs` + `); await click(".post-menu-area .show-post-admin-menu"); - await click(".post-admin-menu .rebuild-html"); + await click( + "[data-content][data-identifier='admin-post-menu'] .rebuild-html" + ); assert.ok(this.baked); - assert.ok(!exists(".post-admin-menu"), "also hides the menu"); + assert + .dom("[data-content][data-identifier='admin-post-menu']") + .doesNotExist("also hides the menu"); }); test("unhide post", async function (assert) { + let unhidden; this.currentUser.admin = true; this.set("args", { canManage: true, hidden: true }); - this.set("unhidePost", () => (this.unhidden = true)); + this.set("unhidePost", () => (unhidden = true)); await render(hbs` + `); await click(".post-menu-area .show-post-admin-menu"); - await click(".post-admin-menu .unhide-post"); - assert.ok(this.unhidden); - assert.ok(!exists(".post-admin-menu"), "also hides the menu"); + + await click( + "[data-content][data-identifier='admin-post-menu'] .unhide-post" + ); + + assert.ok(unhidden); + + assert + .dom("[data-content][data-identifier='admin-post-menu']") + .doesNotExist("also hides the menu"); }); test("change owner", async function (assert) { @@ -669,12 +690,17 @@ module("Integration | Component | Widget | post", function (hooks) { await render(hbs` + `); await click(".post-menu-area .show-post-admin-menu"); - await click(".post-admin-menu .change-owner"); + await click( + "[data-content][data-identifier='admin-post-menu'] .change-owner" + ); assert.ok(this.owned); - assert.ok(!exists(".post-admin-menu"), "also hides the menu"); + assert + .dom("[data-content][data-identifier='admin-post-menu']") + .doesNotExist("also hides the menu"); }); test("reply", async function (assert) { diff --git a/app/assets/javascripts/float-kit/.npmrc b/app/assets/javascripts/float-kit/.npmrc new file mode 100644 index 00000000000..c42da845b44 --- /dev/null +++ b/app/assets/javascripts/float-kit/.npmrc @@ -0,0 +1 @@ +engine-strict = true diff --git a/app/assets/javascripts/float-kit/addon/components/d-button-tooltip.gjs b/app/assets/javascripts/float-kit/addon/components/d-button-tooltip.gjs new file mode 100644 index 00000000000..34b871153ff --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-button-tooltip.gjs @@ -0,0 +1,8 @@ +const DButtonTooltip = ; + +export default DButtonTooltip; diff --git a/app/assets/javascripts/float-kit/addon/components/d-default-toast.gjs b/app/assets/javascripts/float-kit/addon/components/d-default-toast.gjs new file mode 100644 index 00000000000..4fe30ba7f7c --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-default-toast.gjs @@ -0,0 +1,56 @@ +import DButton from "discourse/components/d-button"; +import icon from "discourse-common/helpers/d-icon"; +import { concat, fn, hash } from "@ember/helper"; +import concatClass from "discourse/helpers/concat-class"; +import or from "truth-helpers/helpers/or"; + +const DDefaultToast = ; + +export default DDefaultToast; diff --git a/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs b/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs new file mode 100644 index 00000000000..de7d0ee972b --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs @@ -0,0 +1,85 @@ +import Component from "@glimmer/component"; +import FloatKitApplyFloatingUi from "float-kit/modifiers/apply-floating-ui"; +import FloatKitCloseOnEscape from "float-kit/modifiers/close-on-escape"; +import FloatKitCloseOnClickOutside from "float-kit/modifiers/close-on-click-outside"; +import { modifier } from "ember-modifier"; +import { getScrollParent } from "float-kit/lib/get-scroll-parent"; +import concatClass from "discourse/helpers/concat-class"; +import { htmlSafe } from "@ember/template"; +import { concat } from "@ember/helper"; +import TrapTab from "discourse/modifiers/trap-tab"; +import DFloatPortal from "float-kit/components/d-float-portal"; + +export default class DFloatBody extends Component { + + + closeOnScroll = modifier(() => { + const firstScrollParent = getScrollParent(this.trigger); + + const handler = () => { + this.args.instance.close(); + }; + + firstScrollParent.addEventListener("scroll", handler, { passive: true }); + + return () => { + firstScrollParent.removeEventListener("scroll", handler); + }; + }); + + get supportsCloseOnClickOutside() { + return this.args.instance.expanded && this.options.closeOnClickOutside; + } + + get supportsCloseOnEscape() { + return this.args.instance.expanded && this.options.closeOnEscape; + } + + get supportsCloseOnScroll() { + return this.args.instance.expanded && this.options.closeOnScroll; + } + + get trigger() { + return this.args.instance.trigger; + } + + get options() { + return this.args.instance.options; + } +} diff --git a/app/assets/javascripts/float-kit/addon/components/d-float-portal.gjs b/app/assets/javascripts/float-kit/addon/components/d-float-portal.gjs new file mode 100644 index 00000000000..7cc2650f825 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-float-portal.gjs @@ -0,0 +1,18 @@ +import Component from "@glimmer/component"; +import { isTesting } from "discourse-common/config/environment"; + +export default class DFloatPortal extends Component { + + + get inline() { + return this.args.inline ?? isTesting(); + } +} diff --git a/app/assets/javascripts/float-kit/addon/components/d-inline-float.gjs b/app/assets/javascripts/float-kit/addon/components/d-inline-float.gjs new file mode 100644 index 00000000000..68c1860df49 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-inline-float.gjs @@ -0,0 +1,26 @@ +import DFloatBody from "float-kit/components/d-float-body"; + +const DInlineFloat = ; + +export default DInlineFloat; diff --git a/app/assets/javascripts/float-kit/addon/components/d-inline-menu.gjs b/app/assets/javascripts/float-kit/addon/components/d-inline-menu.gjs new file mode 100644 index 00000000000..cf7e1a31266 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-inline-menu.gjs @@ -0,0 +1,27 @@ +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; +import DInlineFloat from "float-kit/components/d-inline-float"; +import { MENU } from "float-kit/lib/constants"; +import didInsert from "@ember/render-modifiers/modifiers/did-insert"; + +export default class DInlineMenu extends Component { + + + @service menu; +} diff --git a/app/assets/javascripts/float-kit/addon/components/d-inline-tooltip.gjs b/app/assets/javascripts/float-kit/addon/components/d-inline-tooltip.gjs new file mode 100644 index 00000000000..33958abd900 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-inline-tooltip.gjs @@ -0,0 +1,31 @@ +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; +import DInlineFloat from "float-kit/components/d-inline-float"; +import { TOOLTIP } from "float-kit/lib/constants"; +import didInsert from "@ember/render-modifiers/modifiers/did-insert"; +import and from "truth-helpers/helpers/and"; + +export default class DInlineTooltip extends Component { + + + @service tooltip; +} diff --git a/app/assets/javascripts/float-kit/addon/components/d-menu.gjs b/app/assets/javascripts/float-kit/addon/components/d-menu.gjs new file mode 100644 index 00000000000..234ab4fa470 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-menu.gjs @@ -0,0 +1,104 @@ +import Component from "@glimmer/component"; +import { modifier } from "ember-modifier"; +import { tracked } from "@glimmer/tracking"; +import { inject as service } from "@ember/service"; +import DButton from "discourse/components/d-button"; +import DFloatBody from "float-kit/components/d-float-body"; +import concatClass from "discourse/helpers/concat-class"; +import { getOwner } from "discourse-common/lib/get-owner"; +import DMenuInstance from "float-kit/lib/d-menu-instance"; + +export default class DMenu extends Component { + + + @service menu; + + @tracked menuInstance = null; + + registerTrigger = modifier((element) => { + const options = { + ...this.args, + ...{ + autoUpdate: true, + listeners: true, + beforeTrigger: () => { + this.menu.close(); + }, + }, + }; + const instance = new DMenuInstance(getOwner(this), element, options); + + this.menuInstance = instance; + + return () => { + instance.destroy(); + + if (this.isDestroying) { + this.menuInstance = null; + } + }; + }); + + get menuId() { + return `d-menu-${this.menuInstance.id}`; + } + + get options() { + return this.menuInstance?.options ?? {}; + } + + get componentArgs() { + return { + close: this.menu.close, + data: this.options.data, + }; + } +} diff --git a/app/assets/javascripts/float-kit/addon/components/d-popover.gjs b/app/assets/javascripts/float-kit/addon/components/d-popover.gjs new file mode 100644 index 00000000000..508a5076454 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-popover.gjs @@ -0,0 +1,40 @@ +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; +import { modifier } from "ember-modifier"; + +import deprecated from "discourse-common/lib/deprecated"; + +export default class DPopover extends Component { + + + @service tooltip; + + registerDTooltip = modifier((element) => { + deprecated( + "`` is deprecated. Use `` or the `tooltip` service instead.", + { id: "discourse.d-popover" } + ); + + const trigger = element.children[0]; + const content = element.children[1]; + + if (!trigger || !content) { + return; + } + + const instance = this.tooltip.register(trigger, { + content, + }); + + content.remove(); + + return () => { + instance.destroy(); + }; + }); +} diff --git a/app/assets/javascripts/float-kit/addon/components/d-toasts.gjs b/app/assets/javascripts/float-kit/addon/components/d-toasts.gjs new file mode 100644 index 00000000000..2f6f3149017 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-toasts.gjs @@ -0,0 +1,27 @@ +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; +import concatClass from "discourse/helpers/concat-class"; +import { on } from "@ember/modifier"; + +export default class DToasts extends Component { + + + @service toasts; +} diff --git a/app/assets/javascripts/float-kit/addon/components/d-tooltip.gjs b/app/assets/javascripts/float-kit/addon/components/d-tooltip.gjs new file mode 100644 index 00000000000..cc468134404 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-tooltip.gjs @@ -0,0 +1,109 @@ +import Component from "@glimmer/component"; +import { modifier } from "ember-modifier"; +import { tracked } from "@glimmer/tracking"; +import icon from "discourse-common/helpers/d-icon"; +import { inject as service } from "@ember/service"; +import DFloatBody from "float-kit/components/d-float-body"; +import concatClass from "discourse/helpers/concat-class"; +import DTooltipInstance from "float-kit/lib/d-tooltip-instance"; +import { getOwner } from "discourse-common/lib/get-owner"; +import and from "truth-helpers/helpers/and"; + +export default class DTooltip extends Component { + + + @service tooltip; + + @tracked tooltipInstance = null; + + registerTrigger = modifier((element) => { + const options = { + ...this.args, + ...{ + listeners: true, + beforeTrigger: () => { + this.tooltip.close(); + }, + }, + }; + const instance = new DTooltipInstance(getOwner(this), element, options); + + this.tooltipInstance = instance; + + return () => { + instance.destroy(); + + if (this.isDestroying) { + this.tooltipInstance = null; + } + }; + }); + + get options() { + return this.tooltipInstance?.options; + } + + get componentArgs() { + return { + close: this.tooltip.close, + data: this.options.data, + }; + } +} diff --git a/app/assets/javascripts/float-kit/addon/lib/constants.js b/app/assets/javascripts/float-kit/addon/lib/constants.js new file mode 100644 index 00000000000..64a1a39c25e --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/lib/constants.js @@ -0,0 +1,76 @@ +export const FLOAT_UI_PLACEMENTS = [ + "top", + "top-start", + "top-end", + "right", + "right-start", + "right-end", + "bottom", + "bottom-start", + "bottom-end", + "left", + "left-start", + "left-end", +]; + +export const TOOLTIP = { + options: { + animated: true, + arrow: true, + beforeTrigger: null, + closeOnClickOutside: true, + closeOnEscape: true, + closeOnScroll: true, + component: null, + content: null, + identifier: null, + interactive: false, + listeners: false, + maxWidth: 350, + data: null, + offset: 10, + triggers: ["hover", "click"], + untriggers: ["hover", "click"], + placement: "top", + fallbackPlacements: FLOAT_UI_PLACEMENTS, + autoUpdate: true, + trapTab: true, + }, + portalOutletId: "d-tooltip-portal-outlet", +}; + +export const MENU = { + options: { + animated: true, + arrow: false, + beforeTrigger: null, + closeOnEscape: true, + closeOnClickOutside: true, + closeOnScroll: false, + component: null, + content: null, + identifier: null, + interactive: true, + listeners: false, + maxWidth: 400, + data: null, + offset: 10, + triggers: ["click"], + untriggers: ["click"], + placement: "bottom", + fallbackPlacements: FLOAT_UI_PLACEMENTS, + autoUpdate: true, + trapTab: true, + }, + portalOutletId: "d-menu-portal-outlet", +}; + +import DDefaultToast from "float-kit/components/d-default-toast"; + +export const TOAST = { + options: { + autoClose: true, + duration: 10000, + component: DDefaultToast, + }, +}; diff --git a/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js b/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js new file mode 100644 index 00000000000..bf069c7f010 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js @@ -0,0 +1,63 @@ +import { inject as service } from "@ember/service"; +import { action } from "@ember/object"; +import { setOwner } from "@ember/application"; +import { MENU } from "float-kit/lib/constants"; +import { guidFor } from "@ember/object/internals"; +import FloatKitInstance from "float-kit/lib/float-kit-instance"; + +export default class DMenuInstance extends FloatKitInstance { + @service menu; + + constructor(owner, trigger, options = {}) { + super(...arguments); + + setOwner(this, owner); + this.options = { ...MENU.options, ...options }; + this.id = trigger.id || guidFor(trigger); + this.trigger = trigger; + this.setupListeners(); + } + + @action + onMouseMove(event) { + if (this.trigger.contains(event.target) && this.expanded) { + return; + } + + this.onTrigger(event); + } + + @action + onClick(event) { + if (this.expanded && this.untriggers.includes("click")) { + this.onUntrigger(event); + return; + } + + this.onTrigger(event); + } + + @action + onMouseLeave(event) { + if (this.untriggers.includes("hover")) { + this.onUntrigger(event); + } + } + + @action + async onTrigger() { + this.options.beforeTrigger?.(this); + await this.show(); + } + + @action + async onUntrigger() { + await this.close(); + } + + @action + async destroy() { + await this.close(); + this.tearDownListeners(); + } +} diff --git a/app/assets/javascripts/float-kit/addon/lib/d-toast-instance.js b/app/assets/javascripts/float-kit/addon/lib/d-toast-instance.js new file mode 100644 index 00000000000..4e5ef55c7ac --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/lib/d-toast-instance.js @@ -0,0 +1,51 @@ +import { setOwner } from "@ember/application"; +import { TOAST } from "float-kit/lib/constants"; +import uniqueId from "discourse/helpers/unique-id"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import { modifier } from "ember-modifier"; +import discourseLater from "discourse-common/lib/later"; +import { cancel } from "@ember/runloop"; + +const CSS_TRANSITION_DELAY_MS = 500; +const TRANSITION_CLASS = "-fade-out"; + +export default class DToastInstance { + @service toasts; + + options = null; + id = uniqueId(); + autoCloseHandler = null; + + registerAutoClose = modifier((element) => { + let innerHandler; + + this.autoCloseHandler = discourseLater(() => { + element.classList.add(TRANSITION_CLASS); + + innerHandler = discourseLater(() => { + this.close(); + }, CSS_TRANSITION_DELAY_MS); + }, this.options.duration || TOAST.options.duration); + + return () => { + cancel(innerHandler); + cancel(this.autoCloseHandler); + }; + }); + + constructor(owner, options = {}) { + setOwner(this, owner); + this.options = { ...TOAST.options, ...options }; + } + + @action + close() { + this.toasts.close(this); + } + + @action + cancelAutoClose() { + cancel(this.autoCloseHandler); + } +} diff --git a/app/assets/javascripts/float-kit/addon/lib/d-tooltip-instance.js b/app/assets/javascripts/float-kit/addon/lib/d-tooltip-instance.js new file mode 100644 index 00000000000..b242d8d3b73 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/lib/d-tooltip-instance.js @@ -0,0 +1,63 @@ +import { inject as service } from "@ember/service"; +import { action } from "@ember/object"; +import { setOwner } from "@ember/application"; +import { TOOLTIP } from "float-kit/lib/constants"; +import { guidFor } from "@ember/object/internals"; +import FloatKitInstance from "float-kit/lib/float-kit-instance"; + +export default class DTooltipInstance extends FloatKitInstance { + @service tooltip; + + constructor(owner, trigger, options = {}) { + super(...arguments); + + setOwner(this, owner); + this.options = { ...TOOLTIP.options, ...options }; + this.id = trigger.id || guidFor(trigger); + this.trigger = trigger; + this.setupListeners(); + } + + @action + onMouseMove(event) { + if (this.trigger.contains(event.target) && this.expanded) { + return; + } + + this.onTrigger(event); + } + + @action + onClick(event) { + if (this.expanded && this.untriggers.includes("click")) { + this.onUntrigger(event); + return; + } + + this.onTrigger(event); + } + + @action + onMouseLeave(event) { + if (this.untriggers.includes("hover")) { + this.onUntrigger(event); + } + } + + @action + async onTrigger() { + this.options.beforeTrigger?.(this); + await this.show(); + } + + @action + async onUntrigger() { + await this.close(); + } + + @action + async destroy() { + await this.close(); + this.tearDownListeners(); + } +} diff --git a/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js b/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js new file mode 100644 index 00000000000..a9ae1326058 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js @@ -0,0 +1,198 @@ +import { action } from "@ember/object"; +import { cancel, next } from "@ember/runloop"; +import { tracked } from "@glimmer/tracking"; +import discourseLater from "discourse-common/lib/later"; +import { makeArray } from "discourse-common/lib/helpers"; +import { bind } from "discourse-common/utils/decorators"; + +const TOUCH_OPTIONS = { passive: true, capture: true }; + +function cancelEvent(event) { + event.preventDefault(); + event.stopImmediatePropagation(); +} + +export default class FloatKitInstance { + @tracked expanded = false; + @tracked id = null; + + trigger = null; + content = null; + + @action + async show() { + this.expanded = true; + + await new Promise((resolve) => next(resolve)); + } + + @action + async close() { + this.expanded = false; + + await new Promise((resolve) => next(resolve)); + } + + @action + onFocus(event) { + this.onTrigger(event); + } + + @action + onBlur(event) { + this.onTrigger(event); + } + + @action + onFocusIn(event) { + this.onTrigger(event); + } + + @action + onFocusOut(event) { + this.onTrigger(event); + } + + @action + onTouchStart(event) { + if (event.touches.length > 1) { + this.onTouchCancel(); + return; + } + + event.stopPropagation(); + + this.trigger.addEventListener( + "touchmove", + this.onTouchCancel, + TOUCH_OPTIONS + ); + this.trigger.addEventListener( + "touchcancel", + this.onTouchCancel, + TOUCH_OPTIONS + ); + this.trigger.addEventListener( + "touchend", + this.onTouchCancel, + TOUCH_OPTIONS + ); + this.touchTimeout = discourseLater(() => { + if (this.isDestroying || this.isDestroyed) { + return; + } + + this.trigger.addEventListener("touchend", cancelEvent, { + once: true, + capture: true, + }); + + this.onTrigger(event); + }, 500); + } + + @bind + onTouchCancel() { + cancel(this.touchTimeout); + + this.trigger.removeEventListener("touchmove", this.onTouchCancel); + this.trigger.removeEventListener("touchend", this.onTouchCancel); + this.trigger.removeEventListener("touchcancel", this.onTouchCancel); + } + + tearDownListeners() { + if (!this.options.listeners) { + return; + } + + makeArray(this.triggers) + .filter(Boolean) + .forEach((trigger) => { + switch (trigger) { + case "hold": + this.trigger.addEventListener("touchstart", this.onTouchStart); + break; + case "focus": + this.trigger.removeEventListener("focus", this.onFocus); + this.trigger.removeEventListener("blur", this.onBlur); + break; + case "focusin": + this.trigger.removeEventListener("focusin", this.onFocusIn); + this.trigger.removeEventListener("focusout", this.onFocusOut); + break; + case "hover": + this.trigger.removeEventListener("mousemove", this.onMouseMove); + if (!this.options.interactive) { + this.trigger.removeEventListener("mouseleave", this.onMouseLeave); + } + + break; + case "click": + this.trigger.removeEventListener("click", this.onClick); + break; + } + }); + + cancel(this.touchTimeout); + } + + setupListeners() { + if (!this.options.listeners) { + return; + } + + makeArray(this.triggers) + .filter(Boolean) + .forEach((trigger) => { + switch (trigger) { + case "hold": + this.trigger.addEventListener( + "touchstart", + this.onTouchStart, + TOUCH_OPTIONS + ); + break; + case "focus": + this.trigger.addEventListener("focus", this.onFocus, { + passive: true, + }); + this.trigger.addEventListener("blur", this.onBlur, { + passive: true, + }); + break; + case "focusin": + this.trigger.addEventListener("focusin", this.onFocusIn, { + passive: true, + }); + this.trigger.addEventListener("focusout", this.onFocusOut, { + passive: true, + }); + break; + case "hover": + this.trigger.addEventListener("mousemove", this.onMouseMove, { + passive: true, + }); + if (!this.options.interactive) { + this.trigger.addEventListener("mouseleave", this.onMouseLeave, { + passive: true, + }); + } + + break; + case "click": + this.trigger.addEventListener("click", this.onClick, { + passive: true, + }); + break; + } + }); + } + + get triggers() { + return this.options.triggers ?? ["click"]; + } + + get untriggers() { + return this.options.untriggers ?? ["click"]; + } +} diff --git a/app/assets/javascripts/float-kit/addon/lib/get-scroll-parent.js b/app/assets/javascripts/float-kit/addon/lib/get-scroll-parent.js new file mode 100644 index 00000000000..d0de893ac0c --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/lib/get-scroll-parent.js @@ -0,0 +1,13 @@ +export function getScrollParent(node) { + const isElement = node instanceof HTMLElement; + const overflowY = isElement && window.getComputedStyle(node).overflowY; + const isScrollable = overflowY !== "visible" && overflowY !== "hidden"; + + if (!node || node === document.documentElement) { + return null; + } else if (isScrollable && node.scrollHeight >= node.clientHeight) { + return node; + } + + return getScrollParent(node.parentNode) || window; +} diff --git a/app/assets/javascripts/float-kit/addon/lib/update-position.js b/app/assets/javascripts/float-kit/addon/lib/update-position.js new file mode 100644 index 00000000000..40987b95710 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/lib/update-position.js @@ -0,0 +1,93 @@ +import { + arrow, + computePosition, + flip, + inline, + offset, + shift, +} from "@floating-ui/dom"; +import { FLOAT_UI_PLACEMENTS } from "float-kit/lib/constants"; +import { isTesting } from "discourse-common/config/environment"; +import { headerOffset } from "discourse/lib/offset-calculator"; +import { iconHTML } from "discourse-common/lib/icon-library"; +import domFromString from "discourse-common/lib/dom-from-string"; + +export async function updatePosition(trigger, content, options) { + let padding = 0; + if (!isTesting()) { + padding = options.padding || { + top: headerOffset(), + left: 10, + right: 10, + bottom: 10, + }; + } + + const flipOptions = { + fallbackPlacements: options.fallbackPlacements ?? FLOAT_UI_PLACEMENTS, + padding, + }; + + const middleware = [ + offset(options.offset ? parseInt(options.offset, 10) : 10), + ]; + + if (options.inline) { + middleware.push(inline()); + } + + middleware.push(flip(flipOptions)); + middleware.push(shift({ padding })); + + let arrowElement; + if (options.arrow) { + arrowElement = content.querySelector(".arrow"); + + if (!arrowElement) { + arrowElement = domFromString( + iconHTML("tippy-rounded-arrow", { class: "arrow" }) + )[0]; + content.appendChild(arrowElement); + } + + middleware.push(arrow({ element: arrowElement })); + } + + content.dataset.strategy = options.strategy || "absolute"; + + const { x, y, placement, middlewareData } = await computePosition( + trigger, + content, + { + placement: options.placement, + strategy: options.strategy || "absolute", + middleware, + } + ); + + if (options.computePosition) { + options.computePosition(content, { + x, + y, + placement, + middlewareData, + arrowElement, + }); + } else { + content.dataset.placement = placement; + Object.assign(content.style, { + left: `${x}px`, + top: `${y}px`, + }); + + if (middlewareData.arrow && arrowElement) { + const arrowX = middlewareData.arrow.x; + const arrowY = middlewareData.arrow.y; + + Object.assign(arrowElement.style, { + left: arrowX != null ? `${arrowX}px` : "", + top: arrowY != null ? `${arrowY}px` : "", + }); + } + } +} diff --git a/app/assets/javascripts/float-kit/addon/modifiers/apply-floating-ui.js b/app/assets/javascripts/float-kit/addon/modifiers/apply-floating-ui.js new file mode 100644 index 00000000000..338eb9e1e91 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/modifiers/apply-floating-ui.js @@ -0,0 +1,37 @@ +import Modifier from "ember-modifier"; +import { registerDestructor } from "@ember/destroyable"; +import { bind } from "discourse-common/utils/decorators"; +import { autoUpdate } from "@floating-ui/dom"; +import { updatePosition } from "float-kit/lib/update-position"; + +export default class FloatKitApplyFloatingUi extends Modifier { + constructor(owner, args) { + super(owner, args); + registerDestructor(this, (instance) => instance.teardown()); + } + + modify(element, [trigger, options, instance]) { + instance.content = element; + this.instance = instance; + this.options = options ?? {}; + + if (this.options.autoUpdate) { + this.cleanup = autoUpdate(trigger, element, this.update); + } else { + this.update(); + } + } + + @bind + async update() { + await updatePosition( + this.instance.trigger, + this.instance.content, + this.options + ); + } + + teardown() { + this.cleanup?.(); + } +} diff --git a/app/assets/javascripts/float-kit/addon/modifiers/close-on-click-outside.js b/app/assets/javascripts/float-kit/addon/modifiers/close-on-click-outside.js new file mode 100644 index 00000000000..84b0f64eb8d --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/modifiers/close-on-click-outside.js @@ -0,0 +1,38 @@ +import Modifier from "ember-modifier"; +import { registerDestructor } from "@ember/destroyable"; +import { bind } from "discourse-common/utils/decorators"; + +export default class FloatKitCloseOnClickOutside extends Modifier { + constructor(owner, args) { + super(owner, args); + registerDestructor(this, (instance) => instance.cleanup()); + } + + modify(element, [trigger, closeFn]) { + this.closeFn = closeFn; + this.trigger = trigger; + this.element = element; + document.addEventListener("click", this.check, { + passive: true, + }); + } + + @bind + check(event) { + if (this.element.contains(event.target)) { + return; + } + if ( + this.trigger instanceof HTMLElement && + this.trigger.contains(event.target) + ) { + return; + } + + this.closeFn(); + } + + cleanup() { + document.removeEventListener("click", this.check); + } +} diff --git a/app/assets/javascripts/float-kit/addon/modifiers/close-on-escape.js b/app/assets/javascripts/float-kit/addon/modifiers/close-on-escape.js new file mode 100644 index 00000000000..a6d038c4882 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/modifiers/close-on-escape.js @@ -0,0 +1,33 @@ +import Modifier from "ember-modifier"; +import { registerDestructor } from "@ember/destroyable"; +import { bind } from "discourse-common/utils/decorators"; +import { inject as service } from "@ember/service"; + +export default class FloatKitCloseOnEscape extends Modifier { + @service menu; + + constructor(owner, args) { + super(owner, args); + registerDestructor(this, (instance) => instance.cleanup()); + } + + modify(element, [closeFn]) { + this.closeFn = closeFn; + this.element = element; + + document.addEventListener("keydown", this.check); + } + + @bind + check(event) { + if (event.key === "Escape") { + event.stopPropagation(); + event.preventDefault(); + this.closeFn(); + } + } + + cleanup() { + document.removeEventListener("keydown", this.check); + } +} diff --git a/app/assets/javascripts/float-kit/addon/services/menu.js b/app/assets/javascripts/float-kit/addon/services/menu.js new file mode 100644 index 00000000000..c132e9ec5e3 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/services/menu.js @@ -0,0 +1,120 @@ +import Service from "@ember/service"; +import { getOwner } from "@ember/application"; +import { action } from "@ember/object"; +import DMenuInstance from "float-kit/lib/d-menu-instance"; +import { guidFor } from "@ember/object/internals"; +import { tracked } from "@glimmer/tracking"; +import { updatePosition } from "float-kit/lib/update-position"; + +export default class Menu extends Service { + @tracked activeMenu; + @tracked portalOutletElement; + + /** + * Render a menu + * + * @param {Element | DMenuInstance} + * - trigger - the element that triggered the menu, can also be an object implementing `getBoundingClientRect` + * - menu - an instance of a menu + * @param {Object} [options] - options + * @param {String | Element | Component} [options.content] - Specifies the content of the menu + * @param {Integer} [options.maxWidth] - Specifies the maximum width of the content + * @param {Object} [options.data] - An object which will be passed as the `@data` argument when content is a `Component` + * @param {Boolean} [options.arrow] - Determines if the menu has an arrow + * @param {Boolean} [options.offset] - Displaces the content from its reference trigger in pixels + * @param {String} [options.identifier] - Add a data-identifier attribute to the trigger and the content + * @param {Boolean} [options.inline] - Improves positioning for trigger that spans over multiple lines + * + * @returns {Promise} + */ + @action + async show() { + let instance; + + if (arguments[0] instanceof DMenuInstance) { + instance = arguments[0]; + + if (this.activeMenu === instance && this.activeMenu.expanded) { + return; + } + } else { + const trigger = arguments[0]; + if ( + this.activeMenu && + this.activeMenu.id === + (trigger?.id?.length ? trigger.id : guidFor(trigger)) && + this.activeMenu.expanded + ) { + this.activeMenu?.close(); + return; + } + + instance = new DMenuInstance(getOwner(this), trigger, arguments[1]); + } + + await this.replace(instance); + instance.expanded = true; + return instance; + } + + /** + * Replaces any active menu- + */ + @action + async replace(menu) { + await this.activeMenu?.close(); + this.activeMenu = menu; + } + + /** + * Closes the active menu + * @param {DMenuInstance} [menu] - the menu to close, if not provider will close any active menu + */ + @action + async close(menu) { + if (this.activeMenu && menu && this.activeMenu.id !== menu.id) { + return; + } + + await this.activeMenu?.close(); + this.activeMenu = null; + } + + /** + * Update the menu position + * @param {DMenuInstance} [menu] - the menu to update, if not provider will update any active menu + */ + @action + async update(menu) { + const instance = menu || this.activeMenu; + if (!instance) { + return; + } + await updatePosition(instance.trigger, instance.content, instance.options); + await instance.show(); + } + + /** + * Register event listeners on a trigger to show a menu + * + * @param {Element} trigger - the element that triggered the menu, can also be an object implementing `getBoundingClientRect` + * @param {Object} [options] - @see `show` + * + * @returns {DMenuInstance} An instance of the menu + */ + @action + register(trigger, options = {}) { + return new DMenuInstance(getOwner(this), trigger, { + ...options, + listeners: true, + beforeTrigger: async (menu) => { + await this.replace(menu); + }, + }); + } + + @action + registerPortalOutletElement(element) { + this.portalOutletElement = element; + } +} diff --git a/app/assets/javascripts/float-kit/addon/services/toasts.js b/app/assets/javascripts/float-kit/addon/services/toasts.js new file mode 100644 index 00000000000..3c1381a5a2c --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/services/toasts.js @@ -0,0 +1,113 @@ +import Service from "@ember/service"; +import { tracked } from "@glimmer/tracking"; +import { TrackedArray } from "@ember-compat/tracked-built-ins"; +import { action } from "@ember/object"; +import DDefaultToast from "float-kit/components/d-default-toast"; +import DToastInstance from "float-kit/lib/d-toast-instance"; +import { getOwner } from "discourse-common/lib/get-owner"; + +export default class Toasts extends Service { + @tracked activeToasts = new TrackedArray(); + + /** + * Render a toast + * + * @param {Object} [options] - options passed to the toast component as `@toast` argument + * @param {String} [options.duration] - The duration (ms) of the toast, will be closed after this time + * @param {ComponentClass} [options.component] - A component to render, will use `DDefaultToast` if not provided + * @param {String} [options.class] - A class added to the d-toast element + * @param {Object} [options.data] - An object which will be passed as the `@data` argument to the component + * + * @returns {DToastInstance} - a toast instance + */ + @action + show(options = {}) { + const instance = new DToastInstance(getOwner(this), options); + this.activeToasts.push(instance); + return instance; + } + + /** + * Render a DDefaultToast toast with the default theme + * + * @param {Object} [options] - @see show + * + * @returns {DToastInstance} - a toast instance + */ + @action + default(options = {}) { + options.data.theme = "default"; + + return this.show({ ...options, component: DDefaultToast }); + } + + /** + * Render a DDefaultToast toast with the success theme + * + * @param {Object} [options] - @see show + * + * @returns {DToastInstance} - a toast instance + */ + @action + success(options = {}) { + options.data.theme = "success"; + options.data.icon = "check"; + + return this.show({ ...options, component: DDefaultToast }); + } + + /** + * Render a DDefaultToast toast with the error theme + * + * @param {Object} [options] - @see show + * + * @returns {DToastInstance} - a toast instance + */ + @action + error(options = {}) { + options.data.theme = "error"; + options.data.icon = "exclamation-triangle"; + + return this.show({ ...options, component: DDefaultToast }); + } + + /** + * Render a DDefaultToast toast with the warning theme + * + * @param {Object} [options] - @see show + * + * @returns {DToastInstance} - a toast instance + */ + @action + warning(options = {}) { + options.data.theme = "warning"; + options.data.icon = "exclamation-circle"; + + return this.show({ ...options, component: DDefaultToast }); + } + + /** + * Render a DDefaultToast toast with the info theme + * + * @param {Object} [options] - @see show + * + * @returns {DToastInstance} - a toast instance + */ + @action + info(options = {}) { + options.data.theme = "info"; + options.data.icon = "info-circle"; + + return this.show({ ...options, component: DDefaultToast }); + } + + /** + * Close a toast. Any object containg a valid `id` property can be used as a toast parameter. + */ + @action + close(toast) { + this.activeToasts = new TrackedArray( + this.activeToasts.filter((activeToast) => activeToast.id !== toast.id) + ); + } +} diff --git a/app/assets/javascripts/float-kit/addon/services/tooltip.js b/app/assets/javascripts/float-kit/addon/services/tooltip.js new file mode 100644 index 00000000000..19eaa29f5be --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/services/tooltip.js @@ -0,0 +1,120 @@ +import Service from "@ember/service"; +import { getOwner } from "@ember/application"; +import { action } from "@ember/object"; +import DTooltipInstance from "float-kit/lib/d-tooltip-instance"; +import { guidFor } from "@ember/object/internals"; +import { tracked } from "@glimmer/tracking"; +import { updatePosition } from "float-kit/lib/update-position"; + +export default class Tooltip extends Service { + @tracked activeTooltip; + @tracked portalOutletElement; + + /** + * Render a tooltip + * + * @param {Element | DTooltipInstance} + * - trigger - the element that triggered the tooltip, can also be an object implementing `getBoundingClientRect` + * - tooltip - an instance of a tooltip + * @param {Object} [options] - options, if trigger given as first argument + * @param {String | Element | Component} [options.content] - Specifies the content of the tooltip + * @param {Integer} [options.maxWidth] - Specifies the maximum width of the content + * @param {Object} [options.data] - An object which will be passed as the `@data` argument when content is a `Component` + * @param {Boolean} [options.arrow] - Determines if the tooltip has an arrow + * @param {Boolean} [options.offset] - Displaces the content from its reference trigger in pixels + * @param {String} [options.identifier] - Add a data-identifier attribute to the trigger and the content + * @param {Boolean} [options.inline] - Improves positioning for trigger that spans over multiple lines + * + * @returns {Promise} + */ + @action + async show() { + let instance; + + if (arguments[0] instanceof DTooltipInstance) { + instance = arguments[0]; + + if (this.activeTooltip === instance && this.activeTooltip.expanded) { + return; + } + } else { + const trigger = arguments[0]; + if ( + this.activeTooltip && + this.activeTooltip.id === + (trigger?.id?.length ? trigger.id : guidFor(trigger)) && + this.activeTooltip.expanded + ) { + this.activeTooltip?.close(); + return; + } + + instance = new DTooltipInstance(getOwner(this), trigger, arguments[1]); + } + + await this.replace(instance); + instance.expanded = true; + return instance; + } + + /** + * Replaces any active tooltip + */ + @action + async replace(tooltip) { + await this.activeTooltip?.close(); + this.activeTooltip = tooltip; + } + + /** + * Closes the active tooltip + * @param {DTooltipInstance} [tooltip] - the tooltip to close, if not provider will close any active tooltip + */ + @action + async close(tooltip) { + if (this.activeTooltip && tooltip && this.activeTooltip.id !== tooltip.id) { + return; + } + + await this.activeTooltip?.close(); + this.activeTooltip = null; + } + + /** + * Update the tooltip position + * @param {DTooltipInstance} [tooltip] - the tooltip to update, if not provider will update any active tooltip + */ + @action + async update(tooltip) { + const instance = tooltip || this.activeTooltip; + if (!instance) { + return; + } + await updatePosition(instance.trigger, instance.content, instance.options); + await instance.show(); + } + + /** + * Register event listeners on a trigger to show a tooltip + * + * @param {Element} trigger - the element that triggered the tooltip, can also be an object implementing `getBoundingClientRect` + * @param {Object} [options] - @see `show` + * + * @returns {DTooltipInstance} An instance of the tooltip + */ + @action + register(trigger, options = {}) { + return new DTooltipInstance(getOwner(this), trigger, { + ...options, + listeners: true, + beforeTrigger: async (tooltip) => { + await this.replace(tooltip); + }, + }); + } + + @action + registerPortalOutletElement(element) { + this.portalOutletElement = element; + } +} diff --git a/app/assets/javascripts/float-kit/app/.gitkeep b/app/assets/javascripts/float-kit/app/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/assets/javascripts/float-kit/app/components/d-button-tooltip.js b/app/assets/javascripts/float-kit/app/components/d-button-tooltip.js new file mode 100644 index 00000000000..ff10ff34c1b --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-button-tooltip.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-button-tooltip"; diff --git a/app/assets/javascripts/float-kit/app/components/d-default-toast.gjs b/app/assets/javascripts/float-kit/app/components/d-default-toast.gjs new file mode 100644 index 00000000000..3bf6e0d8e6f --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-default-toast.gjs @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-default-toast"; diff --git a/app/assets/javascripts/float-kit/app/components/d-inline-menu.js b/app/assets/javascripts/float-kit/app/components/d-inline-menu.js new file mode 100644 index 00000000000..65c4b0de930 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-inline-menu.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-inline-menu"; diff --git a/app/assets/javascripts/float-kit/app/components/d-inline-tooltip.js b/app/assets/javascripts/float-kit/app/components/d-inline-tooltip.js new file mode 100644 index 00000000000..173122c7b20 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-inline-tooltip.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-inline-tooltip"; diff --git a/app/assets/javascripts/float-kit/app/components/d-menu.js b/app/assets/javascripts/float-kit/app/components/d-menu.js new file mode 100644 index 00000000000..1abf4922f56 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-menu.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-menu"; diff --git a/app/assets/javascripts/float-kit/app/components/d-popover.js b/app/assets/javascripts/float-kit/app/components/d-popover.js new file mode 100644 index 00000000000..9698f99ba72 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-popover.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-popover"; diff --git a/app/assets/javascripts/float-kit/app/components/d-toasts.js b/app/assets/javascripts/float-kit/app/components/d-toasts.js new file mode 100644 index 00000000000..59a465f7240 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-toasts.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-toasts"; diff --git a/app/assets/javascripts/float-kit/app/components/d-tooltip.js b/app/assets/javascripts/float-kit/app/components/d-tooltip.js new file mode 100644 index 00000000000..7f388b800b1 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-tooltip.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-tooltip"; diff --git a/app/assets/javascripts/float-kit/app/lib/d-menu-instance.js b/app/assets/javascripts/float-kit/app/lib/d-menu-instance.js new file mode 100644 index 00000000000..b26ba8e00ef --- /dev/null +++ b/app/assets/javascripts/float-kit/app/lib/d-menu-instance.js @@ -0,0 +1 @@ +export { default } from "float-kit/lib/d-menu-instance"; diff --git a/app/assets/javascripts/float-kit/app/lib/d-tooltip-instance.js b/app/assets/javascripts/float-kit/app/lib/d-tooltip-instance.js new file mode 100644 index 00000000000..1afb55aca88 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/lib/d-tooltip-instance.js @@ -0,0 +1 @@ +export { default } from "float-kit/lib/d-tooltip-instance"; diff --git a/app/assets/javascripts/float-kit/app/services/menu.js b/app/assets/javascripts/float-kit/app/services/menu.js new file mode 100644 index 00000000000..7a5860455d5 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/services/menu.js @@ -0,0 +1 @@ +export { default } from "float-kit/services/menu"; diff --git a/app/assets/javascripts/float-kit/app/services/toasts.js b/app/assets/javascripts/float-kit/app/services/toasts.js new file mode 100644 index 00000000000..99ed2c35b61 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/services/toasts.js @@ -0,0 +1 @@ +export { default } from "float-kit/services/toasts"; diff --git a/app/assets/javascripts/float-kit/app/services/tooltip.js b/app/assets/javascripts/float-kit/app/services/tooltip.js new file mode 100644 index 00000000000..71206d00609 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/services/tooltip.js @@ -0,0 +1 @@ +export { default } from "float-kit/services/tooltip"; diff --git a/app/assets/javascripts/float-kit/config/ember-cli-update.json b/app/assets/javascripts/float-kit/config/ember-cli-update.json new file mode 100644 index 00000000000..994928cd9c9 --- /dev/null +++ b/app/assets/javascripts/float-kit/config/ember-cli-update.json @@ -0,0 +1,20 @@ +{ + "schemaVersion": "1.0.0", + "packages": [ + { + "name": "ember-cli", + "version": "5.0.0", + "blueprints": [ + { + "name": "addon", + "outputRepo": "https://github.com/ember-cli/ember-addon-output", + "codemodsSource": "ember-addon-codemods-manifest@1", + "isBaseBlueprint": true, + "options": [ + "--no-welcome" + ] + } + ] + } + ] +} diff --git a/app/assets/javascripts/float-kit/ember-cli-build.js b/app/assets/javascripts/float-kit/ember-cli-build.js new file mode 100644 index 00000000000..da57a67830a --- /dev/null +++ b/app/assets/javascripts/float-kit/ember-cli-build.js @@ -0,0 +1,27 @@ +"use strict"; + +const EmberAddon = require("ember-cli/lib/broccoli/ember-addon"); + +module.exports = function (defaults) { + const app = new EmberAddon(defaults, { + autoImport: { + publicAssetURL: "", + }, + }); + + /* + This build file specifies the options for the dummy test app of this + addon, located in `/tests/dummy` + This build file does *not* influence how the addon or the app using it + behave. You most likely want to be modifying `./index.js` or app's build file + */ + + const { maybeEmbroider } = require("@embroider/test-setup"); + return maybeEmbroider(app, { + skipBabel: [ + { + package: "qunit", + }, + ], + }); +}; diff --git a/app/assets/javascripts/float-kit/index.js b/app/assets/javascripts/float-kit/index.js new file mode 100644 index 00000000000..28c7dca628f --- /dev/null +++ b/app/assets/javascripts/float-kit/index.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + name: require("./package").name, + options: {}, + isDevelopingAddon() { + return true; + }, +}; diff --git a/app/assets/javascripts/float-kit/jsconfig.json b/app/assets/javascripts/float-kit/jsconfig.json new file mode 100644 index 00000000000..9f224112d4b --- /dev/null +++ b/app/assets/javascripts/float-kit/jsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../../jsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "float-kit/*": ["./addon/*"], + "discourse/*": ["../discourse/app/*"], + "discourse/tests/*": ["../discourse/tests/*"], + "discourse-common/*": ["../discourse-common/addon/*"], + } + }, +} diff --git a/app/assets/javascripts/float-kit/package.json b/app/assets/javascripts/float-kit/package.json new file mode 100644 index 00000000000..b5bb6050cd3 --- /dev/null +++ b/app/assets/javascripts/float-kit/package.json @@ -0,0 +1,49 @@ +{ + "name": "float-kit", + "version": "1.0.0", + "description": "Discourse's floating panels component.", + "author": "Discourse", + "license": "GPL-2.0-only", + "keywords": [ + "ember-addon" + ], + "scripts": { + "build": "ember build", + "lint:hbs": "ember-template-lint .", + "lint:js": "eslint .", + "start": "ember serve" + }, + "dependencies": { + "ember-auto-import": "^2.6.3", + "ember-cli-babel": "^7.26.11", + "ember-cli-htmlbars": "^6.3.0", + "ember-template-imports": "^3.4.2" + }, + "devDependencies": { + "@babel/core": "^7.22.10", + "@ember/optional-features": "^2.0.0", + "@ember/string": "^3.1.1", + "@embroider/test-setup": "^3.0.1", + "@glimmer/component": "^1.1.2", + "broccoli-asset-rev": "^3.0.0", + "ember-cli": "~5.0.0", + "ember-cli-inject-live-reload": "^2.1.0", + "ember-cli-sri": "^2.1.1", + "ember-cli-terser": "^4.0.2", + "ember-disable-prototype-extensions": "^1.1.3", + "ember-load-initializers": "^2.1.1", + "ember-resolver": "^10.1.1", + "ember-source": "~3.28.12", + "ember-source-channel-url": "^3.0.0", + "loader.js": "^4.7.0", + "webpack": "^5.88.2" + }, + "engines": { + "node": "16.* || >= 18", + "npm": "please-use-yarn", + "yarn": ">= 1.21.1" + }, + "ember": { + "edition": "default" + } +} diff --git a/app/assets/javascripts/package.json b/app/assets/javascripts/package.json index 7b28c5d6886..e73d1f216a9 100644 --- a/app/assets/javascripts/package.json +++ b/app/assets/javascripts/package.json @@ -17,6 +17,7 @@ "ember-production-deprecations", "pretty-text", "select-kit", + "float-kit", "truth-helpers", "wizard" ], diff --git a/app/assets/javascripts/yarn.lock b/app/assets/javascripts/yarn.lock index 695667c5be7..e0c389221e5 100644 --- a/app/assets/javascripts/yarn.lock +++ b/app/assets/javascripts/yarn.lock @@ -28,7 +28,28 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== -"@babel/core@^7.12.0", "@babel/core@^7.14.5", "@babel/core@^7.16.10", "@babel/core@^7.16.7", "@babel/core@^7.21.4", "@babel/core@^7.22.17", "@babel/core@^7.3.4": +"@babel/core@^7.12.0", "@babel/core@^7.14.5", "@babel/core@^7.16.10", "@babel/core@^7.16.7", "@babel/core@^7.21.4", "@babel/core@^7.22.10", "@babel/core@^7.3.4": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.15.tgz#15d4fd03f478a459015a4b94cfbb3bd42c48d2f4" + integrity sha512-PtZqMmgRrvj8ruoEOIwVA3yoF91O+Hgw9o7DAUTNBA6Mo2jpu31clx9a7Nz/9JznqetTR6zwfC4L3LAjKQXUwA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.22.15" + "@babel/helpers" "^7.22.15" + "@babel/parser" "^7.22.15" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.15" + "@babel/types" "^7.22.15" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/core@^7.22.17": version "7.22.17" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.17.tgz#2f9b0b395985967203514b24ee50f9fd0639c866" integrity sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ== @@ -1412,6 +1433,26 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333" integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w== +"@floating-ui/core@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.4.1.tgz#0d633f4b76052668afb932492ac452f7ebe97f17" + integrity sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ== + dependencies: + "@floating-ui/utils" "^0.1.1" + +"@floating-ui/dom@^1.5.0": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.1.tgz#88b70defd002fe851f17b4a25efb2d3c04d7a8d7" + integrity sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw== + dependencies: + "@floating-ui/core" "^1.4.1" + "@floating-ui/utils" "^0.1.1" + +"@floating-ui/utils@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.1.tgz#1a5b1959a528e374e8037c4396c3e825d6cf4a83" + integrity sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw== + "@glimmer/component@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@glimmer/component/-/component-1.1.2.tgz#892ec0c9f0b6b3e41c112be502fde073cf24d17c" @@ -1583,7 +1624,7 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@popperjs/core@^2.11.8", "@popperjs/core@^2.9.0": +"@popperjs/core@^2.11.8": version "2.11.8" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== @@ -10266,13 +10307,6 @@ tiny-lr@^2.0.0: object-assign "^4.1.0" qs "^6.4.0" -tippy.js@^6.3.7: - version "6.3.7" - resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c" - integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ== - dependencies: - "@popperjs/core" "^2.9.0" - tmp@0.0.28: version "0.0.28" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120" diff --git a/app/assets/stylesheets/color_definitions.scss b/app/assets/stylesheets/color_definitions.scss index 5211a129cca..0c99452ae93 100644 --- a/app/assets/stylesheets/color_definitions.scss +++ b/app/assets/stylesheets/color_definitions.scss @@ -144,4 +144,7 @@ --shadow-header: 0 2px 4px -1px rgba(0, 0, 0, #{$shadow-opacity-header}); --shadow-footer-nav: 0 0 2px 0 rgba(0, 0, 0, #{$shadow-opacity-footer-nav}); --shadow-focus-danger: 0 0 6px 0 var(--danger); + + --float-kit-arrow-stroke-color: var(--primary-low); + --float-kit-arrow-fill-color: var(--secondary); } diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 9ec8d1776c0..c93b705538d 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -3,8 +3,6 @@ @import "vendor/normalize-ext"; @import "vendor/pikaday"; @import "vendor/rotate-center"; -@import "vendor/tippy"; -@import "vendor/svg-arrow"; @import "common/whcm"; @import "common/foundation/helpers"; @import "common/foundation/base"; @@ -19,3 +17,4 @@ @import "common/software-update-prompt"; @import "common/topic-timeline"; @import "common/loading-slider"; +@import "common/float-kit/_index"; diff --git a/app/assets/stylesheets/common/base/_index.scss b/app/assets/stylesheets/common/base/_index.scss index 74c56a123c2..387651c4906 100644 --- a/app/assets/stylesheets/common/base/_index.scss +++ b/app/assets/stylesheets/common/base/_index.scss @@ -12,7 +12,6 @@ @import "crawler_layout"; @import "d-image-grid"; @import "d-icon"; -@import "d-popover"; @import "dialog"; @import "directory"; @import "discourse"; diff --git a/app/assets/stylesheets/common/base/d-popover.scss b/app/assets/stylesheets/common/base/d-popover.scss deleted file mode 100644 index 8b9c8f0c587..00000000000 --- a/app/assets/stylesheets/common/base/d-popover.scss +++ /dev/null @@ -1,32 +0,0 @@ -$d-popover-background: var(--secondary); -$d-popover-border: var(--primary-low); - -.tippy-box { - color: var(--primary); - background: $d-popover-background; - border: 1px solid var(--primary-low); - box-shadow: var(--shadow-menu-panel); - border-radius: var(--d-border-radius); - - > .tippy-svg-arrow { - color: $d-popover-background; - } -} - -.tippy-box[data-placement^="top"] .tippy-svg-arrow > svg { - top: 12px; -} -.tippy-box[data-placement^="bottom"] .tippy-svg-arrow > svg { - top: -10px; -} - -#tippy-rounded-arrow { - .svg-arrow { - fill: $d-popover-border; - } -} - -[data-tooltip] > *, -[data-popover] > * { - pointer-events: none; -} diff --git a/app/assets/stylesheets/common/base/search-menu.scss b/app/assets/stylesheets/common/base/search-menu.scss index 31b8a865214..d1f3e54e0be 100644 --- a/app/assets/stylesheets/common/base/search-menu.scss +++ b/app/assets/stylesheets/common/base/search-menu.scss @@ -26,6 +26,7 @@ $search-pad-horizontal: 0.5em; align-items: center; border: 1px solid var(--primary-medium); border-radius: var(--d-border-radius-large); + input#search-term { border-width: 0; border-radius: var(--d-border-radius-large); diff --git a/app/assets/stylesheets/common/base/sidebar-section.scss b/app/assets/stylesheets/common/base/sidebar-section.scss index 98d8636852b..f886c8f1f69 100644 --- a/app/assets/stylesheets/common/base/sidebar-section.scss +++ b/app/assets/stylesheets/common/base/sidebar-section.scss @@ -150,3 +150,9 @@ } } } + +.sidebar-section-header-global-indicator__content { + .d-icon-shield-alt { + padding-right: 0.25rem; + } +} diff --git a/app/assets/stylesheets/common/base/sidebar.scss b/app/assets/stylesheets/common/base/sidebar.scss index c4ba5a41698..f4ef150d1aa 100644 --- a/app/assets/stylesheets/common/base/sidebar.scss +++ b/app/assets/stylesheets/common/base/sidebar.scss @@ -217,6 +217,11 @@ } } } + + .always-public-tooltip { + padding-right: 0.5rem; + } + .btn-flat.add-link { margin-top: 0.5em; margin-left: -0.5em; diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index 49504acf2c6..82bf2379e15 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -560,18 +560,8 @@ aside.quote { } .quote-button { - display: none; - position: absolute; - z-index: z("dropdown"); - background-color: var(--secondary); - border: 1px solid var(--primary-low); - box-shadow: var(--shadow-card); flex-direction: column; - &.animated { - transition: top 0.1s linear, left 0.1s linear; - } - &.visible { display: flex; } @@ -609,12 +599,9 @@ aside.quote { } .btn-flat { - .d-icon { - color: var(--primary-high); - } - .discourse-no-touch & { - &:hover { - background-color: var(--tertiary-low); + &:hover { + .d-icon { + color: var(--tertiary); } } } diff --git a/app/assets/stylesheets/common/base/user-tips.scss b/app/assets/stylesheets/common/base/user-tips.scss index ea432faabfe..94fe31ba042 100644 --- a/app/assets/stylesheets/common/base/user-tips.scss +++ b/app/assets/stylesheets/common/base/user-tips.scss @@ -1,49 +1,24 @@ -.tippy-box[data-theme="user-tip"] { +[data-content][data-identifier="user-tip"] { + min-width: 300px; background-color: var(--tertiary); + border: none; color: var(--secondary); - .btn-flat { - color: var(--secondary); + --float-kit-arrow-stroke-color: var(--tertiary); + --float-kit-arrow-fill-color: var(--tertiary); - &:hover { - color: var(--d-hover); - } - } - - .btn-primary { - background: var(--secondary); - color: var(--tertiary); - - &:hover { - color: var(--tertiary-hover); - } - - .d-icon { - color: var(--tertiary); - } - } - - > .tippy-svg-arrow { - color: var(--tertiary); - } -} - -.user-tip { - z-index: z("composer", "content") - 1; - - &__container { + .user-tip__container { font-weight: normal; - min-width: 300px; padding: 0.5em; text-align: left; } - &__title { + .user-tip__title { font-size: var(--font-up-2); font-weight: bold; } - &__content { + .user-tip__content { margin-top: 0.25em; a { @@ -52,17 +27,12 @@ } } - &__buttons { + .user-tip__buttons { margin-top: 1em; } -} -.tippy-box[data-placement^="left"] > .tippy-svg-arrow { - right: -1px; -} - -.tippy-box[data-theme~="user-tips"] > .tippy-svg-arrow:after, -.tippy-box[data-theme~="user-tips"] > .tippy-svg-arrow > svg { - width: 18px; - height: 18px; + .btn-primary { + background: var(--secondary); + color: var(--tertiary); + } } diff --git a/app/assets/stylesheets/common/components/_index.scss b/app/assets/stylesheets/common/components/_index.scss index 847205aa6b0..9f750493453 100644 --- a/app/assets/stylesheets/common/components/_index.scss +++ b/app/assets/stylesheets/common/components/_index.scss @@ -9,7 +9,6 @@ @import "calendar-date-time-input"; @import "convert-to-public-topic-modal"; @import "d-lightbox"; -@import "d-tooltip"; @import "d-toggle-switch"; @import "date-input"; @import "date-picker"; @@ -46,3 +45,4 @@ @import "user-stream-item"; @import "user-stream"; @import "widget-dropdown"; +@import "admin-post-menu"; diff --git a/app/assets/stylesheets/common/components/admin-post-menu.scss b/app/assets/stylesheets/common/components/admin-post-menu.scss new file mode 100644 index 00000000000..09bb530e350 --- /dev/null +++ b/app/assets/stylesheets/common/components/admin-post-menu.scss @@ -0,0 +1,20 @@ +[data-content][data-identifier="admin-post-menu"] { + ul { + padding: 0.5rem; + margin: 0; + list-style: none; + + li .btn { + width: 100%; + justify-content: flex-start; + } + + li { + margin-bottom: 2px; + + &:last-child { + margin-bottom: 0; + } + } + } +} diff --git a/app/assets/stylesheets/common/components/buttons.scss b/app/assets/stylesheets/common/components/buttons.scss index 95909e5c689..56d8936a620 100644 --- a/app/assets/stylesheets/common/components/buttons.scss +++ b/app/assets/stylesheets/common/components/buttons.scss @@ -381,6 +381,60 @@ } } +.btn-transparent { + background: none; + border: 0; + color: var(--primary); + + &.btn-primary, + &.btn-primary:focus, + &.btn-primary:hover { + .d-icon { + color: var(--tertiary) !important; + } + } + &.btn-danger, + &.btn-danger:focus, + &.btn-danger:hover { + .d-icon { + color: var(--danger) !important; + } + } + &.btn-success, + &.btn-success:focus, + &.btn-success:hover { + .d-icon { + color: var(--success) !important; + } + } + + .discourse-no-touch & { + &:hover { + color: var(--primary); + background: var(--d-hover); + .d-icon { + color: var(--primary); + } + } + } + + &:focus { + color: var(--primary); + background: var(--d-hover); + .d-icon { + color: var(--primary); + } + } + + &:focus-visible { + color: var(--primary); + background: var(--d-hover); + .d-icon { + color: var(--primary); + } + } +} + .btn-mini-toggle { border-radius: var(--d-border-radius); padding: 0.4em 0.467em; diff --git a/app/assets/stylesheets/common/components/d-tooltip.scss b/app/assets/stylesheets/common/components/d-tooltip.scss deleted file mode 100644 index 28433f513db..00000000000 --- a/app/assets/stylesheets/common/components/d-tooltip.scss +++ /dev/null @@ -1,12 +0,0 @@ -// ".tippy-box" is one of the classes tippy.js uses for creating tooltips -// see https://atomiks.github.io/tippyjs/v6/themes/#tippy-elements -// -// Using `data-theme~="d-tooltip"` scopes down these styles -// to tooltips created using the d-tooltip component -.tippy-box[data-theme~="d-tooltip"] { - color: var(--primary); - background: var(--secondary); - border: 1px solid var(--primary-low); - box-shadow: var(--shadow-menu-panel); - overflow-wrap: break-word; -} diff --git a/app/assets/stylesheets/common/components/download-calendar.scss b/app/assets/stylesheets/common/components/download-calendar.scss index 2acfb340f80..fd8693db0c6 100644 --- a/app/assets/stylesheets/common/components/download-calendar.scss +++ b/app/assets/stylesheets/common/components/download-calendar.scss @@ -2,7 +2,7 @@ margin-top: 2em; } -div[data-tippy-root] .download-calendar { +.download-calendar { color: var(--primary-med-or-secondary-med); } diff --git a/app/assets/stylesheets/common/components/user-card.scss b/app/assets/stylesheets/common/components/user-card.scss index 3c9a807ca49..c1b6a3e8c2a 100644 --- a/app/assets/stylesheets/common/components/user-card.scss +++ b/app/assets/stylesheets/common/components/user-card.scss @@ -37,11 +37,14 @@ .user-card, .group-card { width: var(--card-width); - box-shadow: var(--shadow-card); color: var(--primary); background: var(--secondary) center center; background-size: cover; - outline: 2px solid transparent; + position: unset !important; + margin: 0 !important; + border-radius: 0 !important; + box-shadow: unset !important; + z-index: unset !important; .card-content { padding: 10px; diff --git a/app/assets/stylesheets/common/components/user-status-message.scss b/app/assets/stylesheets/common/components/user-status-message.scss index b8f1aae0c4d..3c2b384593d 100644 --- a/app/assets/stylesheets/common/components/user-status-message.scss +++ b/app/assets/stylesheets/common/components/user-status-message.scss @@ -1,22 +1,22 @@ -.user-status-message-description { - margin-left: 0.1em; - color: var(--primary-800); -} - -.user-status-message-tooltip { +[data-content][data-identifier="user-status-message-tooltip"] { .emoji { width: 1em; height: 1em; } + .user-status-message-description { + margin-left: 0.1rem; + color: var(--primary-800); + } + .user-status-tooltip-description { font-weight: bold; - margin-left: 0.1em; + margin-left: 0.25rem; vertical-align: middle; } .user-status-tooltip-until { - margin-top: 0.2em; + margin-top: 0.1rem; color: var(--primary-medium); } } diff --git a/app/assets/stylesheets/common/float-kit/_index.scss b/app/assets/stylesheets/common/float-kit/_index.scss new file mode 100644 index 00000000000..6754da592bc --- /dev/null +++ b/app/assets/stylesheets/common/float-kit/_index.scss @@ -0,0 +1,5 @@ +@import "d-menu"; +@import "d-tooltip"; +@import "d-button-tooltip"; +@import "d-toasts"; +@import "d-default-toast"; diff --git a/app/assets/stylesheets/common/float-kit/d-button-tooltip.scss b/app/assets/stylesheets/common/float-kit/d-button-tooltip.scss new file mode 100644 index 00000000000..7644c6560d0 --- /dev/null +++ b/app/assets/stylesheets/common/float-kit/d-button-tooltip.scss @@ -0,0 +1,12 @@ +.fk-d-button-tooltip { + display: inline-flex; + align-items: center; + + .fk-d-tooltip__trigger { + background: var(--primary-very-low); + height: 100%; + align-items: center; + display: flex; + padding-inline: 0.5rem; + } +} diff --git a/app/assets/stylesheets/common/float-kit/d-default-toast.scss b/app/assets/stylesheets/common/float-kit/d-default-toast.scss new file mode 100644 index 00000000000..3be87e4e43e --- /dev/null +++ b/app/assets/stylesheets/common/float-kit/d-default-toast.scss @@ -0,0 +1,101 @@ +.fk-d-default-toast { + display: flex; + flex: 1 1 auto; + max-width: 350px; + padding: 0.5rem; + + &__close-container { + width: calc(40px - 0.5rem); + height: 30px; + } + + &__icon-container { + flex: 1 0 auto; + display: flex; + width: calc(40px - 0.5rem); + height: 30px; + align-items: center; + justify-content: center; + + &.alert-success { + background-color: var(--success-low); + color: var(--primary); + } + &.alert-error { + background-color: var(--danger-low); + color: var(--primary); + } + &.alert-warning { + background-color: var(--highlight-bg); + color: var(--primary); + } + &.alert-info { + background-color: var(--tertiary-low); + color: var(--primary); + &.clickable { + color: var(--tertiary); + z-index: z("base"); + } + } + + .-success & { + .d-icon { + color: var(--success); + } + } + + .-error & { + .d-icon { + color: var(--danger); + } + } + + .-warning & { + .d-icon { + color: var(--highlight); + } + } + + .-info & { + .d-icon { + color: var(--tertiary); + } + } + } + + &__main-container { + box-sizing: border-box; + display: flex; + flex-direction: column; + width: 100%; + min-height: 30px; + } + + &__texts { + min-height: 30px; + display: flex; + justify-content: center; + flex-direction: column; + } + + &__actions { + display: flex; + flex-wrap: wrap; + padding-top: 1rem; + margin-bottom: -0.5rem; + + .btn { + margin-right: 0.5rem; + margin-bottom: 0.5rem; + } + } + + &__title { + display: flex; + font-weight: 700; + } + + &__message { + display: flex; + } +} diff --git a/app/assets/stylesheets/common/float-kit/d-menu.scss b/app/assets/stylesheets/common/float-kit/d-menu.scss new file mode 100644 index 00000000000..23249ed8ba6 --- /dev/null +++ b/app/assets/stylesheets/common/float-kit/d-menu.scss @@ -0,0 +1,91 @@ +@keyframes d-menu-opening { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +.fk-d-menu { + width: max-content; + position: absolute; + top: 0; + left: 0; + max-width: 600px; + display: flex; + padding: 0; + z-index: z("dropdown"); + + &__trigger { + .touch & { + @include unselectable; + } + } + + &.-animated { + animation: d-menu-opening 0.15s ease-in; + + &[data-placement^="bottom"] { + transform-origin: top center; + } + + &[data-placement^="top"] { + transform-origin: bottom center; + } + + &[data-placement^="right"] { + transform-origin: center left; + } + + &[data-placement^="left"] { + transform-origin: center right; + } + } + + &[data-strategy="fixed"] { + position: fixed; + } + + &__inner-content { + display: flex; + border-radius: var(--d-border-radius); + background-color: var(--secondary); + border: 1px solid var(--primary-low); + box-shadow: var(--shadow-menu-panel); + } + + .arrow { + z-index: z("max"); + position: absolute; + color: var(--secondary); + } + + &[data-placement^="top"] { + .arrow { + bottom: -9px; + rotate: 180deg; + } + } + + &[data-placement^="bottom"] { + .arrow { + top: -9px; + } + } + + &[data-placement^="right"] { + .arrow { + rotate: -90deg; + left: -9px; + } + } + + &[data-placement^="left"] { + .arrow { + rotate: 90deg; + right: -9px; + } + } +} diff --git a/app/assets/stylesheets/common/float-kit/d-toasts.scss b/app/assets/stylesheets/common/float-kit/d-toasts.scss new file mode 100644 index 00000000000..6619ca13080 --- /dev/null +++ b/app/assets/stylesheets/common/float-kit/d-toasts.scss @@ -0,0 +1,48 @@ +@keyframes d-toast-opening { + 0% { + transform: translateX(0px); + } + 50% { + transform: translateX(-5px); + } + 100% { + transform: translateX(0px); + } +} + +.fk-d-toasts { + position: fixed; + top: 5px; + right: 5px; + z-index: z("max"); + + .mobile-view & { + left: 5px; + } + + .fk-d-toast { + box-sizing: border-box; + opacity: 1; + transition: opacity 0.5s; + border-radius: var(--d-border-radius); + overflow: hidden; + background-color: var(--secondary); + border: 1px solid var(--primary-low); + box-shadow: var(--shadow-menu-panel); + overflow-wrap: break-word; + display: flex; + animation: d-toast-opening 0.5s ease-in-out; + + &:hover { + border-color: var(--primary-300); + } + + &.-fade-out { + opacity: 0; + } + + & + .d-toast { + margin-top: 0.25rem; + } + } +} diff --git a/app/assets/stylesheets/common/float-kit/d-tooltip.scss b/app/assets/stylesheets/common/float-kit/d-tooltip.scss new file mode 100644 index 00000000000..d2f1fc79250 --- /dev/null +++ b/app/assets/stylesheets/common/float-kit/d-tooltip.scss @@ -0,0 +1,91 @@ +@keyframes d-tooltip-opening { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +.fk-d-tooltip { + background-color: var(--secondary); + border-radius: var(--d-border-radius); + border: 1px solid var(--primary-low); + box-shadow: var(--shadow-menu-panel); + z-index: z("tooltip"); + width: max-content; + position: absolute; + top: 0; + left: 0; + display: flex !important; + padding: 0; + + &__trigger { + display: inline-flex; + + .touch & { + @include unselectable; + } + } + + &.-animated { + animation: d-tooltip-opening 0.15s ease-in; + + &[data-placement^="bottom"] { + transform-origin: top center; + } + + &[data-placement^="top"] { + transform-origin: bottom center; + } + + &[data-placement^="right"] { + transform-origin: center left; + } + + &[data-placement^="left"] { + transform-origin: center right; + } + } + + &__inner-content { + display: flex; + overflow: hidden; + overflow-wrap: break-word; + padding: 0.5rem; + align-items: center; + } + + .arrow { + z-index: z("max"); + position: absolute; + } + + &[data-placement^="top"] { + .arrow { + bottom: -9px; + rotate: 180deg; + } + } + + &[data-placement^="bottom"] { + .arrow { + top: -9px; + } + } + + &[data-placement^="right"] { + .arrow { + rotate: -90deg; + left: -9px; + } + } + + &[data-placement^="left"] { + .arrow { + rotate: 90deg; + right: -9px; + } + } +} diff --git a/app/assets/stylesheets/desktop/components/user-card.scss b/app/assets/stylesheets/desktop/components/user-card.scss index e048b9ca956..fca587b11bb 100644 --- a/app/assets/stylesheets/desktop/components/user-card.scss +++ b/app/assets/stylesheets/desktop/components/user-card.scss @@ -1,14 +1,6 @@ // shared styles for user and group cards .user-card, .group-card { - z-index: z("usercard"); - &.fixed { - position: fixed; - z-index: z("header") + 1; - } - &.docked-card { - z-index: z("header") + 1; - } // avatar - names - controls .first-row { .names { diff --git a/app/assets/stylesheets/mobile/components/user-card.scss b/app/assets/stylesheets/mobile/components/user-card.scss index f114ed1647e..aa2f04ba7ef 100644 --- a/app/assets/stylesheets/mobile/components/user-card.scss +++ b/app/assets/stylesheets/mobile/components/user-card.scss @@ -1,11 +1,14 @@ +[data-content][data-identifier="card"] { + z-index: z("max"); +} + // shared styles for user and group cards .user-card, .group-card { - // mobile cards should always be on top of everything - 1102 - z-index: z("mobile-composer") + 2; max-width: 95vw; - margin: 0 2.5vw; + margin: 0; max-height: 85vh; // 2.5vh margin-top and margin-bottom. 10vh top + box-sizing: border-box; // avatar - names - controls .first-row { flex-wrap: wrap; diff --git a/app/assets/stylesheets/vendor/svg-arrow.css b/app/assets/stylesheets/vendor/svg-arrow.css deleted file mode 100644 index c2a61ad7ae3..00000000000 --- a/app/assets/stylesheets/vendor/svg-arrow.css +++ /dev/null @@ -1 +0,0 @@ -.tippy-box[data-placement^=top]>.tippy-svg-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-svg-arrow:after,.tippy-box[data-placement^=top]>.tippy-svg-arrow>svg{top:16px;transform:rotate(180deg)}.tippy-box[data-placement^=bottom]>.tippy-svg-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:16px}.tippy-box[data-placement^=left]>.tippy-svg-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-svg-arrow:after,.tippy-box[data-placement^=left]>.tippy-svg-arrow>svg{transform:rotate(90deg);top:calc(50% - 3px);left:11px}.tippy-box[data-placement^=right]>.tippy-svg-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-svg-arrow:after,.tippy-box[data-placement^=right]>.tippy-svg-arrow>svg{transform:rotate(-90deg);top:calc(50% - 3px);right:11px}.tippy-svg-arrow{width:16px;height:16px;fill:#333;text-align:initial}.tippy-svg-arrow,.tippy-svg-arrow>svg{position:absolute} \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/tippy.css b/app/assets/stylesheets/vendor/tippy.css deleted file mode 100644 index e6ae635cb1f..00000000000 --- a/app/assets/stylesheets/vendor/tippy.css +++ /dev/null @@ -1 +0,0 @@ -.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md index 6473d754758..f11dbad05a4 100644 --- a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md +++ b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md @@ -7,12 +7,18 @@ in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.12.0] - 2023-09-06 + +### Added + +- Adds `addPostAdminMenuButton` which allows to register a new button in the post admin menu. + ## [1.11.0] - 2023-08-30 ### Added -- Adds `addBeforeAuthCompleteCallback` which allows plugins and themes to add functions to be - evaluated before the auth-complete logic is run. If any of these callbacks return false, the +- Adds `addBeforeAuthCompleteCallback` which allows plugins and themes to add functions to be + evaluated before the auth-complete logic is run. If any of these callbacks return false, the auth-complete logic will be aborted. ## [1.10.0] - 2023-08-25 @@ -39,7 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.8.0] - 2023-07-18 ### Added -- Adds `addSidebarPanel` which is experimental, and adds a Sidebar panel by returning a class which extends from the +- Adds `addSidebarPanel` which is experimental, and adds a Sidebar panel by returning a class which extends from the BaseCustomSidebarPanel class. - Adds `setSidebarPanel` which is experimental, and sets the current sidebar panel. diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.hbs index 710fca376ab..e9b8c74727e 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.hbs @@ -1,30 +1,24 @@ {{#if @buttons.length}} - - {{#if this.isExpanded}} -
    + as |menu| + > +
      {{#each @buttons as |button|}}
    • {{/each}}
    - {{/if}} + {{/if}} \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.js b/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.js index 70ee9a414b1..94059e2ad32 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer-dropdown.js @@ -1,68 +1,10 @@ import Component from "@glimmer/component"; -import { iconHTML } from "discourse-common/lib/icon-library"; -import tippy from "tippy.js"; import { action } from "@ember/object"; -import { hideOnEscapePlugin } from "discourse/lib/d-popover"; -import { tracked } from "@glimmer/tracking"; export default class ChatComposerDropdown extends Component { - @tracked isExpanded = false; - @tracked tippyInstance = null; - - trigger = null; - @action - setupTrigger(element) { - this.trigger = element; - } - - get ariaControls() { - return this.tippyInstance?.popper?.id; - } - - @action - toggleExpand() { - if (this.args.hasActivePanel) { - this.args.onCloseActivePanel?.(); - } else { - this.isExpanded = !this.isExpanded; - } - } - - @action - onButtonClick(button) { - this.tippyInstance.hide(); + onButtonClick(button, closeFn) { + closeFn(); button.action(); } - - @action - setupPanel(element) { - this.tippyInstance = tippy(this.trigger, { - theme: "chat-composer-dropdown", - trigger: "click", - zIndex: 1400, - arrow: iconHTML("tippy-rounded-arrow"), - interactive: true, - allowHTML: false, - appendTo: "parent", - hideOnClick: true, - plugins: [hideOnEscapePlugin], - content: element, - onShow: () => { - this.isExpanded = true; - return true; - }, - onHide: () => { - this.isExpanded = false; - return true; - }, - }); - - this.tippyInstance.show(); - } - - @action - teardownPanel() { - this.tippyInstance?.destroy(); - } } diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs index 9d12ae0bc5d..66a32b97f1e 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs @@ -35,13 +35,6 @@
    + {{! template-lint-disable modifier-name-case }} + {{#if (and @reaction this.emojiUrl)}} + + {{/if}} + + + @service capabilities; + @service currentUser; + @service tooltip; + @service site; + + @tracked isActive = false; + + registerTooltip = modifier((element) => { + if (!this.popoverContent?.length) { + return; + } + + const instance = this.tooltip.register(element, { + content: htmlSafe(this.popoverContent), + identifier: "chat-message-reaction-tooltip", + animated: false, + placement: "top", + fallbackPlacements: ["bottom"], + triggers: this.site.mobileView ? ["hold"] : ["hover"], + }); + + return () => { + instance?.destroy(); + }; + }); + + get showCount() { + return this.args.showCount ?? true; + } + + get emojiString() { + return `:${this.args.reaction.emoji}:`; + } + + get emojiUrl() { + return emojiUrlFor(this.args.reaction.emoji); + } + + @action + handleClick(event) { + event.stopPropagation(); + + this.args.onReaction?.( + this.args.reaction.emoji, + this.args.reaction.reacted ? "remove" : "add" + ); + + this.tooltip.close(); + } + + @cached + get popoverContent() { + if (!this.args.reaction.count || !this.args.reaction.users?.length) { + return; + } + + return emojiUnescape(getReactionText(this.args.reaction, this.currentUser)); + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.hbs deleted file mode 100644 index 44aa89c5dda..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.hbs +++ /dev/null @@ -1,30 +0,0 @@ -{{#if (and @reaction this.emojiUrl)}} - -{{/if}} \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.js b/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.js deleted file mode 100644 index 1511eec04a1..00000000000 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.js +++ /dev/null @@ -1,156 +0,0 @@ -import Component from "@glimmer/component"; -import { action } from "@ember/object"; -import { emojiUnescape, emojiUrlFor } from "discourse/lib/text"; -import { cancel } from "@ember/runloop"; -import { inject as service } from "@ember/service"; -import setupPopover from "discourse/lib/d-popover"; -import discourseLater from "discourse-common/lib/later"; -import { tracked } from "@glimmer/tracking"; -import { getReactionText } from "discourse/plugins/chat/discourse/lib/get-reaction-text"; - -export default class ChatMessageReaction extends Component { - @service capabilities; - @service currentUser; - - @tracked isActive = false; - - get showCount() { - return this.args.showCount ?? true; - } - - @action - setup(element) { - this.setupListeners(element); - this.setupTooltip(element); - } - - @action - teardown() { - cancel(this.longPressHandler); - this.teardownTooltip(); - } - - @action - setupListeners(element) { - this.element = element; - - if (this.capabilities.touch) { - this.element.addEventListener("touchstart", this.onTouchStart, { - passive: true, - }); - this.element.addEventListener("touchmove", this.cancelTouch, { - passive: true, - }); - this.element.addEventListener("touchend", this.onTouchEnd); - this.element.addEventListener("touchCancel", this.cancelTouch); - } - - this.element.addEventListener("click", this.handleClick, { - passive: true, - }); - } - - @action - teardownListeners() { - if (this.capabilities.touch) { - this.element.removeEventListener("touchstart", this.onTouchStart, { - passive: true, - }); - this.element.removeEventListener("touchmove", this.cancelTouch, { - passive: true, - }); - this.element.removeEventListener("touchend", this.onTouchEnd); - this.element.removeEventListener("touchCancel", this.cancelTouch); - } - - this.element.removeEventListener("click", this.handleClick, { - passive: true, - }); - } - - @action - onTouchStart(event) { - event.stopPropagation(); - this.isActive = true; - - this.longPressHandler = discourseLater(() => { - this.touching = false; - }, 400); - - this.touching = true; - } - - @action - cancelTouch() { - cancel(this.longPressHandler); - this._tippyInstance?.hide(); - this.touching = false; - this.isActive = false; - } - - @action - onTouchEnd(event) { - event.preventDefault(); - - if (this.touching) { - this.handleClick(event); - } - - cancel(this.longPressHandler); - this._tippyInstance?.hide(); - this.isActive = false; - } - - @action - setupTooltip(element) { - this._tippyInstance = setupPopover(element, { - trigger: "mouseenter", - interactive: false, - allowHTML: true, - offset: [0, 10], - onShow(instance) { - if (instance.props.content === "") { - return false; - } - }, - }); - } - - @action - teardownTooltip() { - this._tippyInstance?.destroy(); - } - - @action - refreshTooltip() { - this._tippyInstance?.setContent(this.popoverContent || ""); - } - - get emojiString() { - return `:${this.args.reaction.emoji}:`; - } - - get emojiUrl() { - return emojiUrlFor(this.args.reaction.emoji); - } - - @action - handleClick(event) { - event.stopPropagation(); - - this.args.onReaction?.( - this.args.reaction.emoji, - this.args.reaction.reacted ? "remove" : "add" - ); - - this._tippyInstance?.clearDelayTimeouts(); - } - - get popoverContent() { - if (!this.args.reaction.count || !this.args.reaction.users?.length) { - return; - } - - return emojiUnescape(getReactionText(this.args.reaction, this.currentUser)); - } -} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs index c93132059b5..e5f7687406e 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs @@ -34,7 +34,6 @@ import willDestroy from "@ember/render-modifiers/modifiers/will-destroy"; import ChatOnLongPress from "discourse/plugins/chat/discourse/modifiers/chat/on-long-press"; let _chatMessageDecorators = []; -let _tippyInstances = []; export function addChatMessageDecorator(decorator) { _chatMessageDecorators.push(decorator); @@ -297,13 +296,6 @@ export default class ChatMessage extends Component { this.#teardownMentionedUsers(); } - #destroyTippyInstances() { - _tippyInstances.forEach((instance) => { - instance.destroy(); - }); - _tippyInstances = []; - } - @action refreshStatusOnMentions() { schedule("afterRender", () => { @@ -314,7 +306,7 @@ export default class ChatMessage extends Component { ); mentions.forEach((mention) => { - updateUserStatusOnMention(mention, user.status, _tippyInstances); + updateUserStatusOnMention(getOwner(this), mention, user.status); }); }); }); @@ -596,6 +588,5 @@ export default class ChatMessage extends Component { user.stopTrackingStatus(); user.off("status-changed", this, "refreshStatusOnMentions"); }); - this.#destroyTippyInstances(); } } diff --git a/plugins/chat/assets/javascripts/discourse/helpers/noop.js b/plugins/chat/assets/javascripts/discourse/helpers/noop.js deleted file mode 100644 index c224727fc2e..00000000000 --- a/plugins/chat/assets/javascripts/discourse/helpers/noop.js +++ /dev/null @@ -1,5 +0,0 @@ -import { helper } from "@ember/component/helper"; - -export default helper(function noop() { - return () => {}; -}); diff --git a/plugins/chat/assets/stylesheets/common/chat-composer-dropdown.scss b/plugins/chat/assets/stylesheets/common/chat-composer-dropdown.scss index d0a3364fdd4..9eb8444f66d 100644 --- a/plugins/chat/assets/stylesheets/common/chat-composer-dropdown.scss +++ b/plugins/chat/assets/stylesheets/common/chat-composer-dropdown.scss @@ -1,11 +1,3 @@ -[data-theme="chat-composer-dropdown"] { - margin-left: 0.2rem; - - .tippy-content { - padding: 0; - } -} - .chat-composer.is-disabled { .no-touch & { .chat-composer-dropdown__trigger-btn:hover { @@ -18,7 +10,9 @@ } .chat-composer-dropdown__trigger-btn { + margin-left: 0.2rem; transition: transform 0.25s ease-in-out; + .d-icon { padding: 5px; transition: transform 0.1s ease-in-out; @@ -26,14 +20,25 @@ border-radius: 100%; } + &:focus, + &:hover, + &:active { + .d-icon { + color: var(--primary) !important; + } + + background: none !important; + background-image: none !important; + } + &:hover { transform: scale(1.1); } - &[aria-expanded="true"] { + &.-expanded { .d-icon { transform: rotate(135deg); - transform-origin: center; + transform-origin: center center; } } } @@ -45,9 +50,9 @@ } .chat-composer-dropdown__action-btn { - background: none; width: 100%; justify-content: flex-start; + background: none; .d-icon { color: var(--primary); diff --git a/plugins/chat/assets/stylesheets/common/chat-message.scss b/plugins/chat/assets/stylesheets/common/chat-message.scss index a05bff4c16d..ea179c45e39 100644 --- a/plugins/chat/assets/stylesheets/common/chat-message.scss +++ b/plugins/chat/assets/stylesheets/common/chat-message.scss @@ -24,6 +24,14 @@ } } +[data-content][data-identifier="chat-message-reaction-tooltip"] { + font-size: var(--font-down-1); + + .emoji { + padding-left: 0.5rem; + } +} + .chat-message { align-items: flex-start; padding: 0.25em 0.5em 0.25em 0.75em; @@ -226,10 +234,6 @@ } } - &:has(.tippy-box) { - position: relative; - z-index: 1; - } .chat-message-reaction-list .chat-message-react-btn { display: none; } diff --git a/plugins/chat/spec/system/react_to_message_spec.rb b/plugins/chat/spec/system/react_to_message_spec.rb index 50acaed31dc..0fd6c48d436 100644 --- a/plugins/chat/spec/system/react_to_message_spec.rb +++ b/plugins/chat/spec/system/react_to_message_spec.rb @@ -166,9 +166,6 @@ RSpec.describe "React to message", type: :system do channel.click_reaction(message_1, "female_detective") expect(channel).to have_reaction(message_1, "female_detective", "1") - expect( - channel.find_reaction(message_1, "female_detective")["data-tippy-content"], - ).to include(other_user.username) end end end diff --git a/plugins/chat/test/javascripts/components/chat-channel-test.js b/plugins/chat/test/javascripts/components/chat-channel-test.js index c53359d10db..522f5004e00 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-test.js @@ -61,7 +61,9 @@ module( }); test("it shows status on mentions", async function (assert) { - await render(hbs``); + await render( + hbs`` + ); assertStatusIsRendered( assert, @@ -76,7 +78,9 @@ module( }); test("it updates status on mentions", async function (assert) { - await render(hbs``); + await render( + hbs`` + ); const newStatus = { description: "off to dentist", @@ -89,11 +93,13 @@ module( const selector = statusSelector(mentionedUser.username); await waitFor(selector); + assertStatusIsRendered( assert, statusSelector(mentionedUser.username), newStatus ); + await assertStatusTooltipIsRendered( assert, statusSelector(mentionedUser.username), @@ -102,7 +108,9 @@ module( }); test("it deletes status on mentions", async function (assert) { - await render(hbs``); + await render( + hbs`` + ); this.appEvents.trigger("user-status:changed", { [mentionedUser.id]: null, @@ -114,7 +122,9 @@ module( }); test("it shows status on mentions on messages that came from Message Bus", async function (assert) { - await render(hbs``); + await render( + hbs`` + ); await receiveChatMessageViaMessageBus(); @@ -131,7 +141,9 @@ module( }); test("it updates status on mentions on messages that came from Message Bus", async function (assert) { - await render(hbs``); + await render( + hbs`` + ); await receiveChatMessageViaMessageBus(); const newStatus = { @@ -157,7 +169,9 @@ module( }); test("it deletes status on mentions on messages that came from Message Bus", async function (assert) { - await render(hbs``); + await render( + hbs`` + ); await receiveChatMessageViaMessageBus(); this.appEvents.trigger("user-status:changed", { @@ -181,7 +195,7 @@ module( } async function assertStatusTooltipIsRendered(assert, selector, status) { - await triggerEvent(selector, "mouseenter"); + await triggerEvent(selector, "mousemove"); assert.equal( document diff --git a/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js b/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js index 344d796cdf7..1619388a3b2 100644 --- a/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js +++ b/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js @@ -1,17 +1,16 @@ -import deprecated from "discourse-common/lib/deprecated"; -import { getOwner } from "discourse-common/lib/get-owner"; +import { bind } from "discourse-common/utils/decorators"; import LocalDateBuilder from "../lib/local-date-builder"; import { withPluginApi } from "discourse/lib/plugin-api"; import showModal from "discourse/lib/show-modal"; import { downloadCalendar } from "discourse/lib/download-calendar"; import { renderIcon } from "discourse-common/lib/icon-library"; import I18n from "I18n"; -import { hidePopover, showPopover } from "discourse/lib/d-popover"; import { addTagDecorateCallback, addTextDecorateCallback, } from "discourse/lib/to-markdown"; import generateDateMarkup from "discourse/plugins/discourse-local-dates/lib/local-date-markup-generator"; +import { htmlSafe } from "@ember/template"; // Import applyLocalDates from discourse/lib/local-dates instead export function applyLocalDates(dates, siteSettings) { @@ -348,11 +347,9 @@ function _calculateDuration(element) { export default { name: "discourse-local-dates", + @bind showDatePopover(event) { - const owner = getOwner(this); - if (owner.isDestroyed || owner.isDestroying) { - return; - } + const tooltip = this.container.lookup("service:tooltip"); if (event?.target?.classList?.contains("download-calendar")) { const dataset = event.target.dataset; @@ -363,50 +360,25 @@ export default { }, ]); - // TODO: remove this when rewriting preview as a component - const parentPopover = event.target.closest("[data-tippy-root]"); - if (parentPopover?._tippy) { - parentPopover._tippy.hide(); - } - - return; + return tooltip.close(); } if (!event?.target?.classList?.contains("discourse-local-date")) { return; } - const siteSettings = owner.lookup("service:site-settings"); - - showPopover(event, { - trigger: "click", - content: buildHtmlPreview(event.target, siteSettings), - allowHTML: true, - interactive: true, - appendTo: "parent", - onHidden: (instance) => { - instance.destroy(); - }, + const siteSettings = this.container.lookup("service:site-settings"); + return tooltip.show(event.target, { + content: htmlSafe(buildHtmlPreview(event.target, siteSettings)), }); }, - hideDatePopover(event) { - hidePopover(event); - }, - initialize(container) { - window.addEventListener("click", this.showDatePopover); + this.container = container; + window.addEventListener("click", this.showDatePopover, { passive: true }); const siteSettings = container.lookup("service:site-settings"); if (siteSettings.discourse_local_dates_enabled) { - $.fn.applyLocalDates = function () { - deprecated( - "`$.applyLocalDates()` is deprecated, import and use `applyLocalDates()` instead." - ); - - return applyLocalDates(this.toArray(), siteSettings); - }; - withPluginApi("0.8.8", initializeDiscourseLocalDates); } }, diff --git a/plugins/discourse-local-dates/assets/stylesheets/common/discourse-local-dates.scss b/plugins/discourse-local-dates/assets/stylesheets/common/discourse-local-dates.scss index bd21f6f7616..51a65f5eb99 100644 --- a/plugins/discourse-local-dates/assets/stylesheets/common/discourse-local-dates.scss +++ b/plugins/discourse-local-dates/assets/stylesheets/common/discourse-local-dates.scss @@ -23,29 +23,29 @@ } } -div[data-tippy-root] { - .locale-dates-previews { - max-width: 360px; - .preview { - display: flex; - flex-direction: column; - padding: 5px; +.locale-dates-previews { + max-width: 250px; - .timezone { - font-weight: 700; - } + .preview { + display: flex; + flex-direction: column; + padding: 5px; + margin: 0; - &.current { - background: var(--tertiary-low); - } + .timezone { + font-weight: 700; + } + + &.current { + background: var(--tertiary-low); } } +} - .download-calendar { - text-align: right; - cursor: pointer; - margin-top: 0.5em; - } +.download-calendar { + text-align: right; + cursor: pointer; + margin-top: 0.5em; } .discourse-local-dates-create-modal-footer { diff --git a/plugins/discourse-local-dates/spec/system/local_dates_spec.rb b/plugins/discourse-local-dates/spec/system/local_dates_spec.rb index 08a892d7ea7..a00fad8bcad 100644 --- a/plugins/discourse-local-dates/spec/system/local_dates_spec.rb +++ b/plugins/discourse-local-dates/spec/system/local_dates_spec.rb @@ -31,44 +31,46 @@ describe "Local dates", type: :system do expect(topic_page).to have_content(topic.title) - post_dates = topic_page.find_all("span[data-date]") - # Single date in a paragraph. # - post_dates[0].click - tippy_date = topic_page.find(".tippy-content .current .date-time") - - expect(tippy_date).to have_text("#{formatted_date_for_year(12, 15)}\n2:19 PM", exact: true) + find("span[data-date]:nth-of-type(1)").click + expect(page.find("[data-content] .current .date-time")).to have_text( + "#{formatted_date_for_year(12, 15)}\n2:19 PM", + exact: true, + ) + page.send_keys(:escape) # Two single dates in the same paragraph. # - post_dates[1].click - tippy_date = topic_page.find(".tippy-content .current .date-time") + find("span[data-date]:nth-of-type(2)").click + expect(page.find("[data-content] .current .date-time")).to have_text( + "#{formatted_date_for_year(12, 15)}\n1:20 AM", + exact: true, + ) + page.send_keys(:escape) - expect(tippy_date).to have_text("#{formatted_date_for_year(12, 15)}\n1:20 AM", exact: true) - - post_dates[2].click - tippy_date = topic_page.find(".tippy-content .current .date-time") - - expect(tippy_date).to have_text("#{formatted_date_for_year(12, 15)}\n2:40 AM", exact: true) + find("span[data-date]:nth-of-type(3)").click + expect(page.find("[data-content] .current .date-time")).to have_text( + "#{formatted_date_for_year(12, 15)}\n2:40 AM", + exact: true, + ) + page.send_keys(:escape) # Two date ranges in the same paragraph. # - post_dates[3].click - tippy_date = topic_page.find(".tippy-content .current .date-time") - - expect(tippy_date).to have_text( + find("span[data-date]:nth-of-type(4)").click + expect(page.find("[data-content] .current .date-time")).to have_text( "#{formatted_date_for_year(12, 15)}\n11:25 AM → 12:26 AM", exact: true, ) + page.send_keys(:escape) - post_dates[5].click - tippy_date = topic_page.find(".tippy-content .current .date-time") - - expect(tippy_date).to have_text( + find("span[data-date]:nth-of-type(6)").click + expect(page.find("[data-content] .current .date-time")).to have_text( "#{formatted_date_for_year(12, 22)} 11:57 AM → #{formatted_date_for_year(12, 23)} 11:58 AM", exact: true, ) + page.send_keys(:escape) end end diff --git a/plugins/styleguide/assets/javascripts/discourse/components/dummy-component.gjs b/plugins/styleguide/assets/javascripts/discourse/components/dummy-component.gjs new file mode 100644 index 00000000000..cafcff046ca --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/dummy-component.gjs @@ -0,0 +1,7 @@ +import Component from "@glimmer/component"; + +export default class DummyComponent extends Component { + +} diff --git a/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/menus.hbs b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/menus.hbs new file mode 100644 index 00000000000..e50ca8baa02 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/menus.hbs @@ -0,0 +1,103 @@ + + + <:sample> + + {{this.content}} + + + + + + <:sample> + + <:trigger> + {{this.label}} + + <:content> + {{this.content}} + + + + + + + <:sample> + + + <:actions> + Register + + + + + <:sample> + + + <:actions> + Register + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/menus.js b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/menus.js new file mode 100644 index 00000000000..a42ff77af70 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/menus.js @@ -0,0 +1,112 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import { tracked } from "@glimmer/tracking"; +import DummyComponent from "discourse/plugins/styleguide/discourse/components/dummy-component"; +import { htmlSafe } from "@ember/template"; +import { MENU } from "float-kit/lib/constants"; + +export default class Menus extends Component { + @service menu; + + @tracked label = "What is this?"; + @tracked triggers = MENU.options.triggers; + @tracked untriggers = MENU.options.untriggers; + @tracked arrow = MENU.options.arrow; + @tracked inline = MENU.options.inline; + @tracked interactive = MENU.options.interactive; + @tracked maxWidth = MENU.options.maxWidth; + @tracked identifier; + @tracked offset = MENU.options.offset; + @tracked _content = htmlSafe("
    • Hello
    • World!
    "); + + get content() { + return this._content; + } + + set content(value) { + this._content = htmlSafe(value); + } + + get templateCode() { + return ``; + } + + get templateCodeContent() { + return ` + <:trigger> + ${this.label} + + <:content> + ${this.content} + +`; + } + + get serviceCode() { + return `this.menu.register( + document.queryselector(".my-element"), + { content: htmlSafe(${this.content}) } +);`; + } + + get serviceCodeComponent() { + return `this.menu.register( + document.queryselector(".my-element"), + { component: MyComponent, data: { foo: 1 } } +);`; + } + + @action + toggleArrow() { + this.arrow = !this.arrow; + } + + @action + toggleInteractive() { + this.interactive = !this.interactive; + } + + @action + toggleInline() { + this.inline = !this.inline; + } + + @action + registerMenu() { + this.menuInstance?.destroy(); + this.menuInstance = this.menu.register( + document.querySelector("#menu-instance"), + this.options + ); + } + + @action + registerMenuWithComponent() { + this.menuInstanceWithComponent?.destroy(); + this.menuInstanceWithComponent = this.menu.register( + document.querySelector("#menu-instance-with-component"), + { + ...this.options, + component: DummyComponent, + data: { foo: 1 }, + } + ); + } + + get options() { + return { + offset: this.offset, + arrow: this.arrow, + maxWidth: this.maxWidth, + identifier: this.identifier, + interactive: this.interactive, + triggers: this.triggers ?? ["click"], + untriggers: this.untriggers ?? ["click"], + content: this.content, + }; + } +} diff --git a/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/rich-tooltip.hbs b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/rich-tooltip.hbs deleted file mode 100644 index 1c767dabf4f..00000000000 --- a/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/rich-tooltip.hbs +++ /dev/null @@ -1,10 +0,0 @@ - - - {{i18n "styleguide.sections.rich_tooltip.hover_to_see"}} - - -

    {{i18n "styleguide.sections.rich_tooltip.header"}}

    - {{i18n "styleguide.sections.rich_tooltip.description"}} -
    -
    -
    \ No newline at end of file diff --git a/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/toasts.hbs b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/toasts.hbs new file mode 100644 index 00000000000..8664fec2b85 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/toasts.hbs @@ -0,0 +1,93 @@ +{{! template-lint-disable no-potential-path-strings }} + + + <:actions> + + + + + + <:actions> + + + + + + <:actions> + + + + + + <:actions> + + + + + + <:actions> + + + + + + <:actions> + + + + + + + + + {{#if this.autoClose}} + + + + {{/if}} + + + + + Model props for default: + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/toasts.js b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/toasts.js new file mode 100644 index 00000000000..1989cab43f2 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/toasts.js @@ -0,0 +1,70 @@ +import { action } from "@ember/object"; +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { inject as service } from "@ember/service"; +import { TOAST } from "float-kit/lib/constants"; +import DummyComponent from "discourse/plugins/styleguide/discourse/components/dummy-component"; + +export default class Toasts extends Component { + @service toasts; + + @tracked title = "Title"; + @tracked message = "Message"; + @tracked duration = TOAST.options.duration; + @tracked autoClose = TOAST.options.autoClose; + @tracked class; + @tracked action = true; + @tracked icon; + + @action + showCustomComponentToast() { + this.toasts.show({ + duration: this.duration, + autoClose: this.autoClose, + class: this.class, + component: DummyComponent, + data: { + foo: 1, + }, + }); + } + + @action + showToast(theme) { + const actions = []; + + if (this.action) { + actions.push({ + label: "Ok", + class: "btn-primary", + action: (args) => { + // eslint-disable-next-line no-alert + alert("Closing toast:" + args.data.title); + args.close(); + }, + }); + } + + this.toasts[theme]({ + duration: this.duration, + autoClose: this.autoClose, + class: this.class, + data: { + title: this.title, + message: this.message, + icon: this.icon, + actions, + }, + }); + } + + @action + toggleAction() { + this.action = !this.action; + } + + @action + toggleAutoClose() { + this.autoClose = !this.autoClose; + } +} diff --git a/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/tooltips.hbs b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/tooltips.hbs new file mode 100644 index 00000000000..a75f59ea166 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/tooltips.hbs @@ -0,0 +1,95 @@ + + + <:sample> + + + + + + <:sample> + + <:trigger> + {{this.label}} + + <:content> + {{this.content}} + + + + + + + <:sample> + {{this.label}} + + <:actions> + Register + + + + + <:sample> + {{this.label}} + + <:actions> + Register + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/tooltips.js b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/tooltips.js new file mode 100644 index 00000000000..7e8eb1bd878 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/sections/molecules/tooltips.js @@ -0,0 +1,112 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import { tracked } from "@glimmer/tracking"; +import DummyComponent from "discourse/plugins/styleguide/discourse/components/dummy-component"; +import { TOOLTIP } from "float-kit/lib/constants"; +import { htmlSafe } from "@ember/template"; + +export default class Tooltips extends Component { + @service tooltip; + + @tracked label = "What is this?"; + @tracked triggers = TOOLTIP.options.triggers; + @tracked untriggers = TOOLTIP.options.untriggers; + @tracked arrow = TOOLTIP.options.arrow; + @tracked inline = TOOLTIP.options.inline; + @tracked interactive = TOOLTIP.options.interactive; + @tracked maxWidth = TOOLTIP.options.maxWidth; + @tracked identifier; + @tracked offset = TOOLTIP.options.offset; + @tracked _content = "Hello World!"; + + get content() { + return this._content; + } + + set content(value) { + this._content = htmlSafe(value); + } + + get templateCode() { + return ``; + } + + get templateCodeContent() { + return ` + <:trigger> + ${this.label} + + <:content> + ${this.content} + +`; + } + + get serviceCode() { + return `this.tooltip.register( + document.queryselector(".my-element"), + { content: "${this.content}" } +);`; + } + + get serviceCodeComponent() { + return `this.tooltip.register( + document.queryselector(".my-element"), + { component: MyComponent, data: { foo: 1 } } +);`; + } + + @action + toggleArrow() { + this.arrow = !this.arrow; + } + + @action + toggleInteractive() { + this.interactive = !this.interactive; + } + + @action + toggleInline() { + this.inline = !this.inline; + } + + @action + registerTooltip() { + this.tooltipInstance?.destroy(); + this.tooltipInstance = this.tooltip.register( + document.querySelector("#tooltip-instance"), + this.options + ); + } + + @action + registerTooltipWithComponent() { + this.tooltipInstanceWithComponent?.destroy(); + this.tooltipInstanceWithComponent = this.tooltip.register( + document.querySelector("#tooltip-instance-with-component"), + { + ...this.options, + component: DummyComponent, + data: { foo: 1 }, + } + ); + } + + get options() { + return { + offset: this.offset, + arrow: this.arrow, + maxWidth: this.maxWidth, + identifier: this.identifier, + interactive: this.interactive, + triggers: this.triggers, + untriggers: this.untriggers, + content: this.content, + }; + } +} diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.hbs b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.hbs index 87963a352ae..5a7e89d45b0 100644 --- a/plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.hbs +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.hbs @@ -1,3 +1,35 @@ -
    - {{yield}} +
    + {{#if @tag}} + {{@tag}} + {{/if}} + + {{#if (has-block "title")}} +
    + {{yield to="title"}} +
    + {{/if}} + + {{#if (or (has-block) (has-block "sample"))}} +
    + {{#if (has-block)}} + {{yield}} + {{/if}} + + {{#if (has-block "sample")}} + {{yield to="sample"}} + {{/if}} +
    + {{/if}} + + {{#if (has-block "actions")}} +
    + {{yield to="actions"}} +
    + {{/if}} + + {{#if (has-block "code")}} +
    + {{yield to="code"}} +
    + {{/if}}
    \ No newline at end of file diff --git a/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js b/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js index 7239cf85326..30d3c92722d 100644 --- a/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js +++ b/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js @@ -18,7 +18,9 @@ import headerIcons from "../components/sections/molecules/header-icons"; import navigationBar from "../components/sections/molecules/navigation-bar"; import navigationStacked from "../components/sections/molecules/navigation-stacked"; import postMenu from "../components/sections/molecules/post-menu"; -import richTooltip from "../components/sections/molecules/rich-tooltip"; +import tooltips from "../components/sections/molecules/tooltips"; +import menus from "../components/sections/molecules/menus"; +import toasts from "../components/sections/molecules/toasts"; import signupCta from "../components/sections/molecules/signup-cta"; import topicListItem from "../components/sections/molecules/topic-list-item"; import topicNotifications from "../components/sections/molecules/topic-notifications"; @@ -70,7 +72,9 @@ const SECTIONS = [ id: "navigation-stacked", }, { component: postMenu, category: "molecules", id: "post-menu" }, - { component: richTooltip, category: "molecules", id: "rich-tooltip" }, + { component: tooltips, category: "molecules", id: "tooltips" }, + { component: menus, category: "molecules", id: "menus" }, + { component: toasts, category: "molecules", id: "toasts" }, { component: signupCta, category: "molecules", id: "signup-cta" }, { component: topicListItem, category: "molecules", id: "topic-list-item" }, { diff --git a/plugins/styleguide/assets/stylesheets/styleguide.scss b/plugins/styleguide/assets/stylesheets/styleguide.scss index 9d41477e7a9..cc799b631c1 100644 --- a/plugins/styleguide/assets/stylesheets/styleguide.scss +++ b/plugins/styleguide/assets/stylesheets/styleguide.scss @@ -75,12 +75,6 @@ width: 100%; position: relative; - .component { - padding: 2rem; - border: 2px dotted var(--primary-low); - margin-bottom: 2rem; - } - .component-properties { width: 100%; @@ -235,3 +229,42 @@ } } } + +.styleguide__component { + border: 2px dotted var(--primary-low); + margin-bottom: 2rem; + position: relative; + + &-tag { + position: absolute; + top: 0; + right: 0; + padding: 3px 6px; + background: var(--primary-low); + max-width: 25%; + @include ellipsis; + } + + &-sample { + display: flex; + padding: 2rem; + } + + &-actions { + display: flex; + align-items: center; + padding: 1rem 2rem; + } + + &-code { + display: flex; + + .ember-view { + width: 100%; + } + + pre { + margin: 0; + } + } +} diff --git a/plugins/styleguide/config/locales/client.en.yml b/plugins/styleguide/config/locales/client.en.yml index 6a2780abb09..fc227051380 100644 --- a/plugins/styleguide/config/locales/client.en.yml +++ b/plugins/styleguide/config/locales/client.en.yml @@ -16,6 +16,10 @@ en: paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." date_time_inputs: title: "Date/Time inputs" + menus: + title: "Menus" + toasts: + title: "Toasts" font_scale: title: "Font System" colors: @@ -83,12 +87,12 @@ en: title: "Spinners" empty_state: title: "Empty State" - rich_tooltip: - title: "Rich Tooltip" + tooltips: + title: "Tooltips" description: "Description" header: "Header" hover_to_see: "Hover to see a tooltip" char_counter: title: "Character Counter" placeholder: "Enter your text here..." - + diff --git a/spec/system/page_objects/pages/topic.rb b/spec/system/page_objects/pages/topic.rb index 0c463a6bdff..06877050350 100644 --- a/spec/system/page_objects/pages/topic.rb +++ b/spec/system/page_objects/pages/topic.rb @@ -92,12 +92,13 @@ module PageObjects end def click_post_admin_action_button(post, button) - element_klass = ".popup-menu-button" + element_klass = "[data-content][data-identifier='admin-post-menu']" case button when :grant_badge - element_klass += ".grant-badge" + element_klass += " .grant-badge" end - post_by_number(post).find(element_klass).click + + find(element_klass).click end def click_topic_footer_button(button) diff --git a/vendor/assets/svg-icons/discourse-additional.svg b/vendor/assets/svg-icons/discourse-additional.svg index 1954b5e451b..bae0d236ec9 100644 --- a/vendor/assets/svg-icons/discourse-additional.svg +++ b/vendor/assets/svg-icons/discourse-additional.svg @@ -35,10 +35,10 @@ Additional SVG icons - + - - + + diff --git a/yarn.lock b/yarn.lock index a480dd25411..8560bac7466 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,20 +29,20 @@ integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== "@babel/core@^7.18.6", "@babel/core@^7.20.12", "@babel/core@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.15.tgz#15d4fd03f478a459015a4b94cfbb3bd42c48d2f4" - integrity sha512-PtZqMmgRrvj8ruoEOIwVA3yoF91O+Hgw9o7DAUTNBA6Mo2jpu31clx9a7Nz/9JznqetTR6zwfC4L3LAjKQXUwA== + version "7.22.17" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.17.tgz#2f9b0b395985967203514b24ee50f9fd0639c866" + integrity sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.22.13" "@babel/generator" "^7.22.15" "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-module-transforms" "^7.22.15" + "@babel/helper-module-transforms" "^7.22.17" "@babel/helpers" "^7.22.15" - "@babel/parser" "^7.22.15" + "@babel/parser" "^7.22.16" "@babel/template" "^7.22.15" - "@babel/traverse" "^7.22.15" - "@babel/types" "^7.22.15" + "@babel/traverse" "^7.22.17" + "@babel/types" "^7.22.17" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -50,13 +50,13 @@ semver "^6.3.1" "@babel/eslint-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.22.5.tgz#fa032503b9e2d188e25b1b95d29e8b8431042d78" - integrity sha512-C69RWYNYtrgIRE5CmTd77ZiLDXqgBipahJc/jHP3sLcAGj6AJzxNIuKNpVnICqbyK7X3pFUfEvL++rvtbQpZkQ== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.22.15.tgz#263f059c476e29ca4972481a17b8b660cb025a34" + integrity sha512-yc8OOBIQk1EcRrpizuARSQS0TWAcOMpEJ1aafhNznaeYkeL+OhqnDObGFylB8ka8VFF/sZc+S4RzHyO+3LjQxg== dependencies: "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" eslint-visitor-keys "^2.1.0" - semver "^6.3.0" + semver "^6.3.1" "@babel/generator@^7.22.15": version "7.22.15" @@ -86,15 +86,15 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.22.6": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz#c36ea240bb3348f942f08b0fbe28d6d979fab236" - integrity sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ== +"@babel/helper-create-class-features-plugin@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz#97a61b385e57fe458496fad19f8e63b63c867de4" + integrity sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.15" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-replace-supers" "^7.22.9" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" @@ -121,12 +121,12 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-member-expression-to-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" - integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ== +"@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz#b95a144896f6d491ca7863576f820f3628818621" + integrity sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.22.15" "@babel/helper-module-imports@^7.22.15": version "7.22.15" @@ -135,10 +135,10 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-module-transforms@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.15.tgz#40ad2f6950f143900e9c1c72363c0b431a606082" - integrity sha512-l1UiX4UyHSFsYt17iQ3Se5pQQZZHa22zyIXURmvkmLCD4t/aU+dvNWHatKac/D9Vm9UES7nvIqHs4jZqKviUmQ== +"@babel/helper-module-transforms@^7.22.17": + version "7.22.17" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.17.tgz#7edf129097a51ccc12443adbc6320e90eab76693" + integrity sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-module-imports" "^7.22.15" @@ -158,7 +158,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== -"@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": +"@babel/helper-replace-supers@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz#cbdc27d6d8d18cd22c81ae4293765a5d9afd0779" integrity sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg== @@ -221,35 +221,35 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.20.15", "@babel/parser@^7.22.15", "@babel/parser@^7.9.4": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.15.tgz#d34592bfe288a32e741aa0663dbc4829fcd55160" - integrity sha512-RWmQ/sklUN9BvGGpCDgSubhHWfAx24XDTDObup4ffvxaYsptOg2P3KG0j+1eWKLxpkX0j0uHxmpq2Z1SP/VhxA== +"@babel/parser@^7.20.15", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16", "@babel/parser@^7.9.4": + version "7.22.16" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" + integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== "@babel/plugin-proposal-decorators@^7.18.6", "@babel/plugin-proposal-decorators@^7.22.5": - version "7.22.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.22.7.tgz#9b5b73c2e404f0869ef8a8a53765f8203c5467a7" - integrity sha512-omXqPF7Onq4Bb7wHxXjM3jSMSJvUUbvDvmmds7KI5n9Cq6Ln5I05I1W2nRlRof1rGdiUxJrxwe285WF96XlBXQ== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.22.15.tgz#dc774eae73ab8c28a644d490b45aa47a85bb0bf5" + integrity sha512-kc0VvbbUyKelvzcKOSyQUSVVXS5pT3UhRB0e3c9An86MvLqs+gx0dN4asllrDluqSa3m9YyooXKGOFVomnyFkg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.6" + "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/plugin-syntax-decorators" "^7.22.5" + "@babel/plugin-syntax-decorators" "^7.22.10" -"@babel/plugin-syntax-decorators@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.22.5.tgz#329fe2907c73de184033775637dbbc507f09116a" - integrity sha512-avpUOBS7IU6al8MmF1XpAyj9QYeLPuSDJI5D4pVMSMdL7xQokKqJPYQC67RCT0aCTashUXPiGwMJ0DEXXCEmMA== +"@babel/plugin-syntax-decorators@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.22.10.tgz#7d83ea04d893c442b78ebf4c3cbac59a7211deff" + integrity sha512-z1KTVemBjnz+kSEilAsI4lbkPOl5TvJH7YDSY1CTIzvLWJ+KHXp+mRe8VPmfnyvqOPqar1V2gid2PleKzRUstQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/runtime@^7.21.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" - integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" + integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== dependencies: - regenerator-runtime "^0.13.11" + regenerator-runtime "^0.14.0" "@babel/template@^7.22.15", "@babel/template@^7.22.5": version "7.22.15" @@ -260,10 +260,10 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.15.tgz#75be4d2d6e216e880e93017f4e2389aeb77ef2d9" - integrity sha512-DdHPwvJY0sEeN4xJU5uRLmZjgMMDIvMPniLuYzUVXj/GGzysPl0/fwt44JBkyUIzGJPV8QgHMcQdQ34XFuKTYQ== +"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.17": + version "7.22.17" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.17.tgz#b23c203ab3707e3be816043081b4a994fcacec44" + integrity sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg== dependencies: "@babel/code-frame" "^7.22.13" "@babel/generator" "^7.22.15" @@ -271,15 +271,15 @@ "@babel/helper-function-name" "^7.22.5" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" + "@babel/parser" "^7.22.16" + "@babel/types" "^7.22.17" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.22.15", "@babel/types@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.15.tgz#266cb21d2c5fd0b3931e7a91b6dd72d2f617d282" - integrity sha512-X+NLXr0N8XXmN5ZsaQdm9U2SSC3UbIYq/doL++sueHOTisgZHoKaQtZxGuV2cUPQHMfjKEfg/g6oy7Hm6SKFtA== +"@babel/types@^7.22.15", "@babel/types@^7.22.17", "@babel/types@^7.22.5": + version "7.22.17" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.17.tgz#f753352c4610ffddf9c8bc6823f9ff03e2303eee" + integrity sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg== dependencies: "@babel/helper-string-parser" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.15" @@ -417,19 +417,19 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.4.0": - version "4.5.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" - integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== +"@eslint-community/regexpp@^4.6.1": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.0.tgz#11195513186f68d42fbf449f9a7136b2c0c92005" + integrity sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg== -"@eslint/eslintrc@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" - integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== +"@eslint/eslintrc@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.2" + espree "^9.6.0" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -437,10 +437,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.43.0": - version "8.43.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.43.0.tgz#559ca3d9ddbd6bf907ad524320a0d14b85586af0" - integrity sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg== +"@eslint/js@8.49.0": + version "8.49.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333" + integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w== "@fortawesome/fontawesome-free@5.15.4": version "5.15.4" @@ -544,10 +544,10 @@ resolved "https://registry.yarnpkg.com/@highlightjs/cdn-assets/-/cdn-assets-11.8.0.tgz#e3aa9f20bf742b50bd7b1d60a24c8e7d124a602f" integrity sha512-gkfCH4xGBGY9xPaW+t26WpgnfpDhNhB5RtVUDLx3MHkC7ZrmKeIxXsfjzOiuOnEgRk+vydlY6XeOeglh+eVhyg== -"@humanwhocodes/config-array@^0.11.10": - version "0.11.10" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" - integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== +"@humanwhocodes/config-array@^0.11.11": + version "0.11.11" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" + integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" @@ -572,33 +572,28 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@1.4.14": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" - integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + version "0.3.19" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" + integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@jsdoc/salty@^0.2.1": version "0.2.5" @@ -668,10 +663,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@puppeteer/browsers@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-1.6.0.tgz#d52413a7039e40a5ef72fb13fb6505fd87ce842e" - integrity sha512-R2ib8j329427jtKB/qlz0MJbwfJE/6I8ocJLiajsRqJ2PPI8DbjiNzC3lQZeISXEcjOBVhbG2RafN8SlHdcT+A== +"@puppeteer/browsers@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-1.7.0.tgz#714a25ad6963f5478e36004ea7eda254870a4659" + integrity sha512-sl7zI0IkbQGak/+IE3VEEZab5SSOlI5F6558WvzWGC1n3+C722rfewC1ZIkcF9dsoGSsxhsONoseVlNQG4wWvQ== dependencies: debug "4.3.4" extract-zip "2.0.1" @@ -710,9 +705,9 @@ integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== "@types/linkify-it@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" - integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.3.tgz#15a0712296c5041733c79efe233ba17ae5a7587b" + integrity sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g== "@types/markdown-it@^12.2.3": version "12.2.3" @@ -733,9 +728,9 @@ integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/node@*": - version "20.3.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898" - integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw== + version "20.6.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.0.tgz#9d7daa855d33d4efec8aea88cd66db1c2f0ebe16" + integrity sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg== "@types/symlink-or-copy@^1.2.0": version "1.2.0" @@ -759,19 +754,19 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.8.0: - version "8.9.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" - integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== +acorn@^8.9.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== -agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: +agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.0.tgz#536802b76bc0b34aa50195eb2442276d613e3434" integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg== dependencies: debug "^4.3.4" -ajv@^6.10.0, ajv@^6.12.4: +ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -830,6 +825,19 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +arraybuffer.prototype.slice@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" @@ -916,9 +924,9 @@ babel-plugin-ember-modules-api-polyfill@^3.5.0: ember-rfc176-data "^0.3.17" babel-plugin-ember-template-compilation@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-ember-template-compilation/-/babel-plugin-ember-template-compilation-2.1.1.tgz#215c6e983617d514811361a521d61ca4f81450df" - integrity sha512-vwEUw7qfwAgwUokQc5xMxrcJMhCu2dVvDDMIXFyOpXwxt+kqZ2FKvXFV+rJjYchIgHH5rBduEtt4Qk1qeZ6RDA== + version "2.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-ember-template-compilation/-/babel-plugin-ember-template-compilation-2.2.0.tgz#b119fadcd5c831299fbd706420d2ea742848a659" + integrity sha512-1I7f5gf06h5wKdKUvaYEIaoSFur5RLUvTMQG4ak0c5Y11DWUxcoX9hrun1xe9fqfY2dtGFK+ZUM6sn6z8sqK/w== dependencies: "@glimmer/syntax" "^0.84.3" babel-import-util "^2.0.0" @@ -1145,13 +1153,13 @@ broccoli-stew@^3.0.0: walk-sync "^1.1.3" browserslist@^4.21.9: - version "4.21.9" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" - integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== + version "4.21.10" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" + integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== dependencies: - caniuse-lite "^1.0.30001503" - electron-to-chromium "^1.4.431" - node-releases "^2.0.12" + caniuse-lite "^1.0.30001517" + electron-to-chromium "^1.4.477" + node-releases "^2.0.13" update-browserslist-db "^1.0.11" buffer-crc32@~0.2.3: @@ -1187,10 +1195,10 @@ can-symlink@^1.0.0: dependencies: tmp "0.0.28" -caniuse-lite@^1.0.30001503: - version "1.0.30001509" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001509.tgz#2b7ad5265392d6d2de25cd8776d1ab3899570d14" - integrity sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA== +caniuse-lite@^1.0.30001517: + version "1.0.30001532" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001532.tgz#c6a4d5d2da6d2b967f0ee5e12e7f680db6ad2fca" + integrity sha512-FbDFnNat3nMnrROzqrsg314zhqN5LGQ1kyyMk2opcrwGbVGpHRhgCWtAgD5YJUqNAiQ+dklreil/c3Qf1dfCTw== catharsis@^0.9.0: version "0.9.0" @@ -1200,9 +1208,9 @@ catharsis@^0.9.0: lodash "^4.17.15" chai@^4.3.6: - version "4.3.7" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.7.tgz#ec63f6df01829088e8bf55fca839bcd464a8ec51" - integrity sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A== + version "4.3.8" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.8.tgz#40c59718ad6928da6629c70496fe990b2bb5b17c" + integrity sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ== dependencies: assertion-error "^1.1.0" check-error "^1.0.2" @@ -1267,10 +1275,10 @@ chrome-remote-interface@^0.31.3: commander "2.11.x" ws "^7.2.0" -chromium-bidi@0.4.20: - version "0.4.20" - resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.4.20.tgz#1cd56426638452b40b29b7054e83c379e7e2b20a" - integrity sha512-ruHgVZFEv00mAQMz1tQjfjdG63jiPWrQPF6HLlX2ucqLqVTJoWngeBEKHaJ6n1swV/HSvgnBNbtTRIlcVyW3Fw== +chromium-bidi@0.4.22: + version "0.4.22" + resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.4.22.tgz#625dab72946e177f538da2d2b8a681652ef916da" + integrity sha512-wR7Y9Ioez+cNXT4ZP7VNM1HRTljpNnMSLw4/RnwhhZUP4yCU7kIQND00YiktuHekch68jklGPK1q9Jkb29+fQg== dependencies: mitt "3.0.1" @@ -1385,9 +1393,9 @@ convert-source-map@^1.7.0: integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== core-js@^3.27.2, core-js@^3.4.1: - version "3.32.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.1.tgz#a7d8736a3ed9dd05940c3c4ff32c591bb735be77" - integrity sha512-lqufgNn9NLnESg5mQeYsxQP5w7wrViSj0jr/kv6ECQiByzQkrn1MKvV0L3acttpDqfQrHLwr2KCMgX5b8X+lyQ== + version "3.32.2" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.2.tgz#172fb5949ef468f93b4be7841af6ab1f21992db7" + integrity sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ== core-util-is@~1.0.0: version "1.0.3" @@ -1485,10 +1493,10 @@ dequal@^2.0.3: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -devtools-protocol@0.0.1147663: - version "0.0.1147663" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz#4ec5610b39a6250d1f87e6b9c7e16688ed0ac78e" - integrity sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ== +devtools-protocol@0.0.1159816: + version "0.0.1159816" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1159816.tgz#b5848e8597de01e4738589e7553674c7312c8d2a" + integrity sha512-2cZlHxC5IlgkIWe2pSDmCrDiTzbSJWywjbDDnupOImEBcG31CQgBLV8wWE+5t+C4rimcjHsbzy7CBzf9oFjboA== diffhtml@1.0.0-beta.20: version "1.0.0-beta.20" @@ -1540,10 +1548,10 @@ editions@^2.2.0: errlop "^2.0.0" semver "^6.3.0" -electron-to-chromium@^1.4.431: - version "1.4.445" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.445.tgz#058d2c5f3a2981ab1a37440f5a5e42d15672aa6d" - integrity sha512-++DB+9VK8SBJwC+X1zlMfJ1tMA3F0ipi39GdEp+x3cV2TyBihqAgad8cNMWtLDEkbH39nlDQP7PfGrDr3Dr7HA== +electron-to-chromium@^1.4.477: + version "1.4.513" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.513.tgz#41a50bf749aa7d8058ffbf7a131fc3327a7b1675" + integrity sha512-cOB0xcInjm+E5qIssHeXJ29BaUyWpMyFKT5RB3bsLENDheCja0wMkHJyiPl0NBE/VzDI7JDuNEQWhe6RitEUcw== ember-cli-babel-plugin-helpers@^1.1.1: version "1.1.1" @@ -1672,18 +1680,19 @@ errlop@^2.0.0: resolved "https://registry.yarnpkg.com/errlop/-/errlop-2.2.0.tgz#1ff383f8f917ae328bebb802d6ca69666a42d21b" integrity sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw== -es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.21.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" - integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== +es-abstract@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" + integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== dependencies: array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.1" available-typed-arrays "^1.0.5" call-bind "^1.0.2" es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.2.0" + get-intrinsic "^1.2.1" get-symbol-description "^1.0.0" globalthis "^1.0.3" gopd "^1.0.1" @@ -1703,14 +1712,18 @@ es-abstract@^1.19.0, es-abstract@^1.20.4: object-inspect "^1.12.3" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" + regexp.prototype.flags "^1.5.0" + safe-array-concat "^1.0.0" safe-regex-test "^1.0.0" string.prototype.trim "^1.2.7" string.prototype.trimend "^1.0.6" string.prototype.trimstart "^1.0.6" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" typed-array-length "^1.0.4" unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" + which-typed-array "^1.1.10" es-set-tostringtag@^2.0.1: version "2.0.1" @@ -1891,10 +1904,10 @@ eslint-scope@5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" - integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -1923,32 +1936,32 @@ eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" - integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.37.0, eslint@^8.43.0: - version "8.43.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.43.0.tgz#3e8c6066a57097adfd9d390b8fc93075f257a094" - integrity sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q== + version "8.49.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.49.0.tgz#09d80a89bdb4edee2efcf6964623af1054bf6d42" + integrity sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.3" - "@eslint/js" "8.43.0" - "@humanwhocodes/config-array" "^0.11.10" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.2" + "@eslint/js" "8.49.0" + "@humanwhocodes/config-array" "^0.11.11" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" - ajv "^6.10.0" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.2.0" - eslint-visitor-keys "^3.4.1" - espree "^9.5.2" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -1958,7 +1971,6 @@ eslint@^8.37.0, eslint@^8.43.0: globals "^13.19.0" graphemer "^1.4.0" ignore "^5.2.0" - import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" @@ -1968,9 +1980,8 @@ eslint@^8.37.0, eslint@^8.43.0: lodash.merge "^4.6.2" minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" + optionator "^0.9.3" strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" text-table "^0.2.0" esm@^3.2.25: @@ -1978,12 +1989,12 @@ esm@^3.2.25: resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^9.5.2: - version "9.5.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" - integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^8.8.0" + acorn "^8.9.0" acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" @@ -2043,9 +2054,9 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-fifo@^1.1.0, fast-fifo@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.0.tgz#03e381bcbfb29932d7c3afde6e15e83e05ab4d8b" - integrity sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw== + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.1" @@ -2120,14 +2131,15 @@ find-up@^6.3.0: path-exists "^5.0.0" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.1.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.0.tgz#0e54ab4a1a60fe87e2946b6b00657f1c99e1af3f" + integrity sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew== dependencies: - flatted "^3.1.0" + flatted "^3.2.7" + keyv "^4.5.3" rimraf "^3.0.2" -flatted@^3.1.0: +flatted@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== @@ -2221,16 +2233,16 @@ function-bind@^1.1.1: integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" -functions-have-names@^1.2.2, functions-have-names@^1.2.3: +functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== @@ -2255,7 +2267,7 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== @@ -2354,9 +2366,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + version "13.21.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571" + integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg== dependencies: type-fest "^0.20.2" @@ -2488,10 +2500,10 @@ http-proxy-agent@^7.0.0: agent-base "^7.1.0" debug "^4.3.4" -https-proxy-agent@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz#0277e28f13a07d45c663633841e20a40aaafe0ab" - integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ== +https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b" + integrity sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA== dependencies: agent-base "^7.0.2" debug "4" @@ -2513,7 +2525,7 @@ import-cwd@^3.0.0: dependencies: import-from "^3.0.0" -import-fresh@^3.0.0, import-fresh@^3.2.1: +import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -2551,7 +2563,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== -internal-slot@^1.0.3, internal-slot@^1.0.5: +internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== @@ -2607,10 +2619,10 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.12.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" - integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== dependencies: has "^1.0.3" @@ -2712,15 +2724,11 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: has-symbols "^1.0.2" is-typed-array@^1.1.10, is-typed-array@^1.1.9: - version "1.1.10" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" - integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" + which-typed-array "^1.1.11" is-unc-path@^1.0.0: version "1.0.0" @@ -2758,6 +2766,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -2859,6 +2872,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -2890,6 +2908,13 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +keyv@^4.5.3: + version "4.5.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" + integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== + dependencies: + json-buffer "3.0.1" + klaw@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146" @@ -2903,65 +2928,65 @@ language-subtag-registry@^0.3.20: integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== language-tags@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.8.tgz#042b4bdb0d4e771a9f8cc2fdc9bb26a52a367312" - integrity sha512-aWAZwgPLS8hJ20lNPm9HNVs4inexz6S2sQa3wx/+ycuutMNE5/IfYxiWYBbi+9UWCQVaXYCOPUl6gFrPR7+jGg== + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== dependencies: language-subtag-registry "^0.3.20" -lefthook-darwin-arm64@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/lefthook-darwin-arm64/-/lefthook-darwin-arm64-1.4.3.tgz#b253fbc20041815010da66e8588faf4bbd75c0aa" - integrity sha512-ZFsgIzN+Z0c4RpMMHU/M4J43XFyXYGZI8r0GG4jqVMX+prDBIb/6vpgMdZxK5IozRUmnfLGQPXcVokE9WBHSOg== +lefthook-darwin-arm64@1.4.10: + version "1.4.10" + resolved "https://registry.yarnpkg.com/lefthook-darwin-arm64/-/lefthook-darwin-arm64-1.4.10.tgz#841729b16ea2611d0a4bbc5090de8e53912fd805" + integrity sha512-mHYTdvsevxwQLMtgDKM8igaY7JUwlGpoMQMrjLiuMuW9SSv0Bl8gZtilEWcxOiWxGcIUwV4Xw+90tlwKQlO7Qg== -lefthook-darwin-x64@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/lefthook-darwin-x64/-/lefthook-darwin-x64-1.4.3.tgz#5afa63f7a18cf89495895fc1321cdd749825bf9c" - integrity sha512-O6ZesdJ9MStI38gNfJh0ShExIf0KyHG1lR32F9FUFklFwhcquRM+uIDyDqVCxU3UWqmKwcbTk9AJWMjQZGPLxQ== +lefthook-darwin-x64@1.4.10: + version "1.4.10" + resolved "https://registry.yarnpkg.com/lefthook-darwin-x64/-/lefthook-darwin-x64-1.4.10.tgz#63d34cbd72222b75c8123aa90d8ff8a65636bee6" + integrity sha512-rfa9AClAS5gXix0eyv0vV70r7s4jt7dWlR6TSky+rRcxWJ88Vay9U1ZpHlVTAh0karaiHrh6e7AQX+7WbGWUBA== -lefthook-freebsd-arm64@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/lefthook-freebsd-arm64/-/lefthook-freebsd-arm64-1.4.3.tgz#070141ccc981658bd7545f40b14814b410f2930e" - integrity sha512-6swb+98Qs6P9V9Rcd2lHWH2LunFEk+kfIPpf6oiPrOHnw3OkfFhQLmawX425Ari7Y9qy9gfDoNe/0/IR7YGmGw== +lefthook-freebsd-arm64@1.4.10: + version "1.4.10" + resolved "https://registry.yarnpkg.com/lefthook-freebsd-arm64/-/lefthook-freebsd-arm64-1.4.10.tgz#7eca602866cc39d4b4345e08683437e35735c013" + integrity sha512-Nh4jEg1OQwsQ+i/kt9XIufQONVj6iEhGWwWoyfycT71wHFCrbNy348Q84EjHTltIUAvE/5jrQdnRDOL6pMZ/Tw== -lefthook-freebsd-x64@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/lefthook-freebsd-x64/-/lefthook-freebsd-x64-1.4.3.tgz#47b4e7cf4c0459b1152e605c047541ec3eec5f0b" - integrity sha512-+58NARATypmIj0kDutd29ozywr6IeA0lVBxsVzq7C5ycYrd31iNak3lnaEvNJvdj5fGVNEL9X7XRojylthZlAg== +lefthook-freebsd-x64@1.4.10: + version "1.4.10" + resolved "https://registry.yarnpkg.com/lefthook-freebsd-x64/-/lefthook-freebsd-x64-1.4.10.tgz#00b9f8d42f356cca230f927c279f8fe4b8defbec" + integrity sha512-3DiI2asrtiYvGXLb6H1ATj7yEj14B6A4c33HLmN5J94PP6rVw6xU2G/PIIAz2wi7/W3igEmuT79F8JJT1mqc7A== -lefthook-linux-arm64@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/lefthook-linux-arm64/-/lefthook-linux-arm64-1.4.3.tgz#b3caae33a20cc2665ec047a098e7b4e4498d63d1" - integrity sha512-gAWJQEhgheOfPu579fhlcNNm3KoJbNkT11AATKHlFu+esyiCxI8xZVpGwDQVMphO7s43FKbkQJSvIM4tElb6FQ== +lefthook-linux-arm64@1.4.10: + version "1.4.10" + resolved "https://registry.yarnpkg.com/lefthook-linux-arm64/-/lefthook-linux-arm64-1.4.10.tgz#8eed8cb53d63326118ff65cd4667dc3a9cb8ac43" + integrity sha512-gXwTaEYYLfIIPX2Z63aijPSqmvZf003OKjhBB1Og74qWDxfhKs4zZXoWEdc1BARq6U9hImKdyOD/zDc31uB4mw== -lefthook-linux-x64@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/lefthook-linux-x64/-/lefthook-linux-x64-1.4.3.tgz#ac2990e23a4c39bf774e4094f33c0fbaef79129c" - integrity sha512-mVv/amRqX81nQcizhRRx/X6KKNIkMUG4HdDItWkDazwLAviZ2zv8TRcSaEYBOpriP/dBZm8gt6EhzPpfoWtcJw== +lefthook-linux-x64@1.4.10: + version "1.4.10" + resolved "https://registry.yarnpkg.com/lefthook-linux-x64/-/lefthook-linux-x64-1.4.10.tgz#f2f2480002b5f33f4fe9e500fec2a48ee9734d85" + integrity sha512-svWdDgk2hXpcE37yY27DTbyIgnJuqofrNc30cmHonLO3VQC5CyLwp45CiNtY6qxW0UVBxqSuGuZcB2TPZQfyWQ== -lefthook-windows-arm64@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/lefthook-windows-arm64/-/lefthook-windows-arm64-1.4.3.tgz#dc0dcfea32badab031183bdc8cd91bda929cee9b" - integrity sha512-ccClPTC7AvhcbyT6pLzdV8K8e/P0T7p/THGRtcyjkKIU0IS89k95VazDlt22QPzUTc8UMNCQyZ1XY4UDx295jw== +lefthook-windows-arm64@1.4.10: + version "1.4.10" + resolved "https://registry.yarnpkg.com/lefthook-windows-arm64/-/lefthook-windows-arm64-1.4.10.tgz#1711146b4d33bcf5a5e8af098ecb874f1a67af22" + integrity sha512-ntP30mgAdcbDP1fqL6CXn/60tLXVVYQo0aDLLcI/oBI2ff1Im6zeBDVtujv33KdDVrx5Mu9qi7bQstgG02PzvA== -lefthook-windows-x64@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/lefthook-windows-x64/-/lefthook-windows-x64-1.4.3.tgz#795b1215969c7d0d4007f89bfe660cfc2736075c" - integrity sha512-q1K5kycfqTYLm5/pOCiAFa+hoSFt/29QjHVZAWRmk/nKDIf8MvTWX0tdaEx7/VJuG3cgQT1jM+xiTwSmNUXTKg== +lefthook-windows-x64@1.4.10: + version "1.4.10" + resolved "https://registry.yarnpkg.com/lefthook-windows-x64/-/lefthook-windows-x64-1.4.10.tgz#2fbb8cc56384f5145c70f27ea85a59e214a52a15" + integrity sha512-MwEGtGpe6EJPfE5jvtxvHsmn2/sQP7+zo2FUf87o2CafmqAOR/R7EvQ7qeg6z6T33cWxnyQSBN23tV10bKhtOQ== lefthook@^1.2.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/lefthook/-/lefthook-1.4.3.tgz#cedfd13f13c87a974ea5e055853c7fae0c6df11e" - integrity sha512-zvhAJ9wDQW7F27XYWRfx72L6LuPLu49be+sRUF+DKku1IGT+x3eHjQ9k70pt65lnEq1X8Xl/Xygm3Kwi/piOYg== + version "1.4.10" + resolved "https://registry.yarnpkg.com/lefthook/-/lefthook-1.4.10.tgz#2c79ae13c4af93dc9d332c9c2c5620c38c67cd74" + integrity sha512-d0XRT7LoHdPYKNdCQNcIx4arFzTb0fro/PQrwJ1qSLBVlN9ljaYszLqsrVMzTNSPjGU1Pb1FbSw1GMxLG9G8kA== optionalDependencies: - lefthook-darwin-arm64 "1.4.3" - lefthook-darwin-x64 "1.4.3" - lefthook-freebsd-arm64 "1.4.3" - lefthook-freebsd-x64 "1.4.3" - lefthook-linux-arm64 "1.4.3" - lefthook-linux-x64 "1.4.3" - lefthook-windows-arm64 "1.4.3" - lefthook-windows-x64 "1.4.3" + lefthook-darwin-arm64 "1.4.10" + lefthook-darwin-x64 "1.4.10" + lefthook-freebsd-arm64 "1.4.10" + lefthook-freebsd-x64 "1.4.10" + lefthook-linux-arm64 "1.4.10" + lefthook-linux-x64 "1.4.10" + lefthook-windows-arm64 "1.4.10" + lefthook-windows-x64 "1.4.10" levn@^0.4.1: version "0.4.1" @@ -3106,11 +3131,11 @@ magic-string@^0.25.7: sourcemap-codec "^1.4.8" magic-string@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.0.tgz#fd58a4748c5c4547338a424e90fa5dd17f4de529" - integrity sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ== + version "0.30.3" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.3.tgz#403755dfd9d6b398dfa40635d52e96c5ac095b85" + integrity sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw== dependencies: - "@jridgewell/sourcemap-codec" "^1.4.13" + "@jridgewell/sourcemap-codec" "^1.4.15" magnific-popup@1.1.0: version "1.1.0" @@ -3274,16 +3299,16 @@ no-case@^3.0.4: tslib "^2.0.3" node-fetch@^2.6.0, node-fetch@^2.6.12: - version "2.6.12" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" - integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" -node-releases@^2.0.12: - version "2.0.12" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" - integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== +node-releases@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== object-assign@^4.1.0: version "4.1.1" @@ -3324,7 +3349,7 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -optionator@^0.9.1: +optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== @@ -3392,18 +3417,18 @@ p-locate@^6.0.0: p-limit "^4.0.0" pac-proxy-agent@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.0.tgz#db42120c64292685dafaf2bd921e223c56bfb13b" - integrity sha512-t4tRAMx0uphnZrio0S0Jw9zg3oDbz1zVhQ/Vy18FjLfP1XOLNUEjaVxYCYRI6NS+BsMBXKIzV6cTLOkO9AtywA== + version "7.0.1" + resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz#6b9ddc002ec3ff0ba5fdf4a8a21d363bcc612d75" + integrity sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A== dependencies: "@tootallnate/quickjs-emscripten" "^0.23.0" agent-base "^7.0.2" debug "^4.3.4" get-uri "^6.0.1" http-proxy-agent "^7.0.0" - https-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.2" pac-resolver "^7.0.0" - socks-proxy-agent "^8.0.1" + socks-proxy-agent "^8.0.2" pac-resolver@^7.0.0: version "7.0.0" @@ -3593,15 +3618,15 @@ punycode@^2.1.0: integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== puppeteer-core@^21.0.3: - version "21.0.3" - resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-21.0.3.tgz#201bfbf18a9467dbedb10c3c2c9c43462bb9bb84" - integrity sha512-AGvopfkA0jLbW5Ba0m6kBuvRIpLo76PXUK3zJYkXOr9NI1LknJESyai6TtXc6GUSewMkinmyEDx1pFgq900hqg== + version "21.1.1" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-21.1.1.tgz#59be20b6f69acc2139ba2d9e02a33793b59254ff" + integrity sha512-Tlcajcf44zwfa9Sbwv3T8BtaNMJ69wtpHIxwl2NOBTyTK3D1wppQovXTjfw0TDOm3a16eCfQ+5BMi3vRQ4kuAQ== dependencies: - "@puppeteer/browsers" "1.6.0" - chromium-bidi "0.4.20" + "@puppeteer/browsers" "1.7.0" + chromium-bidi "0.4.22" cross-fetch "4.0.0" debug "4.3.4" - devtools-protocol "0.0.1147663" + devtools-protocol "0.0.1159816" ws "8.13.0" queue-microtask@^1.2.2: @@ -3645,12 +3670,12 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== -regexp.prototype.flags@^1.4.3: +regexp.prototype.flags@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== @@ -3718,11 +3743,11 @@ resolve-package-path@^3.1.0: resolve "^1.17.0" resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.17.0, resolve@^1.22.3: - version "1.22.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.3.tgz#4b4055349ffb962600972da1fdc33c46a4eb3283" - integrity sha512-P8ur/gp/AmbEzjr729bZnLjXK5Z+4P0zhIJgBgzqRih7hL7BOukHGtSTA3ACMY467GRFz3duQsi0bDZdR7DKdw== + version "1.22.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" + integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== dependencies: - is-core-module "^2.12.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -3794,6 +3819,16 @@ rxjs@^7.8.1: dependencies: tslib "^2.1.0" +safe-array-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -3891,12 +3926,12 @@ snake-case@^3.0.3, snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" -socks-proxy-agent@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz#ffc5859a66dac89b0c4dab90253b96705f3e7120" - integrity sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ== +socks-proxy-agent@^8.0.1, socks-proxy-agent@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz#5acbd7be7baf18c46a3f293a840109a430a640ad" + integrity sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g== dependencies: - agent-base "^7.0.1" + agent-base "^7.0.2" debug "^4.3.4" socks "^2.7.1" @@ -3962,45 +3997,45 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: strip-ansi "^6.0.1" string.prototype.matchall@^4.0.5, string.prototype.matchall@^4.0.6: - version "4.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3" - integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg== + version "4.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.9.tgz#148779de0f75d36b13b15885fec5cadde994520d" + integrity sha512-6i5hL3MqG/K2G43mWXWgP+qizFW/QH/7kCNN13JrJS5q48FN5IKksLDscexKP3dnmB6cdm9jlNgAsWNLpSykmA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" has-symbols "^1.0.3" - internal-slot "^1.0.3" - regexp.prototype.flags "^1.4.3" + internal-slot "^1.0.5" + regexp.prototype.flags "^1.5.0" side-channel "^1.0.4" string.prototype.trim@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" - integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" string_decoder@^1.1.1: version "1.3.0" @@ -4238,6 +4273,36 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" @@ -4248,9 +4313,9 @@ typed-array-length@^1.0.4: is-typed-array "^1.1.9" typescript@^5.1.3: - version "5.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" @@ -4354,9 +4419,9 @@ uuid@^8.3.2: integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + version "2.4.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" + integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== validate-peer-dependencies@^1.1.0: version "1.2.0" @@ -4474,17 +4539,16 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-typed-array@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" - integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== +which-typed-array@^1.1.10, which-typed-array@^1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" + integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== dependencies: available-typed-arrays "^1.0.5" call-bind "^1.0.2" for-each "^0.3.3" gopd "^1.0.1" has-tostringtag "^1.0.0" - is-typed-array "^1.1.10" which@^2.0.1: version "2.0.2" @@ -4494,9 +4558,9 @@ which@^2.0.1: isexe "^2.0.0" workerpool@^6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462" - integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== + version "6.4.2" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.2.tgz#5d086f6fef89adbc4300ca24fcafb7082330e960" + integrity sha512-MrDWwemtC4xNV22kbbZDQQQmxNX+yLm790sgYl2wVD3CWnK7LJY1youI/11wHorAjHjK+GEjUxUh74XoPU71uQ== wrap-ansi@^7.0.0: version "7.0.0"