DEV: replaces topic-notifications-options by DMenu (#30298)

This commit introduces <NotificationsTracking /> which is a wrapper component around <DMenu /> which replaces the select-kit component <TopicNotificationsButton />.

Each tracking case has its dedicated component:

- topic -> `<TopicNotificationsTracking />`
- group -> `<GroupNotificationsTracking />`
- tag -> `<TagNotificationsTracking />`
- category -> `<CategoryNotificationsTracking />`
- chat thread -> `<ThreadNotificationsTracking />`
This commit is contained in:
Joffrey JAFFEUX 2024-12-16 19:59:18 +01:00 committed by GitHub
parent 062e4fb4f3
commit 41df705188
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 565 additions and 289 deletions

View File

@ -0,0 +1,16 @@
import NotificationsTracking from "discourse/components/notifications-tracking";
import { i18n } from "discourse-i18n";
const CategoryNotificationsTracking = <template>
<NotificationsTracking
@onChange={{@onChange}}
@levelId={{@levelId}}
@showCaret={{@showCaret}}
@showFullTitle={{@showFullTitle}}
@prefix="category.notifications"
@title={{i18n "category.notifications.title"}}
class="category-notifications-tracking"
/>
</template>;
export default CategoryNotificationsTracking;

View File

@ -108,6 +108,10 @@ export default class DModal extends Component {
}
}
get autofocus() {
return this.args.autofocus ?? true;
}
shouldTriggerClickOnEnter(event) {
if (this.args.submitOnEnter === false) {
return false;
@ -277,7 +281,7 @@ export default class DModal extends Component {
...attributes
{{didInsert this.setupModal}}
{{willDestroy this.cleanupModal}}
{{trapTab preventScroll=false}}
{{trapTab preventScroll=false autofocus=this.autofocus}}
>
<div class="d-modal__container" {{this.registerModalContainer}}>
{{yield to="aboveHeader"}}

View File

@ -119,11 +119,14 @@
{{#unless this.tag}}
{{! don't show category notification menu on tag pages }}
{{#if this.showCategoryNotifications}}
<CategoryNotificationsButton
@value={{this.categoryNotificationLevel}}
@category={{this.category}}
@onChange={{action "changeCategoryNotificationLevel"}}
/>
{{#unless this.category.deleted}}
<CategoryNotificationsTracking
@levelId={{this.categoryNotificationLevel}}
@showFullTitle={{false}}
@showCaret={{false}}
@onChange={{this.changeCategoryNotificationLevel}}
/>
{{/unless}}
{{/if}}
{{/unless}}
{{/if}}
@ -132,9 +135,9 @@
{{#unless this.category}}
{{! don't show tag notification menu on category pages }}
{{#if this.showTagNotifications}}
<TagNotificationsButton
<TagNotificationsTracking
@onChange={{this.changeTagNotificationLevel}}
@value={{this.tagNotification.notification_level}}
@levelId={{this.tagNotification.notification_level}}
/>
{{/if}}
{{/unless}}

View File

@ -0,0 +1,14 @@
import NotificationsTracking from "discourse/components/notifications-tracking";
const GroupNotificationsTracking = <template>
<NotificationsTracking
@onChange={{@onChange}}
@levelId={{@levelId}}
@showCaret={{false}}
@showFullTitle={{false}}
@prefix="groups.notifications"
class="group-notifications-tracking"
/>
</template>;
export default GroupNotificationsTracking;

View File

@ -0,0 +1,160 @@
import Component from "@glimmer/component";
import { fn, hash } from "@ember/helper";
import { action } from "@ember/object";
import DButton from "discourse/components/d-button";
import DropdownMenu from "discourse/components/dropdown-menu";
import PluginOutlet from "discourse/components/plugin-outlet";
import concatClass from "discourse/helpers/concat-class";
import { allLevels, buttonDetails } from "discourse/lib/notification-levels";
import icon from "discourse-common/helpers/d-icon";
import { i18n } from "discourse-i18n";
import DMenu from "float-kit/components/d-menu";
function constructKey(prefix, level, suffix, key) {
let string = prefix + "." + level;
if (suffix) {
string += suffix;
}
return i18n(string + "." + key);
}
class NotificationsTrackingTrigger extends Component {
get showFullTitle() {
return this.args.showFullTitle ?? true;
}
get showCaret() {
return this.args.showCaret ?? true;
}
get title() {
return constructKey(
this.args.prefix,
this.args.selectedLevel.key,
this.args.suffix,
"title"
);
}
<template>
{{icon @selectedLevel.icon}}
{{#if this.showFullTitle}}
<span class="d-button-label">
{{this.title}}
</span>
{{/if}}
{{#if this.showCaret}}
{{icon "angle-down"}}
{{/if}}
</template>
}
export default class NotificationsTracking extends Component {
@action
registerDmenuApi(api) {
this.dmenuApi = api;
}
@action
async setNotificationLevel(level) {
await this.dmenuApi.close();
this.args.onChange?.(level);
}
@action
description(level) {
return constructKey(
this.args.prefix,
level.key,
this.args.suffix,
"description"
);
}
@action
label(level) {
return constructKey(this.args.prefix, level.key, this.args.suffix, "title");
}
@action
isSelectedClass(level) {
return this.args.levelId === level.id ? "-selected" : "";
}
get selectedLevel() {
return buttonDetails(this.args.levelId);
}
get levels() {
return this.args.levels ?? allLevels;
}
<template>
<DMenu
@identifier="notifications-tracking"
@modalForMobile={{true}}
@triggerClass={{concatClass
"notifications-tracking-trigger-btn"
@triggerClass
}}
@onRegisterApi={{this.registerDmenuApi}}
@title={{@title}}
@autofocus={{false}}
data-level-id={{this.selectedLevel.id}}
data-level-name={{this.selectedLevel.key}}
...attributes
>
<:trigger>
<NotificationsTrackingTrigger
@showFullTitle={{@showFullTitle}}
@showCaret={{@showCaret}}
@selectedLevel={{this.selectedLevel}}
@suffix={{@suffix}}
@prefix={{@prefix}}
/>
</:trigger>
<:content>
<DropdownMenu as |dropdown|>
{{#each this.levels as |level|}}
<dropdown.item>
<DButton
class={{concatClass
"notifications-tracking-btn"
(this.isSelectedClass level)
}}
@action={{fn this.setNotificationLevel level.id}}
data-level-id={{level.id}}
data-level-name={{level.key}}
>
<div class="notifications-tracking-btn__icons">
<PluginOutlet
@name="notifications-tracking-icons"
@outletArgs={{hash
selectedLevelId=@levelId
level=level
topic=@topic
}}
>
{{icon level.icon}}
</PluginOutlet>
</div>
<div class="notifications-tracking-btn__texts">
<span class="notifications-tracking-btn__label">
{{this.label level}}
</span>
<span class="notifications-tracking-btn__description">
{{this.description level}}
</span>
</div>
</DButton>
</dropdown.item>
{{/each}}
</DropdownMenu>
</:content>
</DMenu>
</template>
}

View File

@ -0,0 +1,14 @@
import NotificationsTracking from "discourse/components/notifications-tracking";
const TagNotificationsTracking = <template>
<NotificationsTracking
@onChange={{@onChange}}
@levelId={{@levelId}}
@showCaret={{false}}
@showFullTitle={{false}}
@prefix="tagging.notifications"
class="tag-notifications-tracking"
/>
</template>;
export default TagNotificationsTracking;

View File

@ -0,0 +1,24 @@
import Component from "@glimmer/component";
import NotificationsTracking from "discourse/components/notifications-tracking";
import { topicLevels } from "discourse/lib/notification-levels";
import { i18n } from "discourse-i18n";
export default class TopicNotificationsTracking extends Component {
get suffix() {
return this.args.topic?.archetype === "private_message" ? "_pm" : "";
}
<template>
<NotificationsTracking
@onChange={{@onChange}}
@levelId={{@levelId}}
@showCaret={{@showCaret}}
@showFullTitle={{@showFullTitle}}
@prefix="topic.notifications"
@title={{i18n "topic.notifications.title"}}
class="topic-notifications-tracking"
@levels={{topicLevels}}
@suffix={{this.suffix}}
/>
</template>
}

View File

@ -40,8 +40,8 @@
</UserNav::MessagesSecondaryNav>
{{#in-element this.navigationControlsButton}}
<GroupNotificationsButton
@value={{this.group.group_user.notification_level}}
<GroupNotificationsTracking
@levelId={{this.group.group_user.notification_level}}
@onChange={{this.changeGroupNotificationLevel}}
/>
{{/in-element}}

View File

@ -117,7 +117,7 @@ acceptance("Tags", function (needs) {
test("hide tag notifications menu", async function (assert) {
await visit("/tags/c/faq/4/test");
assert.dom(".tag-notifications-button").doesNotExist();
assert.dom(".tag-notifications-tracking").doesNotExist();
});
});

View File

@ -1,7 +1,7 @@
import { visit } from "@ember/test-helpers";
import { test } from "qunit";
import notificationsTracking from "discourse/tests/helpers/notifications-tracking-helper";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import selectKit from "discourse/tests/helpers/select-kit-helper";
acceptance("Topic Notifications button", function (needs) {
needs.user();
@ -13,24 +13,15 @@ acceptance("Topic Notifications button", function (needs) {
});
test("Updating topic notification level", async function (assert) {
const notificationOptions = selectKit(
"#topic-footer-buttons .topic-notifications-options"
);
await visit("/t/internationalization-localization/280");
assert.true(
notificationOptions.exists(),
"displays the notification options button in the topic's footer"
);
await notificationsTracking().selectLevelId(3);
await notificationOptions.expand();
await notificationOptions.selectRowByValue("3");
assert.strictEqual(
notificationOptions.header().label(),
"Watching",
"displays the right notification level"
);
assert
.notificationsTracking()
.hasSelectedLevelName(
"watching",
"displays the right notification level"
);
});
});

View File

@ -592,7 +592,7 @@ acceptance(
.exists({ count: 2 }, "displays the right topic list");
assert
.dom(".group-notifications-button")
.dom(".group-notifications-tracking")
.exists("displays the group notifications button");
});

View File

@ -0,0 +1,31 @@
import QUnit from "qunit";
import { query } from "discourse/tests/helpers/qunit-helpers";
class NotificationsTracking {
constructor(selector, context) {
this.context = context;
if (selector instanceof HTMLElement) {
this.element = selector;
} else {
this.element = query(selector);
}
}
hasSelectedLevelName(name, message) {
this.context
.dom(this.element)
.hasAttribute("data-level-name", name, message);
}
hasSelectedLevelId(id, message) {
this.context.dom(this.element).hasAttribute("data-level-id", name, message);
}
}
export function setupNotificationsTrackingAssertions() {
QUnit.assert.notificationsTracking = function (
selector = ".notifications-tracking-trigger"
) {
return new NotificationsTracking(selector, this);
};
}

View File

@ -0,0 +1,31 @@
import { click } from "@ember/test-helpers";
import { query } from "discourse/tests/helpers/qunit-helpers";
class Notificationstracking {
constructor(selector) {
if (selector instanceof HTMLElement) {
this.element = selector;
} else {
this.element = query(selector);
}
}
async selectLevelId(levelId) {
await click(this.element);
const content = this.content();
await click(content.querySelector(`[data-level-id="${levelId}"]`));
}
content() {
const identifier = this.element.dataset.identifier;
return document.querySelector(
`[data-content][data-identifier="${identifier}"]`
);
}
}
export default function notificationsTracking(
selector = ".notifications-tracking-trigger"
) {
return new Notificationstracking(selector);
}

View File

@ -105,6 +105,7 @@ import I18n from "discourse-i18n";
import { _clearSnapshots } from "select-kit/components/composer-actions";
import { setupDSelectAssertions } from "./d-select-assertions";
import { setupFormKitAssertions } from "./form-kit-assertions";
import { setupNotificationsTrackingAssertions } from "./notifications-tracking-assertions";
import { cleanupTemporaryModuleRegistrations } from "./temporary-module-helper";
export function currentUser() {
@ -485,6 +486,7 @@ QUnit.assert.containsInstance = function (collection, klass, message) {
setupFormKitAssertions();
setupDSelectAssertions();
setupNotificationsTrackingAssertions();
export async function selectDate(selector, date) {
const elem = document.querySelector(selector);

View File

@ -0,0 +1,81 @@
import { hash } from "@ember/helper";
import { click, render } from "@ember/test-helpers";
import { module, test } from "qunit";
import TopicNotificationsTracking from "discourse/components/topic-notifications-tracking";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { i18n } from "discourse-i18n";
function extractDescriptions(rows) {
return [...rows].map((el) =>
el
.querySelector(".notifications-tracking-btn__description")
.textContent.trim()
);
}
function getTranslations(type = "") {
return ["watching", "tracking", "regular", "muted"].map((key) => {
return i18n(`topic.notifications.${key}${type}.description`);
});
}
module("Integration | Component | TopicTracking", function (hooks) {
setupRenderingTest(hooks);
test("regular topic notification level descriptions", async function (assert) {
await render(<template>
<TopicNotificationsTracking @levelId={{1}} />
</template>);
await click(".notifications-tracking-trigger");
const uiTexts = extractDescriptions(
document.querySelectorAll(".notifications-tracking-btn")
);
const descriptions = getTranslations();
assert.strictEqual(
uiTexts.length,
descriptions.length,
"has the correct copy"
);
uiTexts.forEach((text, index) => {
assert.strictEqual(
text.trim(),
descriptions[index].trim(),
"has the correct copy"
);
});
});
test("PM topic notification level descriptions", async function (assert) {
await render(<template>
<TopicNotificationsTracking
@levelId={{1}}
@topic={{hash archetype="private_message"}}
/>
</template>);
await click(".notifications-tracking-trigger");
const uiTexts = extractDescriptions(
document.querySelectorAll(".notifications-tracking-btn")
);
const descriptions = getTranslations("_pm");
assert.strictEqual(
uiTexts.length,
descriptions.length,
"has the correct copy"
);
uiTexts.forEach((text, index) => {
assert.strictEqual(
text.trim(),
descriptions[index].trim(),
"has the correct copy"
);
});
});
});

View File

@ -3,7 +3,6 @@ import { getOwner } from "@ember/owner";
import { render, settled } from "@ember/test-helpers";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import I18n, { i18n } from "discourse-i18n";
import TopicNotificationsButton from "select-kit/components/topic-notifications-button";
@ -50,20 +49,16 @@ module(
<TopicNotificationsButton @topic={{state.topic}} @expanded={{true}} />
</template>);
assert.strictEqual(
selectKit().header().label(),
"Normal",
"has the correct label"
);
assert
.dom(".notifications-tracking-trigger")
.hasText("Normal", "has the correct label");
state.topic = buildTopic.call(this, { level: 2 });
await settled();
assert.strictEqual(
selectKit().header().label(),
"Tracking",
"correctly changes the label"
);
assert
.dom(".notifications-tracking-trigger")
.hasText("Tracking", "has the correct label");
});
test("the header has a localized title", async function (assert) {
@ -77,11 +72,9 @@ module(
<TopicNotificationsButton @topic={{topic}} @expanded={{true}} />
</template>);
assert.strictEqual(
selectKit().header().label(),
`${originalTranslation} PM`,
"has the correct label for PMs"
);
assert
.dom(".notifications-tracking-trigger")
.hasText(`${originalTranslation} PM`, "has the correct label for PMs");
});
test("notification reason text - user mailing list mode", async function (assert) {

View File

@ -1,100 +0,0 @@
import { getOwner } from "@ember/owner";
import { render } from "@ember/test-helpers";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { i18n } from "discourse-i18n";
import TopicNotificationsOptions from "select-kit/components/topic-notifications-options";
function extractDescriptions(rows) {
return [...rows].map((el) => el.querySelector(".desc").textContent.trim());
}
function getTranslations(type = "") {
return ["watching", "tracking", "regular", "muted"].map((key) => {
return i18n(`topic.notifications.${key}${type}.description`);
});
}
module(
"Integration | Component | select-kit/topic-notifications-options",
function (hooks) {
setupRenderingTest(hooks);
test("regular topic notification level descriptions", async function (assert) {
const store = getOwner(this).lookup("service:store");
const topic = store.createRecord("topic", {
id: 4563,
title: "Qunit Test Topic",
archetype: "regular",
details: {
notification_level: 1,
},
});
await render(<template>
<TopicNotificationsOptions
@value={{topic.details.notification_level}}
@topic={{topic}}
/>
</template>);
await selectKit().expand();
const uiTexts = extractDescriptions(selectKit().rows());
const descriptions = getTranslations();
assert.strictEqual(
uiTexts.length,
descriptions.length,
"has the correct copy"
);
uiTexts.forEach((text, index) => {
assert.strictEqual(
text.trim(),
descriptions[index].trim(),
"has the correct copy"
);
});
});
test("PM topic notification level descriptions", async function (assert) {
const store = getOwner(this).lookup("service:store");
const topic = store.createRecord("topic", {
id: 4563,
title: "Qunit Test Topic",
archetype: "private_message",
details: {
notification_level: 1,
},
});
await render(<template>
<TopicNotificationsOptions
@value={{topic.details.notification_level}}
@topic={{topic}}
/>
</template>);
await selectKit().expand();
const uiTexts = extractDescriptions(selectKit().rows());
const descriptions = getTranslations("_pm");
assert.strictEqual(
uiTexts.length,
descriptions.length,
"has the correct copy"
);
uiTexts.forEach((text, index) => {
assert.strictEqual(
text.trim(),
descriptions[index].trim(),
"has the correct copy"
);
});
});
}
);

View File

@ -111,6 +111,7 @@ export default class DMenu extends Component {
<DModal
@closeModal={{this.menuInstance.close}}
@hideHeader={{true}}
@autofocus={{this.options.autofocus}}
class={{concatClass
"fk-d-menu-modal"
(concat this.options.identifier "-content")

View File

@ -15,7 +15,7 @@ export default class FloatKitCloseOnEscape extends Modifier {
this.closeFn = closeFn;
this.element = element;
document.addEventListener("keydown", this.check);
document.addEventListener("keydown", this.check, { capture: true });
}
@bind
@ -28,6 +28,6 @@ export default class FloatKitCloseOnEscape extends Modifier {
}
cleanup() {
document.removeEventListener("keydown", this.check);
document.removeEventListener("keydown", this.check, { capture: true });
}
}

View File

@ -1,16 +0,0 @@
import { readOnly } from "@ember/object/computed";
import { classNames } from "@ember-decorators/component";
import { i18n } from "discourse-i18n";
import NotificationOptionsComponent from "select-kit/components/notifications-button";
import { pluginApiIdentifiers, selectKitOptions } from "./select-kit";
@selectKitOptions({
i18nPrefix: "category.notifications",
showFullTitle: false,
headerAriaLabel: i18n("category.notifications.title"),
})
@pluginApiIdentifiers(["category-notifications-button"])
@classNames("category-notifications-button")
export default class CategoryNotificationsButton extends NotificationOptionsComponent {
@readOnly("category.deleted") isHidden;
}

View File

@ -1,14 +1,13 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { hash } from "@ember/helper";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import { isEmpty } from "@ember/utils";
import TopicNotificationsTracking from "discourse/components/topic-notifications-tracking";
import { NotificationLevels } from "discourse/lib/notification-levels";
import getURL from "discourse-common/lib/get-url";
import I18n, { i18n } from "discourse-i18n";
import TopicNotificationsOptions from "select-kit/components/topic-notifications-options";
const ParagraphWrapper = <template><p class="reason">{{yield}}</p></template>;
const EmptyWrapper = <template>
@ -115,17 +114,14 @@ export default class TopicNotificationsButton extends Component {
<template>
<div class="topic-notifications-button" ...attributes>
<this.conditionalWrapper>
<TopicNotificationsOptions
@value={{this.notificationLevel}}
@topic={{@topic}}
<TopicNotificationsTracking
@levelId={{this.notificationLevel}}
@onChange={{this.changeTopicNotificationLevel}}
@options={{hash
icon=(if this.isLoading "spinner")
showFullTitle=@expanded
showCaret=@expanded
headerAriaLabel=(i18n "topic.notifications.title")
}}
@showFullTitle={{@expanded}}
@showCaret={{@expanded}}
@topic={{@topic}}
/>
{{#if @expanded}}
<span class="text">{{htmlSafe this.reasonText}}</span>
{{/if}}

View File

@ -55,3 +55,4 @@
@import "user-stream";
@import "widget-dropdown";
@import "welcome-header";
@import "notifications-tracking";

View File

@ -0,0 +1,43 @@
.notifications-tracking-trigger-btn {
display: flex;
gap: 0.25em;
}
.notifications-tracking-btn {
display: flex;
flex: 1 0 auto;
box-sizing: border-box;
align-items: center;
&__icons {
display: flex;
align-self: flex-start;
margin-right: 0.75em;
}
&__texts {
line-height: var(--line-height-medium);
flex: 1 1 0%;
align-items: flex-start;
display: flex;
flex-wrap: wrap;
flex-direction: column;
}
&__label {
flex: 1 1 auto;
font-weight: bold;
font-size: var(--font-0);
color: var(--primary);
max-width: 100%;
@include ellipsis;
}
&__description {
flex: 1 1 auto;
font-size: var(--font-down-1);
color: var(--primary-medium);
white-space: normal;
text-align: left;
}
}

View File

@ -104,6 +104,10 @@
justify-content: flex-start;
background: rgba(0, 0, 0, 0);
&.-selected {
background: var(--d-hover);
}
&.btn-danger {
color: var(--danger);

View File

@ -1,20 +1,5 @@
.topic-notifications-button {
&.is-loading {
@include unselectable;
pointer-events: none;
.d-icon-spinner {
margin: 0;
}
.selected-name .d-icon {
display: none;
}
.topic-notifications-options {
opacity: 0.5;
}
}
display: contents;
}
// This is a weird fix for a weird issue in iOS/iPadOS, the browser freezes

View File

@ -57,16 +57,6 @@
grid-row-start: 1;
grid-column-start: 2;
}
.group-notifications-button {
margin-left: 8px;
.select-kit-header {
.selected-name .name {
display: none;
}
}
}
}
.user-messages-page {

View File

@ -1,5 +1,4 @@
import { hash } from "@ember/helper";
import CategoryNotificationsButton from "select-kit/components/category-notifications-button";
import CategoryNotificationsTracking from "discourse/components/category-notifications-tracking";
import BaseField from "./da-base-field";
import DAFieldDescription from "./da-field-description";
import DAFieldLabel from "./da-field-label";
@ -11,10 +10,9 @@ export default class CategoryNotficationLevelField extends BaseField {
<DAFieldLabel @label={{@label}} @field={{@field}} />
<div class="controls">
<CategoryNotificationsButton
@value={{@field.metadata.value}}
<CategoryNotificationsTracking
@levelId={{@field.metadata.value}}
@onChange={{this.mutValue}}
@options={{hash showFullTitle=true}}
/>
<DAFieldDescription @description={{@description}} />

View File

@ -3,7 +3,7 @@ import { render } from "@ember/test-helpers";
import { hbs } from "ember-cli-htmlbars";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import notificationsTracking from "discourse/tests/helpers/notifications-tracking-helper";
import AutomationFabricators from "discourse/plugins/automation/admin/lib/fabricators";
module(
@ -23,9 +23,7 @@ module(
await render(
hbs`<AutomationField @automation={{this.automation}} @field={{this.field}} />`
);
await selectKit().expand();
await selectKit().selectRowByValue(2);
await notificationsTracking().selectLevelId(2);
assert.strictEqual(this.field.metadata.value, 2);
});

View File

@ -1,19 +0,0 @@
import { classNames } from "@ember-decorators/component";
import NotificationsButtonComponent from "select-kit/components/notifications-button";
import {
pluginApiIdentifiers,
selectKitOptions,
} from "select-kit/components/select-kit";
import { threadNotificationButtonLevels } from "discourse/plugins/chat/discourse/lib/chat-notification-levels";
@classNames("thread-notifications-button")
@selectKitOptions({
i18nPrefix: "chat.thread.notifications",
showFullTitle: false,
btnCustomClasses: "btn-flat",
customStyle: true,
})
@pluginApiIdentifiers("thread-notifications-button")
export default class ChatThreadTrackingDropdown extends NotificationsButtonComponent {
content = threadNotificationButtonLevels;
}

View File

@ -5,7 +5,7 @@ import { service } from "@ember/service";
import concatClass from "discourse/helpers/concat-class";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { NotificationLevels } from "discourse/lib/notification-levels";
import ThreadTrackingDropdown from "discourse/plugins/chat/discourse/components/chat-thread-tracking-dropdown";
import ThreadNotificationsTracking from "discourse/plugins/chat/discourse/components/thread-notifications-tracking";
import UserChatThreadMembership from "discourse/plugins/chat/discourse/models/user-chat-thread-membership";
export default class ChatNavbarThreadTrackingDropdown extends Component {
@ -54,8 +54,8 @@ export default class ChatNavbarThreadTrackingDropdown extends Component {
}
<template>
<ThreadTrackingDropdown
@value={{this.threadNotificationLevel}}
<ThreadNotificationsTracking
@levelId={{this.threadNotificationLevel}}
@onChange={{this.updateThreadNotificationLevel}}
class={{concatClass
"c-navbar__thread-tracking-dropdown"

View File

@ -0,0 +1,17 @@
import NotificationsTracking from "discourse/components/notifications-tracking";
import { threadNotificationButtonLevels } from "discourse/plugins/chat/discourse/lib/chat-notification-levels";
const ThreadNotificationsTracking = <template>
<NotificationsTracking
@onChange={{@onChange}}
@levels={{threadNotificationButtonLevels}}
@levelId={{@levelId}}
@showCaret={{false}}
@showFullTitle={{false}}
@prefix="chat.thread.notifications"
class="thread-notifications-tracking"
@triggerClass="btn-transparent"
/>
</template>;
export default ThreadNotificationsTracking;

View File

@ -1,18 +0,0 @@
import { classNames } from "@ember-decorators/component";
import NotificationsButtonComponent from "select-kit/components/notifications-button";
import {
pluginApiIdentifiers,
selectKitOptions,
} from "select-kit/components/select-kit";
import { threadNotificationButtonLevels } from "discourse/plugins/chat/discourse/lib/chat-notification-levels";
@classNames("thread-notifications-button")
@selectKitOptions({
i18nPrefix: "chat.thread.notifications",
showFullTitle: false,
btnCustomClasses: "btn-flat",
})
@pluginApiIdentifiers("thread-notifications-button")
export default class ThreadNotificationsButton extends NotificationsButtonComponent {
content = threadNotificationButtonLevels;
}

View File

@ -22,23 +22,19 @@ module PageObjects
def notifications_button
@notifications_button ||=
PageObjects::Components::SelectKit.new(".thread-notifications-button")
PageObjects::Components::NotificationsTracking.new(".thread-notifications-tracking")
end
def notification_level=(level)
notifications_button.expand
notifications_button.select_row_by_value(
::Chat::UserChatThreadMembership.notification_levels[level.to_sym],
)
notifications_button.has_selected_value?(
notifications_button.toggle
notifications_button.select_level_id(
::Chat::UserChatThreadMembership.notification_levels[level.to_sym],
)
has_notification_level?(level)
end
def has_notification_level?(level)
select_kit =
PageObjects::Components::SelectKit.new(".c-navbar__thread-tracking-dropdown.-persisted")
select_kit.has_selected_value?(
notifications_button.has_selected_level_id?(
::Chat::UserChatThreadMembership.notification_levels[level.to_sym],
)
end

View File

@ -51,15 +51,11 @@
</StyleguideExample>
<StyleguideExample
@title="<TopicNotificationOptions>"
@title="<TopicNotificationsTracking>"
@initialValue={{1}}
as |value|
>
<TopicNotificationsOptions
@topic={{@dummy.topic}}
@value={{value}}
@onChange={{fn (mut value)}}
/>
<TopicNotificationsTracking @levelId={{value}} @onChange={{fn (mut value)}} />
</StyleguideExample>
<StyleguideExample
@ -82,20 +78,8 @@
<CategoriesAdminDropdown @onChange={{@dummyAction}} />
</StyleguideExample>
<StyleguideExample @title="<CategoryNotificationsButton>">
<CategoryNotificationsButton
@category={{get @dummy "categories.0"}}
@value={{1}}
@onChange={{@dummyAction}}
/>
</StyleguideExample>
<StyleguideExample @title="<NotificationsButton>">
<NotificationsButton
@options={{hash i18nPrefix="groups.notifications"}}
@value={{2}}
@onChange={{@dummyAction}}
/>
<StyleguideExample @title="<CategoryNotificationsTracking>">
<CategoryNotificationsTracking @levelId={{1}} @onChange={{@dummyAction}} />
</StyleguideExample>
<StyleguideExample @title="<DropdownSelectBox>">

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
module PageObjects
module Components
class NotificationsTracking < PageObjects::Components::Base
attr_reader :context
def initialize(context)
@context = context
end
def toggle
trigger.click
self
end
def select_level_id(id)
content.find("[data-level-id='#{id}']").click
self
end
def select_level_name(name)
content.find("[data-level-name='#{name}']").click
self
end
def has_selected_level_name?(name)
find("[data-trigger][data-identifier='#{identifier}'][data-level-name='#{name}']")
end
def has_selected_level_id?(id)
find("[data-trigger][data-identifier='#{identifier}'][data-level-id='#{id}']")
end
def trigger
if @context.is_a?(Capybara::Node::Element)
@context
else
find(@context)
end
end
def content
find("[data-content][data-identifier='#{identifier}']")
end
def identifier
trigger["data-identifier"]
end
end
end
end

View File

@ -12,11 +12,7 @@ module PageObjects
end
def has_tracking_status?(name)
select_kit =
PageObjects::Components::SelectKit.new(
"#topic-footer-buttons .topic-notifications-options",
)
expect(select_kit).to have_selected_name(name)
find("#topic-footer-buttons .notifications-tracking-trigger[data-level-name='#{name}']")
end
end
end

View File

@ -263,7 +263,7 @@ module PageObjects
end
def click_notifications_button
find(".topic-notifications-button .select-kit-header").click
find(".topic-notifications-button .notifications-tracking-trigger").click
end
def click_admin_menu_button
@ -272,7 +272,7 @@ module PageObjects
def watch_topic
click_notifications_button
find('li[data-name="watching"]').click
find('.notifications-tracking-btn[data-level-name="watching"]').click
end
def close_topic

View File

@ -2,7 +2,9 @@
describe "Tag notification level", type: :system do
let(:tags_page) { PageObjects::Pages::Tag.new }
let(:select_kit) { PageObjects::Components::SelectKit.new(".tag-notifications-button") }
let(:notifications_tracking) do
PageObjects::Components::NotificationsTracking.new(".tag-notifications-tracking")
end
fab!(:tag_1) { Fabricate(:tag) }
fab!(:current_user) { Fabricate(:admin) }
@ -12,11 +14,13 @@ describe "Tag notification level", type: :system do
describe "when changing a tag's notification level" do
it "should change instantly" do
tags_page.visit_tag(tag_1)
expect(select_kit).to have_selected_name("regular")
select_kit.select_row_by_name("watching")
expect(notifications_tracking).to have_selected_level_name("regular")
expect(select_kit).to have_selected_name("watching")
notifications_tracking.toggle
notifications_tracking.select_level_name("watching")
expect(notifications_tracking).to have_selected_level_name("watching")
end
end
end