mirror of
https://github.com/discourse/discourse.git
synced 2025-02-05 19:11:13 +00:00
Merge branch 'main' into feature/wizard-look-and-feel-improvements
This commit is contained in:
commit
8d3c3493f5
@ -1,8 +1,8 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
|
import DPageSubheader from "discourse/components/d-page-subheader";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
import AdminPageSubheader from "admin/components/admin-page-subheader";
|
|
||||||
import InstallThemeModal from "admin/components/modal/install-theme";
|
import InstallThemeModal from "admin/components/modal/install-theme";
|
||||||
import ThemesGrid from "admin/components/themes-grid";
|
import ThemesGrid from "admin/components/themes-grid";
|
||||||
|
|
||||||
@ -47,9 +47,9 @@ export default class AdminConfigAreasLookAndFeelThemes extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AdminPageSubheader
|
<DPageSubheader
|
||||||
@titleLabel="admin.config_areas.look_and_feel.themes.title"
|
@titleLabel={{i18n "admin.config_areas.look_and_feel.themes.title"}}
|
||||||
@descriptionLabel="admin.customize.theme.themes_intro_new"
|
@descriptionLabel={{i18n "admin.customize.theme.themes_intro_new"}}
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/93648"
|
@learnMoreUrl="https://meta.discourse.org/t/93648"
|
||||||
>
|
>
|
||||||
<:actions as |actions|>
|
<:actions as |actions|>
|
||||||
@ -60,7 +60,7 @@ export default class AdminConfigAreasLookAndFeelThemes extends Component {
|
|||||||
class="admin-look-and-feel__install-theme"
|
class="admin-look-and-feel__install-theme"
|
||||||
/>
|
/>
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageSubheader>
|
</DPageSubheader>
|
||||||
|
|
||||||
<div class="admin-detail">
|
<div class="admin-detail">
|
||||||
<ThemesGrid @themes={{@themes}} />
|
<ThemesGrid @themes={{@themes}} />
|
||||||
|
@ -1,138 +1,25 @@
|
|||||||
import { hash } from "@ember/helper";
|
// TODO (martin) Delete this once we have removed references from plugins.
|
||||||
import DButton from "discourse/components/d-button";
|
|
||||||
|
|
||||||
export const AdminPageActionButton = <template>
|
import {
|
||||||
<DButton
|
DangerActionListItem,
|
||||||
class="admin-page-action-button btn-small"
|
DangerButton,
|
||||||
...attributes
|
DefaultActionListItem,
|
||||||
@action={{@action}}
|
DefaultButton,
|
||||||
@route={{@route}}
|
DPageActionButton,
|
||||||
@routeModels={{@routeModels}}
|
DPageActionListItem,
|
||||||
@label={{@label}}
|
PrimaryActionListItem,
|
||||||
@title={{@title}}
|
PrimaryButton,
|
||||||
@icon={{@icon}}
|
WrappedActionListItem,
|
||||||
@isLoading={{@isLoading}}
|
WrappedButton,
|
||||||
/>
|
} from "discourse/components/d-page-action-button";
|
||||||
</template>;
|
|
||||||
|
|
||||||
// This is used for cases where there is another component,
|
export { DangerActionListItem as DangerActionListItem };
|
||||||
// e.g. UppyBackupUploader, that is a button which cannot use
|
export { DangerButton as DangerButton };
|
||||||
// PrimaryButton and so on directly. This should be used very rarely,
|
export { DefaultActionListItem as DefaultActionListItem };
|
||||||
// most cases are covered by the other button types.
|
export { DefaultButton as DefaultButton };
|
||||||
export const WrappedButton = <template>
|
export { DPageActionButton as DPageActionButton };
|
||||||
<span class="admin-page-action-wrapped-button">{{yield}}</span>
|
export { DPageActionListItem as DPageActionListItem };
|
||||||
</template>;
|
export { PrimaryActionListItem as PrimaryActionListItem };
|
||||||
|
export { PrimaryButton as PrimaryButton };
|
||||||
// No buttons here pass in an @icon by design. They are okay to
|
export { WrappedActionListItem as WrappedActionListItem };
|
||||||
// use on dropdown list items, but our UI guidelines do not allow them
|
export { WrappedButton as WrappedButton };
|
||||||
// on regular buttons.
|
|
||||||
export const PrimaryButton = <template>
|
|
||||||
<AdminPageActionButton
|
|
||||||
class="btn-primary"
|
|
||||||
...attributes
|
|
||||||
@action={{@action}}
|
|
||||||
@route={{@route}}
|
|
||||||
@routeModels={{@routeModels}}
|
|
||||||
@label={{@label}}
|
|
||||||
@title={{@title}}
|
|
||||||
@isLoading={{@isLoading}}
|
|
||||||
/>
|
|
||||||
</template>;
|
|
||||||
|
|
||||||
export const DangerButton = <template>
|
|
||||||
<AdminPageActionButton
|
|
||||||
class="btn-danger"
|
|
||||||
...attributes
|
|
||||||
@action={{@action}}
|
|
||||||
@route={{@route}}
|
|
||||||
@routeModels={{@routeModels}}
|
|
||||||
@label={{@label}}
|
|
||||||
@title={{@title}}
|
|
||||||
@isLoading={{@isLoading}}
|
|
||||||
/>
|
|
||||||
</template>;
|
|
||||||
|
|
||||||
export const DefaultButton = <template>
|
|
||||||
<AdminPageActionButton
|
|
||||||
class="btn-default"
|
|
||||||
...attributes
|
|
||||||
@action={{@action}}
|
|
||||||
@route={{@route}}
|
|
||||||
@routeModels={{@routeModels}}
|
|
||||||
@label={{@label}}
|
|
||||||
@title={{@title}}
|
|
||||||
@isLoading={{@isLoading}}
|
|
||||||
/>
|
|
||||||
</template>;
|
|
||||||
|
|
||||||
export const AdminPageActionListItem = <template>
|
|
||||||
<li class="dropdown-menu__item admin-page-action-list-item">
|
|
||||||
<AdminPageActionButton
|
|
||||||
class="btn-transparent"
|
|
||||||
...attributes
|
|
||||||
@action={{@action}}
|
|
||||||
@route={{@route}}
|
|
||||||
@routeModels={{@routeModels}}
|
|
||||||
@label={{@label}}
|
|
||||||
@title={{@title}}
|
|
||||||
@icon={{@icon}}
|
|
||||||
@isLoading={{@isLoading}}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</template>;
|
|
||||||
|
|
||||||
// This is used for cases where there is another component,
|
|
||||||
// e.g. UppyBackupUploader, that is a button which cannot use
|
|
||||||
// PrimaryActionListItem and so on directly. This should be used very rarely,
|
|
||||||
// most cases are covered by the other list types.
|
|
||||||
export const WrappedActionListItem = <template>
|
|
||||||
<li
|
|
||||||
class="dropdown-menu__item admin-page-action-list-item admin-page-action-wrapped-list-item"
|
|
||||||
>
|
|
||||||
{{yield (hash buttonClass="btn-transparent")}}
|
|
||||||
</li>
|
|
||||||
</template>;
|
|
||||||
|
|
||||||
// It is not a mistake that `btn-default` is used here, in a list
|
|
||||||
// there is no need for blue text.
|
|
||||||
export const PrimaryActionListItem = <template>
|
|
||||||
<AdminPageActionListItem
|
|
||||||
class="btn-default"
|
|
||||||
...attributes
|
|
||||||
@action={{@action}}
|
|
||||||
@route={{@route}}
|
|
||||||
@routeModels={{@routeModels}}
|
|
||||||
@label={{@label}}
|
|
||||||
@title={{@title}}
|
|
||||||
@icon={{@icon}}
|
|
||||||
@isLoading={{@isLoading}}
|
|
||||||
/>
|
|
||||||
</template>;
|
|
||||||
|
|
||||||
export const DefaultActionListItem = <template>
|
|
||||||
<AdminPageActionListItem
|
|
||||||
class="btn-default"
|
|
||||||
...attributes
|
|
||||||
@action={{@action}}
|
|
||||||
@route={{@route}}
|
|
||||||
@routeModels={{@routeModels}}
|
|
||||||
@label={{@label}}
|
|
||||||
@title={{@title}}
|
|
||||||
@icon={{@icon}}
|
|
||||||
@isLoading={{@isLoading}}
|
|
||||||
/>
|
|
||||||
</template>;
|
|
||||||
|
|
||||||
export const DangerActionListItem = <template>
|
|
||||||
<AdminPageActionListItem
|
|
||||||
class="btn-danger"
|
|
||||||
...attributes
|
|
||||||
@action={{@action}}
|
|
||||||
@route={{@route}}
|
|
||||||
@routeModels={{@routeModels}}
|
|
||||||
@label={{@label}}
|
|
||||||
@title={{@title}}
|
|
||||||
@icon={{@icon}}
|
|
||||||
@isLoading={{@isLoading}}
|
|
||||||
/>
|
|
||||||
</template>;
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||||
|
import DPageHeader from "discourse/components/d-page-header";
|
||||||
import NavItem from "discourse/components/nav-item";
|
import NavItem from "discourse/components/nav-item";
|
||||||
import { headerActionComponentForPlugin } from "discourse/lib/admin-plugin-header-actions";
|
import { headerActionComponentForPlugin } from "discourse/lib/admin-plugin-header-actions";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
import AdminPageHeader from "./admin-page-header";
|
|
||||||
import AdminPluginConfigArea from "./admin-plugin-config-area";
|
import AdminPluginConfigArea from "./admin-plugin-config-area";
|
||||||
|
|
||||||
export default class AdminPluginConfigPage extends Component {
|
export default class AdminPluginConfigPage extends Component {
|
||||||
@ -41,14 +41,14 @@ export default class AdminPluginConfigPage extends Component {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="admin-plugin-config-page">
|
<div class="admin-plugin-config-page">
|
||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabelTranslated={{@plugin.nameTitleized}}
|
@titleLabel={{@plugin.nameTitleized}}
|
||||||
@descriptionLabelTranslated={{@plugin.about}}
|
@descriptionLabel={{@plugin.about}}
|
||||||
@learnMoreUrl={{@plugin.linkUrl}}
|
@learnMoreUrl={{@plugin.linkUrl}}
|
||||||
@headerActionComponent={{this.headerActionComponent}}
|
@headerActionComponent={{this.headerActionComponent}}
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/plugins"
|
@path="/admin/plugins"
|
||||||
@label={{i18n "admin.plugins.title"}}
|
@label={{i18n "admin.plugins.title"}}
|
||||||
@ -75,7 +75,7 @@ export default class AdminPluginConfigPage extends Component {
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</:tabs>
|
</:tabs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<div class="admin-plugin-config-page__content">
|
<div class="admin-plugin-config-page__content">
|
||||||
<div class={{this.mainAreaClasses}}>
|
<div class={{this.mainAreaClasses}}>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { hash } from "@ember/helper";
|
import { hash } from "@ember/helper";
|
||||||
import { LinkTo } from "@ember/routing";
|
import { LinkTo } from "@ember/routing";
|
||||||
import concatClass from "discourse/helpers/concat-class";
|
|
||||||
import dIcon from "discourse-common/helpers/d-icon";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
import {
|
import {
|
||||||
DangerButton,
|
DangerButton,
|
||||||
DefaultButton,
|
DefaultButton,
|
||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
} from "admin/components/admin-page-action-button";
|
} from "discourse/components/d-page-action-button";
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
import dIcon from "discourse-common/helpers/d-icon";
|
||||||
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
export default class AdminSectionLandingItem extends Component {
|
export default class AdminSectionLandingItem extends Component {
|
||||||
get title() {
|
get title() {
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import icon from "discourse-common/helpers/d-icon";
|
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
export default class WebhookStatus extends Component {
|
export default class WebhookStatus extends Component {
|
||||||
iconNames = ["far-circle", "circle-xmark", "circle", "circle"];
|
statusClasses = ["--inactive", "--critical", "--success", "--inactive"];
|
||||||
iconClasses = ["text-muted", "text-danger", "text-successful", "text-muted"];
|
|
||||||
|
|
||||||
get status() {
|
get status() {
|
||||||
const lastStatus = this.args.webhook.get("last_delivery_status");
|
const lastStatus = this.args.webhook.get("last_delivery_status");
|
||||||
@ -15,16 +13,17 @@ export default class WebhookStatus extends Component {
|
|||||||
return i18n(`admin.web_hooks.delivery_status.${this.status.name}`);
|
return i18n(`admin.web_hooks.delivery_status.${this.status.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
get iconName() {
|
get statusClass() {
|
||||||
return this.iconNames[this.status.id - 1];
|
return this.statusClasses[this.status.id - 1];
|
||||||
}
|
|
||||||
|
|
||||||
get iconClass() {
|
|
||||||
return this.iconClasses[this.status.id - 1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
{{icon this.iconName class=this.iconClass}}
|
<div role="status" class="status-label {{this.statusClass}}">
|
||||||
{{this.deliveryStatus}}
|
<div class="status-label-indicator">
|
||||||
|
</div>
|
||||||
|
<div class="status-label-text">
|
||||||
|
{{this.deliveryStatus}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<div class="badges">
|
<div class="badges">
|
||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.badges.title"
|
@titleLabel={{i18n "admin.badges.title"}}
|
||||||
@descriptionLabel="admin.badges.page_description"
|
@descriptionLabel={{i18n "admin.badges.page_description"}}
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/understanding-and-using-badges/32540"
|
@learnMoreUrl="https://meta.discourse.org/t/understanding-and-using-badges/32540"
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/badges"
|
@path="/admin/badges"
|
||||||
@label={{i18n "admin.badges.title"}}
|
@label={{i18n "admin.badges.title"}}
|
||||||
@ -35,7 +36,7 @@
|
|||||||
class="edit-groupings-btn"
|
class="edit-groupings-btn"
|
||||||
/>
|
/>
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<div class="admin-container">
|
<div class="admin-container">
|
||||||
<div class="content-list">
|
<div class="content-list">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<AdminPageSubheader @titleLabel="admin.backups.files_title">
|
<DPageSubheader @titleLabel={{i18n "admin.backups.files_title"}}>
|
||||||
<:actions as |actions|>
|
<:actions as |actions|>
|
||||||
<actions.Wrapped as |wrapped|>
|
<actions.Wrapped as |wrapped|>
|
||||||
{{#if this.localBackupStorage}}
|
{{#if this.localBackupStorage}}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</actions.Wrapped>
|
</actions.Wrapped>
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageSubheader>
|
</DPageSubheader>
|
||||||
|
|
||||||
{{#if this.status.restoreDisabled}}
|
{{#if this.status.restoreDisabled}}
|
||||||
<div class="backup-message alert alert-info">
|
<div class="backup-message alert alert-info">
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<div class="admin-backups admin-config-page">
|
<div class="admin-backups admin-config-page">
|
||||||
|
<DPageHeader
|
||||||
<AdminPageHeader
|
@titleLabel={{i18n "admin.backups.title"}}
|
||||||
@titleLabel="admin.backups.title"
|
@descriptionLabel={{i18n "admin.backups.description"}}
|
||||||
@descriptionLabel="admin.backups.description"
|
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/create-download-and-restore-a-backup-of-your-discourse-database/122710"
|
@learnMoreUrl="https://meta.discourse.org/t/create-download-and-restore-a-backup-of-your-discourse-database/122710"
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/backups"
|
@path="/admin/backups"
|
||||||
@label={{i18n "admin.backups.title"}}
|
@label={{i18n "admin.backups.title"}}
|
||||||
@ -32,7 +32,7 @@
|
|||||||
/>
|
/>
|
||||||
<PluginOutlet @name="downloader" @connectorTagName="div" />
|
<PluginOutlet @name="downloader" @connectorTagName="div" />
|
||||||
</:tabs>
|
</:tabs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<PluginOutlet @name="before-backup-list" @connectorTagName="div" />
|
<PluginOutlet @name="before-backup-list" @connectorTagName="div" />
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.config_areas.about.header"
|
@titleLabel={{i18n "admin.config_areas.about.header"}}
|
||||||
@descriptionLabelTranslated={{i18n
|
@descriptionLabel={{i18n
|
||||||
"admin.config_areas.about.description"
|
"admin.config_areas.about.description"
|
||||||
(hash basePath=(base-path))
|
(hash basePath=(base-path))
|
||||||
}}
|
}}
|
||||||
@ -8,12 +8,13 @@
|
|||||||
@learnMoreUrl="https://meta.discourse.org/t/understanding-and-customizing-the-about-page/332161"
|
@learnMoreUrl="https://meta.discourse.org/t/understanding-and-customizing-the-about-page/332161"
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/config/about"
|
@path="/admin/config/about"
|
||||||
@label={{i18n "admin.config_areas.about.header"}}
|
@label={{i18n "admin.config_areas.about.header"}}
|
||||||
/>
|
/>
|
||||||
</:breadcrumbs>
|
</:breadcrumbs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<div class="admin-container admin-config-page__main-area">
|
<div class="admin-container admin-config-page__main-area">
|
||||||
<AdminConfigAreas::About @data={{this.model.site_settings}} />
|
<AdminConfigAreas::About @data={{this.model.site_settings}} />
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.config_areas.flags.header"
|
@titleLabel={{i18n "admin.config_areas.flags.header"}}
|
||||||
@descriptionLabel="admin.config_areas.flags.subheader"
|
@descriptionLabel={{i18n "admin.config_areas.flags.subheader"}}
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/moderation-flags/325589"
|
@learnMoreUrl="https://meta.discourse.org/t/moderation-flags/325589"
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/config/flags"
|
@path="/admin/config/flags"
|
||||||
@label={{i18n "admin.config_areas.flags.header"}}
|
@label={{i18n "admin.config_areas.flags.header"}}
|
||||||
@ -30,7 +31,7 @@
|
|||||||
class="admin-flags-tabs__flags"
|
class="admin-flags-tabs__flags"
|
||||||
/>
|
/>
|
||||||
</:tabs>
|
</:tabs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<div class="admin-container admin-config-page__main-area">
|
<div class="admin-container admin-config-page__main-area">
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.config_areas.look_and_feel.title"
|
@titleLabel={{i18n "admin.config_areas.look_and_feel.title"}}
|
||||||
@descriptionLabel="admin.config_areas.look_and_feel.description"
|
@descriptionLabel={{i18n "admin.config_areas.look_and_feel.description"}}
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966"
|
@learnMoreUrl="https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966"
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/config/look-and-feel"
|
@path="/admin/config/look-and-feel"
|
||||||
@label={{i18n "admin.config_areas.look_and_feel.title"}}
|
@label={{i18n "admin.config_areas.look_and_feel.title"}}
|
||||||
@ -16,7 +17,7 @@
|
|||||||
@label="admin.config_areas.look_and_feel.themes.title"
|
@label="admin.config_areas.look_and_feel.themes.title"
|
||||||
/>
|
/>
|
||||||
</:tabs>
|
</:tabs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<div class="admin-container admin-config-page__main-area">
|
<div class="admin-container admin-config-page__main-area">
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
<PluginOutlet @name="admin-dashboard-top" @connectorTagName="div" />
|
<PluginOutlet @name="admin-dashboard-top" @connectorTagName="div" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<AdminPageHeader @hideTabs={{true}}>
|
<DPageHeader @hideTabs={{true}}>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin.dashboard.title"}} />
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin.dashboard.title"}} />
|
||||||
</:breadcrumbs>
|
</:breadcrumbs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
{{#if this.showVersionChecks}}
|
{{#if this.showVersionChecks}}
|
||||||
<div class="section-top">
|
<div class="section-top">
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<div class="admin-emojis admin-config-page">
|
<div class="admin-emojis admin-config-page">
|
||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.emoji.title"
|
@titleLabel={{i18n "admin.emoji.title"}}
|
||||||
@descriptionLabel="admin.emoji.description"
|
@descriptionLabel={{i18n "admin.emoji.description"}}
|
||||||
@hideTabs={{this.hideTabs}}
|
@hideTabs={{this.hideTabs}}
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/customize/emojis"
|
@path="/admin/customize/emojis"
|
||||||
@label={{i18n "admin.emoji.title"}}
|
@label={{i18n "admin.emoji.title"}}
|
||||||
@ -25,7 +26,7 @@
|
|||||||
class="admin-emojis-tabs__emoji"
|
class="admin-emojis-tabs__emoji"
|
||||||
/>
|
/>
|
||||||
</:tabs>
|
</:tabs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<div class="admin-container admin-config-page__main-area">
|
<div class="admin-container admin-config-page__main-area">
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<div class="admin-permalinks admin-config-page">
|
<div class="admin-permalinks admin-config-page">
|
||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.permalink.title"
|
@titleLabel={{i18n "admin.permalink.title"}}
|
||||||
@descriptionLabel="admin.permalink.description"
|
@descriptionLabel={{i18n "admin.permalink.description"}}
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/redirect-old-forum-urls-to-new-discourse-urls/20930"
|
@learnMoreUrl="https://meta.discourse.org/t/redirect-old-forum-urls-to-new-discourse-urls/20930"
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/customize/permalinks"
|
@path="/admin/customize/permalinks"
|
||||||
@label={{i18n "admin.permalink.title"}}
|
@label={{i18n "admin.permalink.title"}}
|
||||||
@ -19,7 +20,7 @@
|
|||||||
class="admin-permalinks__header-add-permalink"
|
class="admin-permalinks__header-add-permalink"
|
||||||
/>
|
/>
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
<div class="admin-container admin-config-page__main-area">
|
<div class="admin-container admin-config-page__main-area">
|
||||||
<ConditionalLoadingSpinner @condition={{this.loading}}>
|
<ConditionalLoadingSpinner @condition={{this.loading}}>
|
||||||
<div class="permalink-search">
|
<div class="permalink-search">
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<div class="admin-plugins-list-container">
|
<div class="admin-plugins-list-container">
|
||||||
|
|
||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.plugins.installed"
|
@titleLabel={{i18n "admin.plugins.installed"}}
|
||||||
@descriptionLabel="admin.plugins.description"
|
@descriptionLabel={{i18n "admin.plugins.description"}}
|
||||||
@learnMoreUrl="https://www.discourse.org/plugins"
|
@learnMoreUrl="https://www.discourse.org/plugins"
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/plugins"
|
@path="/admin/plugins"
|
||||||
@label={{i18n "admin.plugins.title"}}
|
@label={{i18n "admin.plugins.title"}}
|
||||||
@ -32,7 +33,7 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</:tabs>
|
</:tabs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<div class="alert alert-info -top-margin admin-plugins-howto">
|
<div class="alert alert-info -top-margin admin-plugins-howto">
|
||||||
{{dIcon "circle-info"}}
|
{{dIcon "circle-info"}}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
{{#if this.showTopNav}}
|
{{#if this.showTopNav}}
|
||||||
<div class="admin-page-header">
|
<div class="d-page-header">
|
||||||
<DBreadcrumbsContainer />
|
<DBreadcrumbsContainer />
|
||||||
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/plugins"
|
@path="/admin/plugins"
|
||||||
@label={{i18n "admin.plugins.title"}}
|
@label={{i18n "admin.plugins.title"}}
|
||||||
/>
|
/>
|
||||||
<div class="admin-nav-submenu">
|
<div class="d-nav-submenu">
|
||||||
<HorizontalOverflowNav class="main-nav nav plugin-nav">
|
<HorizontalOverflowNav class="main-nav nav plugin-nav">
|
||||||
<NavItem @route="adminPlugins.index" @label="admin.plugins.title" />
|
<NavItem @route="adminPlugins.index" @label="admin.plugins.title" />
|
||||||
{{#each this.adminRoutes as |route|}}
|
{{#each this.adminRoutes as |route|}}
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.section_landing_pages.account.title"
|
@titleLabel={{i18n "admin.section_landing_pages.account.title"}}
|
||||||
@hideTabs={{true}}
|
@hideTabs={{true}}
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/section/account"
|
@path="/admin/section/account"
|
||||||
@label={{i18n "admin.section_landing_pages.account.title"}}
|
@label={{i18n "admin.section_landing_pages.account.title"}}
|
||||||
/>
|
/>
|
||||||
</:breadcrumbs>
|
</:breadcrumbs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<AdminSectionLandingWrapper>
|
<AdminSectionLandingWrapper>
|
||||||
<AdminSectionLandingItem
|
<AdminSectionLandingItem
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<div class="admin-user_fields admin-config-page">
|
<div class="admin-user_fields admin-config-page">
|
||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.user_fields.title"
|
@titleLabel={{i18n "admin.user_fields.title"}}
|
||||||
@descriptionLabel="admin.user_fields.help"
|
@descriptionLabel={{i18n "admin.user_fields.help"}}
|
||||||
@hideTabs={{true}}
|
@hideTabs={{true}}
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/creating-and-configuring-custom-user-fields/113192"
|
@learnMoreUrl="https://meta.discourse.org/t/creating-and-configuring-custom-user-fields/113192"
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/customize/user_fields"
|
@path="/admin/customize/user_fields"
|
||||||
@label={{i18n "admin.user_fields.title"}}
|
@label={{i18n "admin.user_fields.title"}}
|
||||||
@ -17,7 +18,7 @@
|
|||||||
@label="admin.user_fields.add"
|
@label="admin.user_fields.add"
|
||||||
/>
|
/>
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<div class="admin-config-page__main-area">
|
<div class="admin-config-page__main-area">
|
||||||
<div class="user-fields">
|
<div class="user-fields">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<AdminPageSubheader @titleLabelTranslated={{this.title}}>
|
<DPageSubheader @titleLabel={{this.title}}>
|
||||||
<:actions as |actions|>
|
<:actions as |actions|>
|
||||||
{{#if this.canCheckEmails}}
|
{{#if this.canCheckEmails}}
|
||||||
{{#if this.showEmails}}
|
{{#if this.showEmails}}
|
||||||
@ -16,7 +16,7 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageSubheader>
|
</DPageSubheader>
|
||||||
|
|
||||||
<PluginOutlet @name="admin-users-list-show-before" />
|
<PluginOutlet @name="admin-users-list-show-before" />
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<div class="admin-users admin-config-page">
|
<div class="admin-users admin-config-page">
|
||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.users.title"
|
@titleLabel={{i18n "admin.users.title"}}
|
||||||
@descriptionLabel="admin.users.description"
|
@descriptionLabel={{i18n "admin.users.description"}}
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/accessing-a-user-s-admin-page/311859"
|
@learnMoreUrl="https://meta.discourse.org/t/accessing-a-user-s-admin-page/311859"
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/users/list"
|
@path="/admin/users/list"
|
||||||
@label={{i18n "admin.users.title"}}
|
@label={{i18n "admin.users.title"}}
|
||||||
@ -72,7 +73,7 @@
|
|||||||
class="admin-users-tabs__groups"
|
class="admin-users-tabs__groups"
|
||||||
/>
|
/>
|
||||||
</:tabs>
|
</:tabs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
<div class="admin-container admin-config-page__main-area">
|
<div class="admin-container admin-config-page__main-area">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,19 +14,34 @@
|
|||||||
|
|
||||||
{{#if this.model}}
|
{{#if this.model}}
|
||||||
<LoadMore @selector=".web-hooks tr" @action={{this.loadMore}}>
|
<LoadMore @selector=".web-hooks tr" @action={{this.loadMore}}>
|
||||||
<table class="web-hooks grid">
|
<table class="d-admin-table web-hooks">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{i18n "admin.web_hooks.delivery_status.title"}}</th>
|
|
||||||
<th>{{i18n "admin.web_hooks.payload_url"}}</th>
|
<th>{{i18n "admin.web_hooks.payload_url"}}</th>
|
||||||
<th>{{i18n "admin.web_hooks.description_label"}}</th>
|
<th>{{i18n "admin.web_hooks.description_label"}}</th>
|
||||||
<th>{{i18n "admin.web_hooks.controls"}}</th>
|
<th>{{i18n "admin.web_hooks.delivery_status.title"}}</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#each this.model as |webhook|}}
|
{{#each this.model as |webhook|}}
|
||||||
<tr>
|
<tr class="d-admin-row__content">
|
||||||
<td class="delivery-status">
|
<td class="d-admin-row__overview payload-url">
|
||||||
|
<LinkTo @route="adminWebHooks.edit" @model={{webhook}}>
|
||||||
|
{{webhook.payload_url}}
|
||||||
|
</LinkTo>
|
||||||
|
</td>
|
||||||
|
<td class="d-admin-row__detail description">
|
||||||
|
<div class="d-admin-row__mobile-label">
|
||||||
|
{{i18n "admin.web_hooks.description_label"}}
|
||||||
|
</div>
|
||||||
|
{{webhook.description}}
|
||||||
|
</td>
|
||||||
|
<td class="d-admin-row__detail delivery-status">
|
||||||
|
<div class="d-admin-row__mobile-label">
|
||||||
|
{{i18n "admin.web_hooks.delivery_status.title"}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<LinkTo @route="adminWebHooks.show" @model={{webhook}}>
|
<LinkTo @route="adminWebHooks.show" @model={{webhook}}>
|
||||||
<WebhookStatus
|
<WebhookStatus
|
||||||
@deliveryStatuses={{this.deliveryStatuses}}
|
@deliveryStatuses={{this.deliveryStatuses}}
|
||||||
@ -34,28 +49,24 @@
|
|||||||
/>
|
/>
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
</td>
|
</td>
|
||||||
<td class="payload-url">
|
<td class="d-admin-row__controls controls">
|
||||||
<LinkTo @route="adminWebHooks.edit" @model={{webhook}}>
|
<div class="d-admin-row__controls-options">
|
||||||
{{webhook.payload_url}}
|
<LinkTo
|
||||||
</LinkTo>
|
@route="adminWebHooks.edit"
|
||||||
</td>
|
@model={{webhook}}
|
||||||
<td class="description">{{webhook.description}}</td>
|
class="btn btn-default no-text"
|
||||||
<td class="controls">
|
title={{i18n "admin.web_hooks.edit"}}
|
||||||
<LinkTo
|
>
|
||||||
@route="adminWebHooks.edit"
|
{{d-icon "far-pen-to-square"}}
|
||||||
@model={{webhook}}
|
</LinkTo>
|
||||||
class="btn btn-default no-text"
|
|
||||||
title={{i18n "admin.web_hooks.edit"}}
|
|
||||||
>
|
|
||||||
{{d-icon "far-pen-to-square"}}
|
|
||||||
</LinkTo>
|
|
||||||
|
|
||||||
<DButton
|
<DButton
|
||||||
@action={{fn this.destroyWebhook webhook}}
|
@action={{fn this.destroyWebhook webhook}}
|
||||||
@icon="xmark"
|
@icon="xmark"
|
||||||
@title="delete"
|
@title="delete"
|
||||||
class="destroy btn-danger"
|
class="destroy btn-danger"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@titleLabel="admin.dashboard.new_features.title"
|
@titleLabel={{i18n "admin.dashboard.new_features.title"}}
|
||||||
@descriptionLabel="admin.dashboard.new_features.subtitle"
|
@descriptionLabel={{i18n "admin.dashboard.new_features.subtitle"}}
|
||||||
@learnMoreUrl="https://meta.discourse.org/tags/c/announcements/67/release-notes"
|
@learnMoreUrl="https://meta.discourse.org/tags/c/announcements/67/release-notes"
|
||||||
@hideTabs={{true}}
|
@hideTabs={{true}}
|
||||||
>
|
>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/whats-new"
|
@path="/admin/whats-new"
|
||||||
@label={{i18n "admin.dashboard.new_features.title"}}
|
@label={{i18n "admin.dashboard.new_features.title"}}
|
||||||
@ -16,7 +17,7 @@
|
|||||||
@action={{this.checkForUpdates}}
|
@action={{this.checkForUpdates}}
|
||||||
/>
|
/>
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
|
|
||||||
<div class="admin-container admin-config-page__main-area">
|
<div class="admin-container admin-config-page__main-area">
|
||||||
<div class="admin-config-area">
|
<div class="admin-config-area">
|
||||||
|
@ -12,10 +12,7 @@
|
|||||||
</:button>
|
</:button>
|
||||||
<:tooltip>
|
<:tooltip>
|
||||||
{{#if @disabled}}
|
{{#if @disabled}}
|
||||||
<DTooltip
|
<DTooltip @icon="circle-info" @content={{i18n this.disallowedReason}} />
|
||||||
@icon="circle-info"
|
|
||||||
@content={{i18n "topic.create_disabled_category"}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</:tooltip>
|
</:tooltip>
|
||||||
</DButtonTooltip>
|
</DButtonTooltip>
|
||||||
|
@ -5,4 +5,12 @@ import { tagName } from "@ember-decorators/component";
|
|||||||
export default class CreateTopicButton extends Component {
|
export default class CreateTopicButton extends Component {
|
||||||
label = "topic.create";
|
label = "topic.create";
|
||||||
btnClass = "btn-default";
|
btnClass = "btn-default";
|
||||||
|
|
||||||
|
get disallowedReason() {
|
||||||
|
if (this.canCreateTopicOnTag === false) {
|
||||||
|
return "topic.create_disabled_tag";
|
||||||
|
} else if (this.disabled) {
|
||||||
|
return "topic.create_disabled_category";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ const ACTION_AS_STRING_DEPRECATION_ARGS = [
|
|||||||
|
|
||||||
export default class DButton extends GlimmerComponentWithDeprecatedParentView {
|
export default class DButton extends GlimmerComponentWithDeprecatedParentView {
|
||||||
@service router;
|
@service router;
|
||||||
|
@service capabilities;
|
||||||
|
|
||||||
@notEmpty("args.icon") btnIcon;
|
@notEmpty("args.icon") btnIcon;
|
||||||
|
|
||||||
@ -114,6 +115,7 @@ export default class DButton extends GlimmerComponentWithDeprecatedParentView {
|
|||||||
|
|
||||||
_triggerAction(event) {
|
_triggerAction(event) {
|
||||||
const { action: actionVal, route, routeModels } = this.args;
|
const { action: actionVal, route, routeModels } = this.args;
|
||||||
|
const isIOS = this.capabilities?.isIOS;
|
||||||
|
|
||||||
if (actionVal || route) {
|
if (actionVal || route) {
|
||||||
if (actionVal) {
|
if (actionVal) {
|
||||||
@ -129,19 +131,35 @@ export default class DButton extends GlimmerComponentWithDeprecatedParentView {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (typeof actionVal === "object" && actionVal.value) {
|
} else if (typeof actionVal === "object" && actionVal.value) {
|
||||||
// Using `next()` to optimise INP
|
if (isIOS) {
|
||||||
next(() =>
|
// Don't optimise INP in iOS
|
||||||
|
// it results in focus events not being triggered
|
||||||
forwardEvent
|
forwardEvent
|
||||||
? actionVal.value(actionParam, event)
|
? actionVal.value(actionParam, event)
|
||||||
: actionVal.value(actionParam)
|
: actionVal.value(actionParam);
|
||||||
);
|
} else {
|
||||||
|
// Using `next()` to optimise INP
|
||||||
|
next(() =>
|
||||||
|
forwardEvent
|
||||||
|
? actionVal.value(actionParam, event)
|
||||||
|
: actionVal.value(actionParam)
|
||||||
|
);
|
||||||
|
}
|
||||||
} else if (typeof actionVal === "function") {
|
} else if (typeof actionVal === "function") {
|
||||||
// Using `next()` to optimise INP
|
if (isIOS) {
|
||||||
next(() =>
|
// Don't optimise INP in iOS
|
||||||
|
// it results in focus events not being triggered
|
||||||
forwardEvent
|
forwardEvent
|
||||||
? actionVal(actionParam, event)
|
? actionVal(actionParam, event)
|
||||||
: actionVal(actionParam)
|
: actionVal(actionParam);
|
||||||
);
|
} else {
|
||||||
|
// Using `next()` to optimise INP
|
||||||
|
next(() =>
|
||||||
|
forwardEvent
|
||||||
|
? actionVal(actionParam, event)
|
||||||
|
: actionVal(actionParam)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (route) {
|
} else if (route) {
|
||||||
if (routeModels) {
|
if (routeModels) {
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
import { hash } from "@ember/helper";
|
||||||
|
import DButton from "discourse/components/d-button";
|
||||||
|
|
||||||
|
export const DPageActionButton = <template>
|
||||||
|
<DButton
|
||||||
|
class="d-page-action-button btn-small"
|
||||||
|
...attributes
|
||||||
|
@action={{@action}}
|
||||||
|
@route={{@route}}
|
||||||
|
@routeModels={{@routeModels}}
|
||||||
|
@label={{@label}}
|
||||||
|
@title={{@title}}
|
||||||
|
@icon={{@icon}}
|
||||||
|
@isLoading={{@isLoading}}
|
||||||
|
/>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
// This is used for cases where there is another component,
|
||||||
|
// e.g. UppyBackupUploader, that is a button which cannot use
|
||||||
|
// PrimaryButton and so on directly. This should be used very rarely,
|
||||||
|
// most cases are covered by the other button types.
|
||||||
|
export const WrappedButton = <template>
|
||||||
|
<span class="d-page-action-wrapped-button">{{yield}}</span>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
// No buttons here pass in an @icon by design. They are okay to
|
||||||
|
// use on dropdown list items, but our UI guidelines do not allow them
|
||||||
|
// on regular buttons.
|
||||||
|
export const PrimaryButton = <template>
|
||||||
|
<DPageActionButton
|
||||||
|
class="btn-primary"
|
||||||
|
...attributes
|
||||||
|
@action={{@action}}
|
||||||
|
@route={{@route}}
|
||||||
|
@routeModels={{@routeModels}}
|
||||||
|
@label={{@label}}
|
||||||
|
@title={{@title}}
|
||||||
|
@isLoading={{@isLoading}}
|
||||||
|
/>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
export const DangerButton = <template>
|
||||||
|
<DPageActionButton
|
||||||
|
class="btn-danger"
|
||||||
|
...attributes
|
||||||
|
@action={{@action}}
|
||||||
|
@route={{@route}}
|
||||||
|
@routeModels={{@routeModels}}
|
||||||
|
@label={{@label}}
|
||||||
|
@title={{@title}}
|
||||||
|
@isLoading={{@isLoading}}
|
||||||
|
/>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
export const DefaultButton = <template>
|
||||||
|
<DPageActionButton
|
||||||
|
class="btn-default"
|
||||||
|
...attributes
|
||||||
|
@action={{@action}}
|
||||||
|
@route={{@route}}
|
||||||
|
@routeModels={{@routeModels}}
|
||||||
|
@label={{@label}}
|
||||||
|
@title={{@title}}
|
||||||
|
@isLoading={{@isLoading}}
|
||||||
|
/>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
export const DPageActionListItem = <template>
|
||||||
|
<li class="dropdown-menu__item d-page-action-list-item">
|
||||||
|
<DPageActionButton
|
||||||
|
class="btn-transparent"
|
||||||
|
...attributes
|
||||||
|
@action={{@action}}
|
||||||
|
@route={{@route}}
|
||||||
|
@routeModels={{@routeModels}}
|
||||||
|
@label={{@label}}
|
||||||
|
@title={{@title}}
|
||||||
|
@icon={{@icon}}
|
||||||
|
@isLoading={{@isLoading}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
// This is used for cases where there is another component,
|
||||||
|
// e.g. UppyBackupUploader, that is a button which cannot use
|
||||||
|
// PrimaryActionListItem and so on directly. This should be used very rarely,
|
||||||
|
// most cases are covered by the other list types.
|
||||||
|
export const WrappedActionListItem = <template>
|
||||||
|
<li
|
||||||
|
class="dropdown-menu__item d-page-action-list-item d-page-action-wrapped-list-item"
|
||||||
|
>
|
||||||
|
{{yield (hash buttonClass="btn-transparent")}}
|
||||||
|
</li>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
// It is not a mistake that there is no PrimaryActionListItem here, in a list
|
||||||
|
// there is no need for blue text.
|
||||||
|
export const DefaultActionListItem = <template>
|
||||||
|
<DPageActionListItem
|
||||||
|
class="btn-default"
|
||||||
|
...attributes
|
||||||
|
@action={{@action}}
|
||||||
|
@route={{@route}}
|
||||||
|
@routeModels={{@routeModels}}
|
||||||
|
@label={{@label}}
|
||||||
|
@title={{@title}}
|
||||||
|
@icon={{@icon}}
|
||||||
|
@isLoading={{@isLoading}}
|
||||||
|
/>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
export const DangerActionListItem = <template>
|
||||||
|
<DPageActionListItem
|
||||||
|
class="btn-danger"
|
||||||
|
...attributes
|
||||||
|
@action={{@action}}
|
||||||
|
@route={{@route}}
|
||||||
|
@routeModels={{@routeModels}}
|
||||||
|
@label={{@label}}
|
||||||
|
@title={{@title}}
|
||||||
|
@icon={{@icon}}
|
||||||
|
@isLoading={{@isLoading}}
|
||||||
|
/>
|
||||||
|
</template>;
|
@ -0,0 +1,145 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { hash } from "@ember/helper";
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import { or } from "truth-helpers";
|
||||||
|
import DBreadcrumbsContainer from "discourse/components/d-breadcrumbs-container";
|
||||||
|
import {
|
||||||
|
DangerActionListItem,
|
||||||
|
DangerButton,
|
||||||
|
DefaultActionListItem,
|
||||||
|
DefaultButton,
|
||||||
|
PrimaryButton,
|
||||||
|
WrappedActionListItem,
|
||||||
|
WrappedButton,
|
||||||
|
} from "discourse/components/d-page-action-button";
|
||||||
|
import DropdownMenu from "discourse/components/dropdown-menu";
|
||||||
|
import HorizontalOverflowNav from "discourse/components/horizontal-overflow-nav";
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import { i18n } from "discourse-i18n";
|
||||||
|
import DMenu from "float-kit/components/d-menu";
|
||||||
|
|
||||||
|
const HEADLESS_ACTIONS = ["new", "edit"];
|
||||||
|
|
||||||
|
export default class DPageHeader extends Component {
|
||||||
|
@service site;
|
||||||
|
@service router;
|
||||||
|
@tracked shouldDisplay = true;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.router.on("routeDidChange", this, this.#checkIfShouldDisplay);
|
||||||
|
this.#checkIfShouldDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
super.willDestroy(...arguments);
|
||||||
|
this.router.off("routeDidChange", this, this.#checkIfShouldDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
#checkIfShouldDisplay() {
|
||||||
|
if (this.args.shouldDisplay !== undefined) {
|
||||||
|
return (this.shouldDisplay = this.args.shouldDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPath = this.router._router.currentPath;
|
||||||
|
if (!currentPath) {
|
||||||
|
return (this.shouldDisplay = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This has a little admin-specific logic in it, in future
|
||||||
|
// we could extract this out and have it a bit more generic,
|
||||||
|
// for now I think it's a fine tradeoff.
|
||||||
|
const pathSegments = currentPath.split(".");
|
||||||
|
this.shouldDisplay =
|
||||||
|
!pathSegments.includes("admin") ||
|
||||||
|
!HEADLESS_ACTIONS.find((segment) => pathSegments.includes(segment));
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{#if this.shouldDisplay}}
|
||||||
|
<div class="d-page-header">
|
||||||
|
<div class="d-page-header__breadcrumbs">
|
||||||
|
<DBreadcrumbsContainer />
|
||||||
|
{{yield to="breadcrumbs"}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-page-header__title-row">
|
||||||
|
{{#if @titleLabel}}
|
||||||
|
<h1 class="d-page-header__title">{{@titleLabel}}</h1>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if (or (has-block "actions") @headerActionComponent)}}
|
||||||
|
<div class="d-page-header__actions">
|
||||||
|
{{#if this.site.mobileView}}
|
||||||
|
<DMenu
|
||||||
|
@identifier="d-page-header-mobile-actions"
|
||||||
|
@title={{i18n "more_options"}}
|
||||||
|
@icon="ellipsis-vertical"
|
||||||
|
class="btn-small"
|
||||||
|
>
|
||||||
|
<:content>
|
||||||
|
<DropdownMenu class="d-page-header__mobile-actions">
|
||||||
|
{{#let
|
||||||
|
(hash
|
||||||
|
Primary=DefaultActionListItem
|
||||||
|
Default=DefaultActionListItem
|
||||||
|
Danger=DangerActionListItem
|
||||||
|
Wrapped=WrappedActionListItem
|
||||||
|
)
|
||||||
|
as |actions|
|
||||||
|
}}
|
||||||
|
{{#if (has-block "actions")}}
|
||||||
|
{{yield actions to="actions"}}
|
||||||
|
{{else}}
|
||||||
|
<@headerActionComponent @actions={{actions}} />
|
||||||
|
{{/if}}
|
||||||
|
{{/let}}
|
||||||
|
</DropdownMenu>
|
||||||
|
</:content>
|
||||||
|
</DMenu>
|
||||||
|
{{else}}
|
||||||
|
{{#let
|
||||||
|
(hash
|
||||||
|
Primary=PrimaryButton
|
||||||
|
Default=DefaultButton
|
||||||
|
Danger=DangerButton
|
||||||
|
Wrapped=WrappedButton
|
||||||
|
)
|
||||||
|
as |actions|
|
||||||
|
}}
|
||||||
|
{{#if (has-block "actions")}}
|
||||||
|
{{yield actions to="actions"}}
|
||||||
|
{{else}}
|
||||||
|
<@headerActionComponent @actions={{actions}} />
|
||||||
|
{{/if}}
|
||||||
|
{{/let}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if @descriptionLabel}}
|
||||||
|
<p class="d-page-header__description">
|
||||||
|
{{htmlSafe @descriptionLabel}}
|
||||||
|
{{#if @learnMoreUrl}}
|
||||||
|
<span class="d-page-header__learn-more">{{htmlSafe
|
||||||
|
(i18n "learn_more_with_link" url=@learnMoreUrl)
|
||||||
|
}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#unless @hideTabs}}
|
||||||
|
<div class="d-nav-submenu">
|
||||||
|
<HorizontalOverflowNav class="d-nav-submenu__tabs">
|
||||||
|
{{yield to="tabs"}}
|
||||||
|
</HorizontalOverflowNav>
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</template>
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { hash } from "@ember/helper";
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import {
|
||||||
|
DangerActionListItem,
|
||||||
|
DangerButton,
|
||||||
|
DefaultActionListItem,
|
||||||
|
DefaultButton,
|
||||||
|
PrimaryButton,
|
||||||
|
WrappedActionListItem,
|
||||||
|
WrappedButton,
|
||||||
|
} from "discourse/components/d-page-action-button";
|
||||||
|
import DropdownMenu from "discourse/components/dropdown-menu";
|
||||||
|
import { i18n } from "discourse-i18n";
|
||||||
|
import DMenu from "float-kit/components/d-menu";
|
||||||
|
|
||||||
|
export default class DPageSubheader extends Component {
|
||||||
|
@service site;
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="d-page-subheader">
|
||||||
|
<div class="d-page-subheader__title-row">
|
||||||
|
<h2 class="d-page-subheader__title">{{@titleLabel}}</h2>
|
||||||
|
{{#if (has-block "actions")}}
|
||||||
|
<div class="d-page-subheader__actions">
|
||||||
|
{{#if this.site.mobileView}}
|
||||||
|
<DMenu
|
||||||
|
@identifier="d-page-subheader-mobile-actions"
|
||||||
|
@title={{i18n "more_options"}}
|
||||||
|
@icon="ellipsis-vertical"
|
||||||
|
class="btn-small"
|
||||||
|
>
|
||||||
|
<:content>
|
||||||
|
<DropdownMenu class="d-page-subheader__mobile-actions">
|
||||||
|
{{yield
|
||||||
|
(hash
|
||||||
|
Primary=DefaultActionListItem
|
||||||
|
Default=DefaultActionListItem
|
||||||
|
Danger=DangerActionListItem
|
||||||
|
Wrapped=WrappedActionListItem
|
||||||
|
)
|
||||||
|
to="actions"
|
||||||
|
}}
|
||||||
|
</DropdownMenu>
|
||||||
|
</:content>
|
||||||
|
</DMenu>
|
||||||
|
{{else}}
|
||||||
|
{{yield
|
||||||
|
(hash
|
||||||
|
Primary=PrimaryButton
|
||||||
|
Default=DefaultButton
|
||||||
|
Danger=DangerButton
|
||||||
|
Wrapped=WrappedButton
|
||||||
|
)
|
||||||
|
to="actions"
|
||||||
|
}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if @descriptionLabel}}
|
||||||
|
<p class="d-page-subheader__description">
|
||||||
|
{{htmlSafe @descriptionLabel}}
|
||||||
|
{{#if @learnMoreUrl}}
|
||||||
|
<span class="d-page-subheader__learn-more">
|
||||||
|
{{htmlSafe
|
||||||
|
(i18n "learn_more_with_link" url=@learnMoreUrl)
|
||||||
|
}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
}
|
@ -46,6 +46,10 @@ export default class DSelect extends Component {
|
|||||||
return this.args.value && this.args.value !== NO_VALUE_OPTION;
|
return this.args.value && this.args.value !== NO_VALUE_OPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get includeNone() {
|
||||||
|
return this.args.includeNone ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<select
|
<select
|
||||||
value={{@value}}
|
value={{@value}}
|
||||||
@ -53,13 +57,15 @@ export default class DSelect extends Component {
|
|||||||
class="d-select"
|
class="d-select"
|
||||||
{{on "input" this.handleInput}}
|
{{on "input" this.handleInput}}
|
||||||
>
|
>
|
||||||
<DSelectOption @value={{NO_VALUE_OPTION}}>
|
{{#if this.includeNone}}
|
||||||
{{#if this.hasSelectedValue}}
|
<DSelectOption @value={{NO_VALUE_OPTION}}>
|
||||||
{{i18n "none_placeholder"}}
|
{{#if this.hasSelectedValue}}
|
||||||
{{else}}
|
{{i18n "none_placeholder"}}
|
||||||
{{i18n "select_placeholder"}}
|
{{else}}
|
||||||
{{/if}}
|
{{i18n "select_placeholder"}}
|
||||||
</DSelectOption>
|
{{/if}}
|
||||||
|
</DSelectOption>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{yield (hash Option=(component DSelectOption selected=@value))}}
|
{{yield (hash Option=(component DSelectOption selected=@value))}}
|
||||||
</select>
|
</select>
|
||||||
|
@ -48,6 +48,13 @@
|
|||||||
<label class="alt-placeholder" for="login-account-password">
|
<label class="alt-placeholder" for="login-account-password">
|
||||||
{{i18n "login.password"}}
|
{{i18n "login.password"}}
|
||||||
</label>
|
</label>
|
||||||
|
{{#if @loginPassword}}
|
||||||
|
<TogglePasswordMask
|
||||||
|
@maskPassword={{this.maskPassword}}
|
||||||
|
@togglePasswordMask={{this.togglePasswordMask}}
|
||||||
|
tabindex="3"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
<div class="login__password-links">
|
<div class="login__password-links">
|
||||||
<a
|
<a
|
||||||
href
|
href
|
||||||
@ -57,13 +64,6 @@
|
|||||||
>
|
>
|
||||||
{{i18n "forgot_password.action"}}
|
{{i18n "forgot_password.action"}}
|
||||||
</a>
|
</a>
|
||||||
{{#if @loginPassword}}
|
|
||||||
<TogglePasswordMask
|
|
||||||
@maskPassword={{this.maskPassword}}
|
|
||||||
@togglePasswordMask={{this.togglePasswordMask}}
|
|
||||||
tabindex="3"
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="caps-lock-warning {{unless this.capsLockOn 'hidden'}}">
|
<div class="caps-lock-warning {{unless this.capsLockOn 'hidden'}}">
|
||||||
{{d-icon "triangle-exclamation"}}
|
{{d-icon "triangle-exclamation"}}
|
||||||
|
@ -138,7 +138,10 @@
|
|||||||
<label class="alt-placeholder" for="new-account-password">
|
<label class="alt-placeholder" for="new-account-password">
|
||||||
{{i18n "user.password.title"}}
|
{{i18n "user.password.title"}}
|
||||||
</label>
|
</label>
|
||||||
|
<TogglePasswordMask
|
||||||
|
@maskPassword={{this.maskPassword}}
|
||||||
|
@togglePasswordMask={{this.togglePasswordMask}}
|
||||||
|
/>
|
||||||
<div class="create-account__password-info">
|
<div class="create-account__password-info">
|
||||||
<div class="create-account__password-tip-validation">
|
<div class="create-account__password-tip-validation">
|
||||||
{{#if this.showPasswordValidation}}
|
{{#if this.showPasswordValidation}}
|
||||||
@ -163,10 +166,6 @@
|
|||||||
{{i18n "login.caps_lock_warning"}}
|
{{i18n "login.caps_lock_warning"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TogglePasswordMask
|
|
||||||
@maskPassword={{this.maskPassword}}
|
|
||||||
@togglePasswordMask={{this.togglePasswordMask}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
@ -3,10 +3,7 @@ import { tracked } from "@glimmer/tracking";
|
|||||||
import { concat } from "@ember/helper";
|
import { concat } from "@ember/helper";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { eq } from "truth-helpers";
|
|
||||||
import concatClass from "discourse/helpers/concat-class";
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
import dIcon from "discourse-common/helpers/d-icon";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
export default class SignupProgressBar extends Component {
|
export default class SignupProgressBar extends Component {
|
||||||
@service siteSettings;
|
@service siteSettings;
|
||||||
@ -22,10 +19,6 @@ export default class SignupProgressBar extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stepText(step) {
|
|
||||||
return i18n(`create_account.progress_bar.${step}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
get currentStepIndex() {
|
get currentStepIndex() {
|
||||||
return this.steps.findIndex((step) => step === this.args.step);
|
return this.steps.findIndex((step) => step === this.args.step);
|
||||||
}
|
}
|
||||||
@ -57,19 +50,8 @@ export default class SignupProgressBar extends Component {
|
|||||||
>
|
>
|
||||||
<div class="signup-progress-bar__step">
|
<div class="signup-progress-bar__step">
|
||||||
<div class="signup-progress-bar__circle">
|
<div class="signup-progress-bar__circle">
|
||||||
{{#if this.site.desktopView}}
|
|
||||||
{{#if (eq (this.getStepState index) "completed")}}
|
|
||||||
{{dIcon "check"}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
{{#unless (eq index this.lastStepIndex)}}
|
|
||||||
<span class="signup-progress-bar__line"></span>
|
|
||||||
{{/unless}}
|
|
||||||
</div>
|
</div>
|
||||||
<span class="signup-progress-bar__step-text">
|
|
||||||
{{this.stepText step}}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<DButton
|
<DButton
|
||||||
@action={{@togglePasswordMask}}
|
@action={{@togglePasswordMask}}
|
||||||
@label={{if @maskPassword "login.show_password" "login.hide_password"}}
|
@icon={{if @maskPassword "far-eye" "far-eye-slash"}}
|
||||||
@title={{if
|
@title={{if
|
||||||
@maskPassword
|
@maskPassword
|
||||||
"login.show_password_title"
|
"login.show_password_title"
|
||||||
"login.hide_password_title"
|
"login.hide_password_title"
|
||||||
}}
|
}}
|
||||||
class="btn-link toggle-password-mask"
|
class="btn-transparent toggle-password-mask"
|
||||||
/>
|
/>
|
@ -29,6 +29,7 @@ export default class PasswordResetController extends Controller.extend(
|
|||||||
requiresApproval = false;
|
requiresApproval = false;
|
||||||
redirected = false;
|
redirected = false;
|
||||||
maskPassword = true;
|
maskPassword = true;
|
||||||
|
passwordValidationVisible = false;
|
||||||
|
|
||||||
lockImageUrl = getURL("/images/lock.svg");
|
lockImageUrl = getURL("/images/lock.svg");
|
||||||
|
|
||||||
@ -65,6 +66,30 @@ export default class PasswordResetController extends Controller.extend(
|
|||||||
return getURL(redirectTo || "/");
|
return getURL(redirectTo || "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@discourseComputed(
|
||||||
|
"passwordValidation.ok",
|
||||||
|
"passwordValidation.reason",
|
||||||
|
"passwordValidationVisible"
|
||||||
|
)
|
||||||
|
showPasswordValidation(
|
||||||
|
passwordValidationOk,
|
||||||
|
passwordValidationReason,
|
||||||
|
passwordValidationVisible
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
passwordValidationOk ||
|
||||||
|
(passwordValidationReason && passwordValidationVisible)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
togglePasswordValidation() {
|
||||||
|
this.set(
|
||||||
|
"passwordValidationVisible",
|
||||||
|
Boolean(this.passwordValidation.reason)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
done(event) {
|
done(event) {
|
||||||
if (wantsNewWindow(event)) {
|
if (wantsNewWindow(event)) {
|
||||||
|
@ -15,12 +15,17 @@ const SelectOption = <template>
|
|||||||
export default class FKControlSelect extends Component {
|
export default class FKControlSelect extends Component {
|
||||||
static controlType = "select";
|
static controlType = "select";
|
||||||
|
|
||||||
|
get includeNone() {
|
||||||
|
return this.args.field.validation !== "required";
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DSelect
|
<DSelect
|
||||||
class="form-kit__control-select"
|
class="form-kit__control-select"
|
||||||
disabled={{@field.disabled}}
|
disabled={{@field.disabled}}
|
||||||
@value={{@field.value}}
|
@value={{@field.value}}
|
||||||
@onChange={{@field.set}}
|
@onChange={{@field.set}}
|
||||||
|
@includeNone={{this.includeNone}}
|
||||||
...attributes
|
...attributes
|
||||||
>
|
>
|
||||||
{{yield (hash Option=(component SelectOption selected=@field.value))}}
|
{{yield (hash Option=(component SelectOption selected=@field.value))}}
|
||||||
|
@ -3337,7 +3337,7 @@ class PluginApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a component class that will be rendered within the AdminPageHeader component
|
* Registers a component class that will be rendered within the DPageHeader component
|
||||||
* only on plugins using the AdminPluginConfigPage and the new plugin "show" route.
|
* only on plugins using the AdminPluginConfigPage and the new plugin "show" route.
|
||||||
*
|
*
|
||||||
* This component will be passed an `@actions` argument, with Primary, Default, Danger,
|
* This component will be passed an `@actions` argument, with Primary, Default, Danger,
|
||||||
|
@ -134,6 +134,11 @@
|
|||||||
<label class="alt-placeholder" for="new-account-password">
|
<label class="alt-placeholder" for="new-account-password">
|
||||||
{{i18n "invites.password_label"}}
|
{{i18n "invites.password_label"}}
|
||||||
</label>
|
</label>
|
||||||
|
<TogglePasswordMask
|
||||||
|
@maskPassword={{this.maskPassword}}
|
||||||
|
@togglePasswordMask={{this.togglePasswordMask}}
|
||||||
|
@parentController="invites-show"
|
||||||
|
/>
|
||||||
<div class="create-account__password-info">
|
<div class="create-account__password-info">
|
||||||
<div class="create-account__password-tip-validation">
|
<div class="create-account__password-tip-validation">
|
||||||
<InputTip
|
<InputTip
|
||||||
@ -148,11 +153,6 @@
|
|||||||
{{i18n "login.caps_lock_warning"}}
|
{{i18n "login.caps_lock_warning"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TogglePasswordMask
|
|
||||||
@maskPassword={{this.maskPassword}}
|
|
||||||
@togglePasswordMask={{this.togglePasswordMask}}
|
|
||||||
@parentController="invites-show"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
@ -2,11 +2,7 @@
|
|||||||
{{hide-application-sidebar}}
|
{{hide-application-sidebar}}
|
||||||
{{hide-application-header-buttons "search" "login" "signup" "menu"}}
|
{{hide-application-header-buttons "search" "login" "signup" "menu"}}
|
||||||
<div class="container password-reset clearfix">
|
<div class="container password-reset clearfix">
|
||||||
<div class="pull-left col-image">
|
<form class="change-password-form login-left-side">
|
||||||
<img src={{this.lockImageUrl}} class="password-reset-img" alt="" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pull-left col-form">
|
|
||||||
{{#if this.successMessage}}
|
{{#if this.successMessage}}
|
||||||
<p>{{this.successMessage}}</p>
|
<p>{{this.successMessage}}</p>
|
||||||
|
|
||||||
@ -22,92 +18,95 @@
|
|||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<form class="change-password-form">
|
{{#if this.securityKeyOrSecondFactorRequired}}
|
||||||
{{#if this.securityKeyOrSecondFactorRequired}}
|
<h2>{{i18n "user.change_password.title"}}</h2>
|
||||||
<h2>{{i18n "user.change_password.title"}}</h2>
|
<p>
|
||||||
<p>
|
{{i18n "user.change_password.verify_identity"}}
|
||||||
{{i18n "user.change_password.verify_identity"}}
|
</p>
|
||||||
</p>
|
{{#if this.errorMessage}}
|
||||||
{{#if this.errorMessage}}
|
<div class="alert alert-error">{{this.errorMessage}}</div>
|
||||||
<div class="alert alert-error">{{this.errorMessage}}</div>
|
<br />
|
||||||
<br />
|
{{/if}}
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.displaySecurityKeyForm}}
|
{{#if this.displaySecurityKeyForm}}
|
||||||
<SecurityKeyForm
|
<SecurityKeyForm
|
||||||
@setSecondFactorMethod={{fn
|
@setSecondFactorMethod={{fn (mut this.selectedSecondFactorMethod)}}
|
||||||
(mut this.selectedSecondFactorMethod)
|
@backupEnabled={{this.backupEnabled}}
|
||||||
}}
|
@totpEnabled={{this.secondFactorRequired}}
|
||||||
@backupEnabled={{this.backupEnabled}}
|
@otherMethodAllowed={{this.otherMethodAllowed}}
|
||||||
@totpEnabled={{this.secondFactorRequired}}
|
@action={{this.authenticateSecurityKey}}
|
||||||
@otherMethodAllowed={{this.otherMethodAllowed}}
|
/>
|
||||||
@action={{this.authenticateSecurityKey}}
|
|
||||||
/>
|
|
||||||
{{else}}
|
|
||||||
<SecondFactorForm
|
|
||||||
@secondFactorMethod={{this.selectedSecondFactorMethod}}
|
|
||||||
@secondFactorToken={{this.secondFactorToken}}
|
|
||||||
@backupEnabled={{this.backupEnabled}}
|
|
||||||
@totpEnabled={{this.secondFactorRequired}}
|
|
||||||
@isLogin={{false}}
|
|
||||||
>
|
|
||||||
<SecondFactorInput
|
|
||||||
{{on
|
|
||||||
"input"
|
|
||||||
(with-event-value (fn (mut this.secondFactorToken)))
|
|
||||||
}}
|
|
||||||
@secondFactorMethod={{this.selectedSecondFactorMethod}}
|
|
||||||
value={{this.secondFactorToken}}
|
|
||||||
id="second-factor"
|
|
||||||
/>
|
|
||||||
</SecondFactorForm>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#unless this.displaySecurityKeyForm}}
|
|
||||||
<DButton
|
|
||||||
@action={{action "submit"}}
|
|
||||||
@label="submit"
|
|
||||||
type="submit"
|
|
||||||
class="btn-primary"
|
|
||||||
/>
|
|
||||||
{{/unless}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<h2>{{i18n "user.change_password.choose"}}</h2>
|
<SecondFactorForm
|
||||||
{{#if this.errorMessage}}
|
@secondFactorMethod={{this.selectedSecondFactorMethod}}
|
||||||
<div class="alert alert-error">{{this.errorMessage}}</div>
|
@secondFactorToken={{this.secondFactorToken}}
|
||||||
<br />
|
@backupEnabled={{this.backupEnabled}}
|
||||||
{{/if}}
|
@totpEnabled={{this.secondFactorRequired}}
|
||||||
|
@isLogin={{false}}
|
||||||
<div class="input">
|
>
|
||||||
<PasswordField
|
<SecondFactorInput
|
||||||
@value={{this.accountPassword}}
|
{{on
|
||||||
@capsLockOn={{this.capsLockOn}}
|
"input"
|
||||||
type={{if this.maskPassword "password" "text"}}
|
(with-event-value (fn (mut this.secondFactorToken)))
|
||||||
autofocus="autofocus"
|
}}
|
||||||
autocomplete="new-password"
|
@secondFactorMethod={{this.selectedSecondFactorMethod}}
|
||||||
id="new-account-password"
|
value={{this.secondFactorToken}}
|
||||||
|
id="second-factor"
|
||||||
/>
|
/>
|
||||||
|
</SecondFactorForm>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#unless this.displaySecurityKeyForm}}
|
||||||
|
<DButton
|
||||||
|
@action={{action "submit"}}
|
||||||
|
@label="submit"
|
||||||
|
type="submit"
|
||||||
|
class="btn-primary"
|
||||||
|
/>
|
||||||
|
{{/unless}}
|
||||||
|
{{else}}
|
||||||
|
<h2>{{i18n "user.change_password.choose_new"}}</h2>
|
||||||
|
{{#if this.errorMessage}}
|
||||||
|
<div class="alert alert-error">{{this.errorMessage}}</div>
|
||||||
|
<br />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<PasswordField
|
||||||
|
@value={{this.accountPassword}}
|
||||||
|
{{on "focusout" this.togglePasswordValidation}}
|
||||||
|
@capsLockOn={{this.capsLockOn}}
|
||||||
|
type={{if this.maskPassword "password" "text"}}
|
||||||
|
autofocus="autofocus"
|
||||||
|
autocomplete="new-password"
|
||||||
|
id="new-account-password"
|
||||||
|
/>
|
||||||
|
<div class="change-password__password-info">
|
||||||
|
<div class="change-password_tip-validation">
|
||||||
|
{{#if this.showPasswordValidation}}
|
||||||
|
<InputTip @validation={{this.passwordValidation}} />
|
||||||
|
{{/if}}
|
||||||
|
<div
|
||||||
|
class="caps-lock-warning {{unless this.capsLockOn 'hidden'}}"
|
||||||
|
>
|
||||||
|
{{d-icon "triangle-exclamation"}}
|
||||||
|
{{i18n "login.caps_lock_warning"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<TogglePasswordMask
|
<TogglePasswordMask
|
||||||
@maskPassword={{this.maskPassword}}
|
@maskPassword={{this.maskPassword}}
|
||||||
@togglePasswordMask={{this.togglePasswordMask}}
|
@togglePasswordMask={{this.togglePasswordMask}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="caps-lock-warning {{unless this.capsLockOn 'hidden'}}">
|
|
||||||
{{d-icon "triangle-exclamation"}}
|
|
||||||
{{i18n "login.caps_lock_warning"}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<InputTip @validation={{this.passwordValidation}} />
|
<DButton
|
||||||
|
@action={{action "submit"}}
|
||||||
<DButton
|
@label="user.change_password.set_password"
|
||||||
@action={{action "submit"}}
|
type="submit"
|
||||||
@label="user.change_password.set_password"
|
class="btn-primary"
|
||||||
type="submit"
|
/>
|
||||||
class="btn-primary"
|
{{/if}}
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
</form>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</form>
|
||||||
</div>
|
</div>
|
@ -134,7 +134,10 @@
|
|||||||
<label class="alt-placeholder" for="new-account-password">
|
<label class="alt-placeholder" for="new-account-password">
|
||||||
{{i18n "user.password.title"}}
|
{{i18n "user.password.title"}}
|
||||||
</label>
|
</label>
|
||||||
|
<TogglePasswordMask
|
||||||
|
@maskPassword={{this.maskPassword}}
|
||||||
|
@togglePasswordMask={{this.togglePasswordMask}}
|
||||||
|
/>
|
||||||
<div class="create-account__password-info">
|
<div class="create-account__password-info">
|
||||||
<div class="create-account__password-tip-validation">
|
<div class="create-account__password-tip-validation">
|
||||||
{{#if this.showPasswordValidation}}
|
{{#if this.showPasswordValidation}}
|
||||||
@ -159,10 +162,6 @@
|
|||||||
{{i18n "login.caps_lock_warning"}}
|
{{i18n "login.caps_lock_warning"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TogglePasswordMask
|
|
||||||
@maskPassword={{this.maskPassword}}
|
|
||||||
@togglePasswordMask={{this.togglePasswordMask}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
@ -71,28 +71,28 @@ acceptance("Admin - Users List", function (needs) {
|
|||||||
|
|
||||||
await visit("/admin/users/list/active");
|
await visit("/admin/users/list/active");
|
||||||
|
|
||||||
assert.dom(".admin-page-subheader__title").hasText(activeTitle);
|
assert.dom(".d-page-subheader__title").hasText(activeTitle);
|
||||||
assert
|
assert
|
||||||
.dom(".users-list .user:nth-child(1) .username")
|
.dom(".users-list .user:nth-child(1) .username")
|
||||||
.includesText(activeUser);
|
.includesText(activeUser);
|
||||||
|
|
||||||
await click('a[href="/admin/users/list/new"]');
|
await click('a[href="/admin/users/list/new"]');
|
||||||
|
|
||||||
assert.dom(".admin-page-subheader__title").hasText(suspectTitle);
|
assert.dom(".d-page-subheader__title").hasText(suspectTitle);
|
||||||
assert
|
assert
|
||||||
.dom(".users-list .user:nth-child(1) .username")
|
.dom(".users-list .user:nth-child(1) .username")
|
||||||
.includesText(suspectUser);
|
.includesText(suspectUser);
|
||||||
|
|
||||||
await click(".users-list .sortable:nth-child(4)");
|
await click(".users-list .sortable:nth-child(4)");
|
||||||
|
|
||||||
assert.dom(".admin-page-subheader__title").hasText(suspectTitle);
|
assert.dom(".d-page-subheader__title").hasText(suspectTitle);
|
||||||
assert
|
assert
|
||||||
.dom(".users-list .user:nth-child(1) .username")
|
.dom(".users-list .user:nth-child(1) .username")
|
||||||
.includesText(suspectUser);
|
.includesText(suspectUser);
|
||||||
|
|
||||||
await click('a[href="/admin/users/list/active"]');
|
await click('a[href="/admin/users/list/active"]');
|
||||||
|
|
||||||
assert.dom(".admin-page-subheader__title").hasText(activeTitle);
|
assert.dom(".d-page-subheader__title").hasText(activeTitle);
|
||||||
assert
|
assert
|
||||||
.dom(".users-list .user:nth-child(1) .username")
|
.dom(".users-list .user:nth-child(1) .username")
|
||||||
.includesText(activeUser);
|
.includesText(activeUser);
|
||||||
|
@ -7,6 +7,10 @@ let userFound = false;
|
|||||||
|
|
||||||
acceptance("Forgot password", function (needs) {
|
acceptance("Forgot password", function (needs) {
|
||||||
needs.pretender((server, helper) => {
|
needs.pretender((server, helper) => {
|
||||||
|
needs.settings({
|
||||||
|
hide_email_address_taken: false,
|
||||||
|
});
|
||||||
|
|
||||||
server.post("/session/forgot_password", () => {
|
server.post("/session/forgot_password", () => {
|
||||||
return helper.response({
|
return helper.response({
|
||||||
user_found: userFound,
|
user_found: userFound,
|
||||||
@ -78,6 +82,10 @@ acceptance("Forgot password", function (needs) {
|
|||||||
acceptance(
|
acceptance(
|
||||||
"Forgot password - hide_email_address_taken enabled",
|
"Forgot password - hide_email_address_taken enabled",
|
||||||
function (needs) {
|
function (needs) {
|
||||||
|
needs.settings({
|
||||||
|
hide_email_address_taken: true,
|
||||||
|
});
|
||||||
|
|
||||||
needs.pretender((server, helper) => {
|
needs.pretender((server, helper) => {
|
||||||
server.post("/session/forgot_password", () => {
|
server.post("/session/forgot_password", () => {
|
||||||
return helper.response({});
|
return helper.response({});
|
||||||
@ -93,12 +101,12 @@ acceptance(
|
|||||||
.dom(".forgot-password-reset")
|
.dom(".forgot-password-reset")
|
||||||
.isDisabled("disables the button until the field is filled");
|
.isDisabled("disables the button until the field is filled");
|
||||||
|
|
||||||
await fillIn("#username-or-email", "someuser");
|
await fillIn("#username-or-email", "someuser@discourse.org");
|
||||||
await click(".forgot-password-reset");
|
await click(".forgot-password-reset");
|
||||||
|
|
||||||
assert.dom(".d-modal__body").hasHtml(
|
assert.dom(".d-modal__body").hasHtml(
|
||||||
i18n("forgot_password.complete_username", {
|
i18n("forgot_password.complete_email", {
|
||||||
username: "someuser",
|
email: "someuser@discourse.org",
|
||||||
}),
|
}),
|
||||||
"displays a success message"
|
"displays a success message"
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { click, fillIn, visit } from "@ember/test-helpers";
|
import { blur, click, fillIn, visit } from "@ember/test-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import sinon from "sinon";
|
import sinon from "sinon";
|
||||||
import PreloadStore from "discourse/lib/preload-store";
|
import PreloadStore from "discourse/lib/preload-store";
|
||||||
@ -71,6 +71,8 @@ acceptance("Password Reset", function (needs) {
|
|||||||
assert.dom(".password-reset .tip.good").exists("input looks good");
|
assert.dom(".password-reset .tip.good").exists("input looks good");
|
||||||
|
|
||||||
await fillIn(".password-reset input", "123");
|
await fillIn(".password-reset input", "123");
|
||||||
|
await blur(".password-reset input");
|
||||||
|
|
||||||
assert.dom(".password-reset .tip.bad").exists("input is not valid");
|
assert.dom(".password-reset .tip.bad").exists("input is not valid");
|
||||||
assert.dom(".password-reset .tip.bad").includesHtml(
|
assert.dom(".password-reset .tip.bad").includesHtml(
|
||||||
i18n("user.password.too_short", {
|
i18n("user.password.too_short", {
|
||||||
|
@ -4,7 +4,6 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
|||||||
import AdminConfigAreaCard from "admin/components/admin-config-area-card";
|
import AdminConfigAreaCard from "admin/components/admin-config-area-card";
|
||||||
|
|
||||||
module("Integration | Component | AdminConfigAreaCard", function (hooks) {
|
module("Integration | Component | AdminConfigAreaCard", function (hooks) {
|
||||||
hooks.beforeEach(function () {});
|
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
test("renders admin config area card without toggle button", async function (assert) {
|
test("renders admin config area card without toggle button", async function (assert) {
|
||||||
|
@ -1,177 +0,0 @@
|
|||||||
import { click, render } from "@ember/test-helpers";
|
|
||||||
import { module, test } from "qunit";
|
|
||||||
import { forceMobile } from "discourse/lib/mobile";
|
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
import AdminPageSubheader from "admin/components/admin-page-subheader";
|
|
||||||
|
|
||||||
module("Integration | Component | AdminPageSubheader", function (hooks) {
|
|
||||||
setupRenderingTest(hooks);
|
|
||||||
|
|
||||||
test("@titleLabel", async function (assert) {
|
|
||||||
await render(<template>
|
|
||||||
<AdminPageSubheader @titleLabel="admin.title" />
|
|
||||||
</template>);
|
|
||||||
assert
|
|
||||||
.dom(".admin-page-subheader__title")
|
|
||||||
.exists()
|
|
||||||
.hasText(i18n("admin.title"));
|
|
||||||
});
|
|
||||||
|
|
||||||
test("@titleLabelTranslated", async function (assert) {
|
|
||||||
await render(<template>
|
|
||||||
<AdminPageSubheader @titleLabelTranslated="Wow so cool" />
|
|
||||||
</template>);
|
|
||||||
assert.dom(".admin-page-subheader__title").exists().hasText("Wow so cool");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("no @descriptionLabel and no @descriptionLabelTranslated", async function (assert) {
|
|
||||||
await render(<template><AdminPageSubheader /></template>);
|
|
||||||
assert.dom(".admin-page-subheader__description").doesNotExist();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("@descriptionLabel", async function (assert) {
|
|
||||||
await render(<template>
|
|
||||||
<AdminPageSubheader @descriptionLabel="admin.badges.description" />
|
|
||||||
</template>);
|
|
||||||
assert
|
|
||||||
.dom(".admin-page-subheader__description")
|
|
||||||
.exists()
|
|
||||||
.hasText(i18n("admin.badges.description"));
|
|
||||||
});
|
|
||||||
|
|
||||||
test("@descriptionLabelTranslated", async function (assert) {
|
|
||||||
await render(<template>
|
|
||||||
<AdminPageSubheader
|
|
||||||
@descriptionLabelTranslated="Some description which supports <strong>HTML</strong>"
|
|
||||||
/>
|
|
||||||
</template>);
|
|
||||||
assert
|
|
||||||
.dom(".admin-page-subheader__description")
|
|
||||||
.exists()
|
|
||||||
.hasText("Some description which supports HTML");
|
|
||||||
assert.dom(".admin-page-subheader__description strong").exists();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("no @learnMoreUrl", async function (assert) {
|
|
||||||
await render(<template><AdminPageSubheader /></template>);
|
|
||||||
assert.dom(".admin-page-subheader__learn-more").doesNotExist();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("@learnMoreUrl", async function (assert) {
|
|
||||||
await render(<template>
|
|
||||||
<AdminPageSubheader
|
|
||||||
@descriptionLabel="admin.badges.description"
|
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/96331"
|
|
||||||
/>
|
|
||||||
</template>);
|
|
||||||
assert.dom(".admin-page-subheader__learn-more").exists();
|
|
||||||
assert
|
|
||||||
.dom(".admin-page-subheader__learn-more a")
|
|
||||||
.hasText("Learn more…")
|
|
||||||
.hasAttribute("href", "https://meta.discourse.org/t/96331");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("renders all types of action buttons in yielded <:actions>", async function (assert) {
|
|
||||||
let actionCalled = false;
|
|
||||||
const someAction = () => {
|
|
||||||
actionCalled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
await render(<template>
|
|
||||||
<AdminPageSubheader>
|
|
||||||
<:actions as |actions|>
|
|
||||||
<actions.Primary
|
|
||||||
@route="adminBadges.show"
|
|
||||||
@routeModels="new"
|
|
||||||
@icon="plus"
|
|
||||||
@label="admin.badges.new"
|
|
||||||
class="new-badge"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<actions.Default
|
|
||||||
@route="adminBadges.award"
|
|
||||||
@routeModels="new"
|
|
||||||
@icon="upload"
|
|
||||||
@label="admin.badges.mass_award.title"
|
|
||||||
class="award-badge"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<actions.Danger
|
|
||||||
@action={{someAction}}
|
|
||||||
@title="admin.badges.group_settings"
|
|
||||||
@label="admin.badges.group_settings"
|
|
||||||
@icon="gear"
|
|
||||||
class="edit-groupings-btn"
|
|
||||||
/>
|
|
||||||
</:actions>
|
|
||||||
</AdminPageSubheader>
|
|
||||||
</template>);
|
|
||||||
|
|
||||||
assert
|
|
||||||
.dom(
|
|
||||||
".admin-page-subheader__actions .admin-page-action-button.new-badge.btn.btn-small.btn-primary"
|
|
||||||
)
|
|
||||||
.exists();
|
|
||||||
assert
|
|
||||||
.dom(
|
|
||||||
".admin-page-subheader__actions .admin-page-action-button.award-badge.btn.btn-small.btn-default"
|
|
||||||
)
|
|
||||||
.exists();
|
|
||||||
assert
|
|
||||||
.dom(
|
|
||||||
".admin-page-subheader__actions .admin-page-action-button.edit-groupings-btn.btn.btn-small.btn-danger"
|
|
||||||
)
|
|
||||||
.exists();
|
|
||||||
|
|
||||||
await click(".edit-groupings-btn");
|
|
||||||
assert.true(actionCalled);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module(
|
|
||||||
"Integration | Component | AdminPageSubheader | Mobile",
|
|
||||||
function (hooks) {
|
|
||||||
hooks.beforeEach(function () {
|
|
||||||
forceMobile();
|
|
||||||
});
|
|
||||||
|
|
||||||
setupRenderingTest(hooks);
|
|
||||||
|
|
||||||
test("action buttons become a dropdown on mobile", async function (assert) {
|
|
||||||
await render(<template>
|
|
||||||
<AdminPageSubheader>
|
|
||||||
<:actions as |actions|>
|
|
||||||
<actions.Primary
|
|
||||||
@route="adminBadges.show"
|
|
||||||
@routeModels="new"
|
|
||||||
@icon="plus"
|
|
||||||
@label="admin.badges.new"
|
|
||||||
class="new-badge"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<actions.Default
|
|
||||||
@route="adminBadges.award"
|
|
||||||
@routeModels="new"
|
|
||||||
@icon="upload"
|
|
||||||
@label="admin.badges.mass_award.title"
|
|
||||||
class="award-badge"
|
|
||||||
/>
|
|
||||||
</:actions>
|
|
||||||
</AdminPageSubheader>
|
|
||||||
</template>);
|
|
||||||
|
|
||||||
assert
|
|
||||||
.dom(
|
|
||||||
".admin-page-subheader .fk-d-menu__trigger.admin-page-subheader-mobile-actions-trigger"
|
|
||||||
)
|
|
||||||
.exists();
|
|
||||||
|
|
||||||
await click(".admin-page-subheader-mobile-actions-trigger");
|
|
||||||
|
|
||||||
assert
|
|
||||||
.dom(".dropdown-menu.admin-page-subheader__mobile-actions .new-badge")
|
|
||||||
.exists();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -166,7 +166,7 @@ module("Integration | Component | CreateInvite", function (hooks) {
|
|||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
formKit().field("expiresAfterDays").options(),
|
formKit().field("expiresAfterDays").options(),
|
||||||
["__NONE__", "1", "3", "7", "30", "90", "999999"],
|
["1", "3", "7", "30", "90", "999999"],
|
||||||
"the value of invite_expiry_days is added to the dropdown"
|
"the value of invite_expiry_days is added to the dropdown"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ module("Integration | Component | CreateInvite", function (hooks) {
|
|||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
formKit().field("expiresAfterDays").options(),
|
formKit().field("expiresAfterDays").options(),
|
||||||
["__NONE__", "1", "7", "30", "90", "999999"],
|
["1", "7", "30", "90", "999999"],
|
||||||
"the value of invite_expiry_days is not added to the dropdown if it's already one of the options"
|
"the value of invite_expiry_days is not added to the dropdown if it's already one of the options"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { click, render } from "@ember/test-helpers";
|
import { click, render } from "@ember/test-helpers";
|
||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||||
|
import DPageHeader from "discourse/components/d-page-header";
|
||||||
import NavItem from "discourse/components/nav-item";
|
import NavItem from "discourse/components/nav-item";
|
||||||
import { forceMobile } from "discourse/lib/mobile";
|
import { forceMobile } from "discourse/lib/mobile";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
import AdminPageHeader from "admin/components/admin-page-header";
|
|
||||||
|
|
||||||
const AdminPageHeaderActionsTestComponent = <template>
|
const DPageHeaderActionsTestComponent = <template>
|
||||||
<div class="admin-page-header-actions-test-component">
|
<div class="d-page-header-actions-test-component">
|
||||||
<@actions.Default
|
<@actions.Default
|
||||||
@route="adminBadges.award"
|
@route="adminBadges.award"
|
||||||
@routeModels="new"
|
@routeModels="new"
|
||||||
@ -19,125 +19,96 @@ const AdminPageHeaderActionsTestComponent = <template>
|
|||||||
</div>
|
</div>
|
||||||
</template>;
|
</template>;
|
||||||
|
|
||||||
module("Integration | Component | AdminPageHeader", function (hooks) {
|
module("Integration | Component | DPageHeader", function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
test("no @titleLabel or @titleLabelTranslated", async function (assert) {
|
test("no @titleLabel", async function (assert) {
|
||||||
await render(<template><AdminPageHeader /></template>);
|
await render(<template><DPageHeader /></template>);
|
||||||
assert.dom(".admin-page-header__title").doesNotExist();
|
assert.dom(".d-page-header__title").doesNotExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("@titleLabel", async function (assert) {
|
test("@titleLabel", async function (assert) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<AdminPageHeader @titleLabel="admin.title" />
|
<DPageHeader @titleLabel={{i18n "admin.title"}} />
|
||||||
</template>);
|
</template>);
|
||||||
assert
|
assert.dom(".d-page-header__title").exists().hasText(i18n("admin.title"));
|
||||||
.dom(".admin-page-header__title")
|
|
||||||
.exists()
|
|
||||||
.hasText(i18n("admin.title"));
|
|
||||||
});
|
|
||||||
|
|
||||||
test("@titleLabelTranslated", async function (assert) {
|
|
||||||
await render(<template>
|
|
||||||
<AdminPageHeader @titleLabelTranslated="Wow so cool" />
|
|
||||||
</template>);
|
|
||||||
assert.dom(".admin-page-header__title").exists().hasText("Wow so cool");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("@shouldDisplay", async function (assert) {
|
test("@shouldDisplay", async function (assert) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<AdminPageHeader
|
<DPageHeader @titleLabel="Wow so cool" @shouldDisplay={{false}} />
|
||||||
@titleLabelTranslated="Wow so cool"
|
|
||||||
@shouldDisplay={{false}}
|
|
||||||
/>
|
|
||||||
</template>);
|
</template>);
|
||||||
assert.dom(".admin-page-header").doesNotExist();
|
assert.dom(".admin-page-header").doesNotExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("renders base breadcrumbs and yielded <:breadcrumbs>", async function (assert) {
|
test("renders base breadcrumbs and yielded <:breadcrumbs>", async function (assert) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<AdminPageHeader @titleLabel="admin.titile">
|
<DPageHeader @titleLabel={{i18n "admin.titile"}}>
|
||||||
<:breadcrumbs>
|
<:breadcrumbs>
|
||||||
<DBreadcrumbsItem
|
<DBreadcrumbsItem
|
||||||
@path="/admin/badges"
|
@path="/admin/badges"
|
||||||
@label={{i18n "admin.badges.title"}}
|
@label={{i18n "admin.badges.title"}}
|
||||||
/>
|
/>
|
||||||
</:breadcrumbs>
|
</:breadcrumbs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
</template>);
|
</template>);
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom(".admin-page-header__breadcrumbs .d-breadcrumbs__item")
|
.dom(".d-page-header__breadcrumbs .d-breadcrumbs__item")
|
||||||
.exists({ count: 2 });
|
.exists({ count: 1 });
|
||||||
assert
|
assert
|
||||||
.dom(".admin-page-header__breadcrumbs .d-breadcrumbs__item")
|
.dom(".d-page-header__breadcrumbs .d-breadcrumbs__item:last-child")
|
||||||
.hasText(i18n("admin_title"));
|
|
||||||
assert
|
|
||||||
.dom(".admin-page-header__breadcrumbs .d-breadcrumbs__item:last-child")
|
|
||||||
.hasText(i18n("admin.badges.title"));
|
.hasText(i18n("admin.badges.title"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("no @descriptionLabel and no @descriptionLabelTranslated", async function (assert) {
|
test("no @descriptionLabel", async function (assert) {
|
||||||
await render(<template><AdminPageHeader /></template>);
|
await render(<template><DPageHeader /></template>);
|
||||||
assert.dom(".admin-page-header__description").doesNotExist();
|
assert.dom(".d-page-header__description").doesNotExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("@descriptionLabel", async function (assert) {
|
test("@descriptionLabel", async function (assert) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<AdminPageHeader @descriptionLabel="admin.badges.description" />
|
<DPageHeader @descriptionLabel={{i18n "admin.badges.description"}} />
|
||||||
</template>);
|
</template>);
|
||||||
assert
|
assert
|
||||||
.dom(".admin-page-header__description")
|
.dom(".d-page-header__description")
|
||||||
.exists()
|
.exists()
|
||||||
.hasText(i18n("admin.badges.description"));
|
.hasText(i18n("admin.badges.description"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("@descriptionLabelTranslated", async function (assert) {
|
|
||||||
await render(<template>
|
|
||||||
<AdminPageHeader
|
|
||||||
@descriptionLabelTranslated="Some description which supports <strong>HTML</strong>"
|
|
||||||
/>
|
|
||||||
</template>);
|
|
||||||
assert
|
|
||||||
.dom(".admin-page-header__description")
|
|
||||||
.exists()
|
|
||||||
.hasText("Some description which supports HTML");
|
|
||||||
assert.dom(".admin-page-header__description strong").exists();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("no @learnMoreUrl", async function (assert) {
|
test("no @learnMoreUrl", async function (assert) {
|
||||||
await render(<template><AdminPageHeader /></template>);
|
await render(<template><DPageHeader /></template>);
|
||||||
assert.dom(".admin-page-header__learn-more").doesNotExist();
|
assert.dom(".d-page-header__learn-more").doesNotExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("@learnMoreUrl", async function (assert) {
|
test("@learnMoreUrl", async function (assert) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<AdminPageHeader
|
<DPageHeader
|
||||||
@descriptionLabel="admin.badges.description"
|
@descriptionLabel={{i18n "admin.badges.description"}}
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/96331"
|
@learnMoreUrl="https://meta.discourse.org/t/96331"
|
||||||
/>
|
/>
|
||||||
</template>);
|
</template>);
|
||||||
assert.dom(".admin-page-header__learn-more").exists();
|
assert.dom(".d-page-header__learn-more").exists();
|
||||||
assert
|
assert
|
||||||
.dom(".admin-page-header__learn-more a")
|
.dom(".d-page-header__learn-more a")
|
||||||
.hasText("Learn more…")
|
.hasText("Learn more…")
|
||||||
.hasAttribute("href", "https://meta.discourse.org/t/96331");
|
.hasAttribute("href", "https://meta.discourse.org/t/96331");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("renders nav tabs in yielded <:tabs>", async function (assert) {
|
test("renders nav tabs in yielded <:tabs>", async function (assert) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<AdminPageHeader>
|
<DPageHeader>
|
||||||
<:tabs>
|
<:tabs>
|
||||||
<NavItem
|
<NavItem
|
||||||
@route="admin.backups.settings"
|
@route="admin.backups.settings"
|
||||||
@label="settings"
|
@label="settings"
|
||||||
class="admin-backups-tabs__settings"
|
class="d-backups-tabs__settings"
|
||||||
/>
|
/>
|
||||||
</:tabs>
|
</:tabs>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
</template>);
|
</template>);
|
||||||
assert
|
assert
|
||||||
.dom(".admin-nav-submenu__tabs .admin-backups-tabs__settings")
|
.dom(".d-nav-submenu__tabs .d-backups-tabs__settings")
|
||||||
.exists()
|
.exists()
|
||||||
.hasText(i18n("settings"));
|
.hasText(i18n("settings"));
|
||||||
});
|
});
|
||||||
@ -149,7 +120,7 @@ module("Integration | Component | AdminPageHeader", function (hooks) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<AdminPageHeader>
|
<DPageHeader>
|
||||||
<:actions as |actions|>
|
<:actions as |actions|>
|
||||||
<actions.Primary
|
<actions.Primary
|
||||||
@route="adminBadges.show"
|
@route="adminBadges.show"
|
||||||
@ -175,22 +146,22 @@ module("Integration | Component | AdminPageHeader", function (hooks) {
|
|||||||
class="edit-groupings-btn"
|
class="edit-groupings-btn"
|
||||||
/>
|
/>
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
</template>);
|
</template>);
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom(
|
.dom(
|
||||||
".admin-page-header__actions .admin-page-action-button.new-badge.btn.btn-small.btn-primary"
|
".d-page-header__actions .d-page-action-button.new-badge.btn.btn-small.btn-primary"
|
||||||
)
|
)
|
||||||
.exists();
|
.exists();
|
||||||
assert
|
assert
|
||||||
.dom(
|
.dom(
|
||||||
".admin-page-header__actions .admin-page-action-button.award-badge.btn.btn-small.btn-default"
|
".d-page-header__actions .d-page-action-button.award-badge.btn.btn-small.btn-default"
|
||||||
)
|
)
|
||||||
.exists();
|
.exists();
|
||||||
assert
|
assert
|
||||||
.dom(
|
.dom(
|
||||||
".admin-page-header__actions .admin-page-action-button.edit-groupings-btn.btn.btn-small.btn-danger"
|
".d-page-header__actions .d-page-action-button.edit-groupings-btn.btn.btn-small.btn-danger"
|
||||||
)
|
)
|
||||||
.exists();
|
.exists();
|
||||||
|
|
||||||
@ -200,18 +171,14 @@ module("Integration | Component | AdminPageHeader", function (hooks) {
|
|||||||
|
|
||||||
test("@headerActionComponent is rendered with actions arg", async function (assert) {
|
test("@headerActionComponent is rendered with actions arg", async function (assert) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<AdminPageHeader
|
<DPageHeader @headerActionComponent={{DPageHeaderActionsTestComponent}} />
|
||||||
@headerActionComponent={{AdminPageHeaderActionsTestComponent}}
|
|
||||||
/>
|
|
||||||
</template>);
|
</template>);
|
||||||
|
|
||||||
assert
|
assert.dom(".d-page-header-actions-test-component .award-badge").exists();
|
||||||
.dom(".admin-page-header-actions-test-component .award-badge")
|
|
||||||
.exists();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module("Integration | Component | AdminPageHeader | Mobile", function (hooks) {
|
module("Integration | Component | DPageHeader | Mobile", function (hooks) {
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
forceMobile();
|
forceMobile();
|
||||||
});
|
});
|
||||||
@ -220,7 +187,7 @@ module("Integration | Component | AdminPageHeader | Mobile", function (hooks) {
|
|||||||
|
|
||||||
test("action buttons become a dropdown on mobile", async function (assert) {
|
test("action buttons become a dropdown on mobile", async function (assert) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<AdminPageHeader>
|
<DPageHeader>
|
||||||
<:actions as |actions|>
|
<:actions as |actions|>
|
||||||
<actions.Primary
|
<actions.Primary
|
||||||
@route="adminBadges.show"
|
@route="adminBadges.show"
|
||||||
@ -238,19 +205,19 @@ module("Integration | Component | AdminPageHeader | Mobile", function (hooks) {
|
|||||||
class="award-badge"
|
class="award-badge"
|
||||||
/>
|
/>
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageHeader>
|
</DPageHeader>
|
||||||
</template>);
|
</template>);
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom(
|
.dom(
|
||||||
".admin-page-header__actions .fk-d-menu__trigger.admin-page-header-mobile-actions-trigger"
|
".d-page-header__actions .fk-d-menu__trigger.d-page-header-mobile-actions-trigger"
|
||||||
)
|
)
|
||||||
.exists();
|
.exists();
|
||||||
|
|
||||||
await click(".admin-page-header-mobile-actions-trigger");
|
await click(".d-page-header-mobile-actions-trigger");
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom(".dropdown-menu.admin-page-header__mobile-actions .new-badge")
|
.dom(".dropdown-menu.d-page-header__mobile-actions .new-badge")
|
||||||
.exists();
|
.exists();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -0,0 +1,154 @@
|
|||||||
|
import { click, render } from "@ember/test-helpers";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import DPageSubheader from "discourse/components/d-page-subheader";
|
||||||
|
import { forceMobile } from "discourse/lib/mobile";
|
||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
|
module("Integration | Component | DPageSubheader", function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test("@titleLabel", async function (assert) {
|
||||||
|
await render(<template>
|
||||||
|
<DPageSubheader @titleLabel={{i18n "admin.title"}} />
|
||||||
|
</template>);
|
||||||
|
assert
|
||||||
|
.dom(".d-page-subheader__title")
|
||||||
|
.exists()
|
||||||
|
.hasText(i18n("admin.title"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("no @descriptionLabel", async function (assert) {
|
||||||
|
await render(<template><DPageSubheader /></template>);
|
||||||
|
assert.dom(".d-page-subheader__description").doesNotExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("@descriptionLabel", async function (assert) {
|
||||||
|
await render(<template>
|
||||||
|
<DPageSubheader @descriptionLabel={{i18n "admin.badges.description"}} />
|
||||||
|
</template>);
|
||||||
|
assert
|
||||||
|
.dom(".d-page-subheader__description")
|
||||||
|
.exists()
|
||||||
|
.hasText(i18n("admin.badges.description"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("no @learnMoreUrl", async function (assert) {
|
||||||
|
await render(<template><DPageSubheader /></template>);
|
||||||
|
assert.dom(".d-page-subheader__learn-more").doesNotExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("@learnMoreUrl", async function (assert) {
|
||||||
|
await render(<template>
|
||||||
|
<DPageSubheader
|
||||||
|
@descriptionLabel={{i18n "admin.badges.description"}}
|
||||||
|
@learnMoreUrl="https://meta.discourse.org/t/96331"
|
||||||
|
/>
|
||||||
|
</template>);
|
||||||
|
assert.dom(".d-page-subheader__learn-more").exists();
|
||||||
|
assert
|
||||||
|
.dom(".d-page-subheader__learn-more a")
|
||||||
|
.hasText("Learn more…")
|
||||||
|
.hasAttribute("href", "https://meta.discourse.org/t/96331");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders all types of action buttons in yielded <:actions>", async function (assert) {
|
||||||
|
let actionCalled = false;
|
||||||
|
const someAction = () => {
|
||||||
|
actionCalled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
await render(<template>
|
||||||
|
<DPageSubheader>
|
||||||
|
<:actions as |actions|>
|
||||||
|
<actions.Primary
|
||||||
|
@route="adminBadges.show"
|
||||||
|
@routeModels="new"
|
||||||
|
@icon="plus"
|
||||||
|
@label="admin.badges.new"
|
||||||
|
class="new-badge"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<actions.Default
|
||||||
|
@route="adminBadges.award"
|
||||||
|
@routeModels="new"
|
||||||
|
@icon="upload"
|
||||||
|
@label="admin.badges.mass_award.title"
|
||||||
|
class="award-badge"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<actions.Danger
|
||||||
|
@action={{someAction}}
|
||||||
|
@title="admin.badges.group_settings"
|
||||||
|
@label="admin.badges.group_settings"
|
||||||
|
@icon="gear"
|
||||||
|
class="edit-groupings-btn"
|
||||||
|
/>
|
||||||
|
</:actions>
|
||||||
|
</DPageSubheader>
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(
|
||||||
|
".d-page-subheader__actions .d-page-action-button.new-badge.btn.btn-small.btn-primary"
|
||||||
|
)
|
||||||
|
.exists();
|
||||||
|
assert
|
||||||
|
.dom(
|
||||||
|
".d-page-subheader__actions .d-page-action-button.award-badge.btn.btn-small.btn-default"
|
||||||
|
)
|
||||||
|
.exists();
|
||||||
|
assert
|
||||||
|
.dom(
|
||||||
|
".d-page-subheader__actions .d-page-action-button.edit-groupings-btn.btn.btn-small.btn-danger"
|
||||||
|
)
|
||||||
|
.exists();
|
||||||
|
|
||||||
|
await click(".edit-groupings-btn");
|
||||||
|
assert.true(actionCalled);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module("Integration | Component | DPageSubheader | Mobile", function (hooks) {
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
forceMobile();
|
||||||
|
});
|
||||||
|
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test("action buttons become a dropdown on mobile", async function (assert) {
|
||||||
|
await render(<template>
|
||||||
|
<DPageSubheader>
|
||||||
|
<:actions as |actions|>
|
||||||
|
<actions.Primary
|
||||||
|
@route="adminBadges.show"
|
||||||
|
@routeModels="new"
|
||||||
|
@icon="plus"
|
||||||
|
@label="admin.badges.new"
|
||||||
|
class="new-badge"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<actions.Default
|
||||||
|
@route="adminBadges.award"
|
||||||
|
@routeModels="new"
|
||||||
|
@icon="upload"
|
||||||
|
@label="admin.badges.mass_award.title"
|
||||||
|
class="award-badge"
|
||||||
|
/>
|
||||||
|
</:actions>
|
||||||
|
</DPageSubheader>
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(
|
||||||
|
".d-page-subheader .fk-d-menu__trigger.d-page-subheader-mobile-actions-trigger"
|
||||||
|
)
|
||||||
|
.exists();
|
||||||
|
|
||||||
|
await click(".d-page-subheader-mobile-actions-trigger");
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(".dropdown-menu.d-page-subheader__mobile-actions .new-badge")
|
||||||
|
.exists();
|
||||||
|
});
|
||||||
|
});
|
@ -50,6 +50,16 @@ module("Integration | Component | d-select", function (hooks) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("required field", async function (assert) {
|
||||||
|
await render(<template>
|
||||||
|
<DSelect @includeNone={{false}} as |s|>
|
||||||
|
<s.Option @value="foo">The real foo</s.Option>
|
||||||
|
</DSelect>
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
assert.dselect().hasNoOption(NO_VALUE_OPTION);
|
||||||
|
});
|
||||||
|
|
||||||
test("select attributes", async function (assert) {
|
test("select attributes", async function (assert) {
|
||||||
await render(<template><DSelect class="test" /></template>);
|
await render(<template><DSelect class="test" /></template>);
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ module("Integration | Component | webhook-status", function (hooks) {
|
|||||||
assert.dom().hasText("Failed");
|
assert.dom().hasText("Failed");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("iconName", async function (assert) {
|
test("statusLabelClass", async function (assert) {
|
||||||
const webhook = new CoreFabricators(getOwner(this)).webhook();
|
const webhook = new CoreFabricators(getOwner(this)).webhook();
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<WebhookStatus
|
<WebhookStatus
|
||||||
@ -42,30 +42,18 @@ module("Integration | Component | webhook-status", function (hooks) {
|
|||||||
/>
|
/>
|
||||||
</template>);
|
</template>);
|
||||||
|
|
||||||
assert.dom(".d-icon-far-circle").exists();
|
assert.dom(".status-label").hasClass("--inactive");
|
||||||
|
|
||||||
webhook.set("last_delivery_status", 2);
|
webhook.set("last_delivery_status", 2);
|
||||||
|
|
||||||
await rerender();
|
await rerender();
|
||||||
|
assert.dom(".status-label").hasClass("--critical");
|
||||||
|
|
||||||
assert.dom(".d-icon-circle-xmark").exists();
|
webhook.set("last_delivery_status", 3);
|
||||||
});
|
|
||||||
|
|
||||||
test("iconClass", async function (assert) {
|
|
||||||
const webhook = new CoreFabricators(getOwner(this)).webhook();
|
|
||||||
await render(<template>
|
|
||||||
<WebhookStatus
|
|
||||||
@deliveryStatuses={{DELIVERY_STATUSES}}
|
|
||||||
@webhook={{webhook}}
|
|
||||||
/>
|
|
||||||
</template>);
|
|
||||||
|
|
||||||
assert.dom(".d-icon").hasClass("text-muted");
|
|
||||||
|
|
||||||
webhook.set("last_delivery_status", 2);
|
|
||||||
|
|
||||||
await rerender();
|
await rerender();
|
||||||
|
assert.dom(".status-label").hasClass("--success");
|
||||||
|
|
||||||
assert.dom(".d-icon").hasClass("text-danger");
|
webhook.set("last_delivery_status", 4);
|
||||||
|
await rerender();
|
||||||
|
assert.dom(".status-label").hasClass("--inactive");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
$mobile-breakpoint: 700px;
|
$mobile-breakpoint: 700px;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--space-1: 0.25rem;
|
--space-0: 0.125rem; //2px
|
||||||
|
--space-1: 0.25rem; //4px
|
||||||
--space-2: calc(0.25rem * 2);
|
--space-2: calc(0.25rem * 2);
|
||||||
--space-3: calc(0.25rem * 3);
|
--space-3: calc(0.25rem * 3);
|
||||||
--space-4: calc(0.25rem * 4);
|
--space-4: calc(0.25rem * 4);
|
||||||
|
@ -67,25 +67,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default
|
||||||
.status-label {
|
.status-label {
|
||||||
--d-border-radius: var(--space-4);
|
--d-border-radius: var(--space-4);
|
||||||
|
--status-icon-diameter: 8px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
background-color: var(--primary-low);
|
background-color: var(--primary-low);
|
||||||
padding: var(--space-1) var(--space-2);
|
padding: var(--space-0) var(--space-2);
|
||||||
border-radius: var(--d-border-radius);
|
border-radius: var(--d-border-radius);
|
||||||
|
|
||||||
.status-label-indicator {
|
.status-label-indicator {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 6px;
|
width: var(--status-icon-diameter);
|
||||||
height: 6px;
|
height: var(--status-icon-diameter);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: var(--primary-high);
|
background-color: var(--primary-high);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: var(--space-1);
|
margin-right: var(--space-1);
|
||||||
margin-top: 0.4rem;
|
margin-top: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-label-text {
|
.status-label-text {
|
||||||
@ -93,6 +95,45 @@
|
|||||||
font-size: var(--font-down-1);
|
font-size: var(--font-down-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Success badge
|
||||||
|
.status-label.--success {
|
||||||
|
background-color: var(--success-low);
|
||||||
|
|
||||||
|
.status-label-indicator {
|
||||||
|
background-color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label-text {
|
||||||
|
color: var(--success-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical badge
|
||||||
|
.status-label.--critical {
|
||||||
|
background-color: var(--danger-low);
|
||||||
|
|
||||||
|
.status-label-indicator {
|
||||||
|
background-color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label-text {
|
||||||
|
color: var(--danger-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inactive badge
|
||||||
|
.status-label.--inactive {
|
||||||
|
background-color: var(--primary-low);
|
||||||
|
|
||||||
|
.status-label-indicator {
|
||||||
|
background-color: var(--primary-high);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label-text {
|
||||||
|
color: var(--primary-high);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.d-admin-row__overview {
|
.d-admin-row__overview {
|
||||||
|
@ -1,44 +1,24 @@
|
|||||||
// Styles for admin/api
|
// Styles for admin/api
|
||||||
|
|
||||||
table.web-hooks.grid {
|
.d-admin-table.web-hooks {
|
||||||
td.delivery-status {
|
.d-admin-row__overview.payload-url {
|
||||||
div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.d-icon {
|
|
||||||
margin-right: 0.25em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
td.payload-url {
|
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
max-width: 55vw;
|
max-width: 20vw;
|
||||||
}
|
|
||||||
td.controls {
|
|
||||||
display: flex;
|
|
||||||
button {
|
|
||||||
margin-left: 0.25em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 550px) {
|
|
||||||
tbody {
|
|
||||||
tr {
|
|
||||||
grid-template-columns: 0.5fr repeat(2, 1fr) 0.5fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.controls {
|
@include breakpoint(medium) {
|
||||||
text-align: right;
|
max-width: 70vw;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@include breakpoint(mobile-extra-large) {
|
|
||||||
tbody {
|
.d-admin-row__detail.description {
|
||||||
tr {
|
@include breakpoint(medium) {
|
||||||
grid-template-columns: 0.5fr 1fr;
|
display: block;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
td.controls {
|
|
||||||
grid-row: 2;
|
.d-admin-row__mobile-label {
|
||||||
|
@include breakpoint(medium) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@
|
|||||||
float: left;
|
float: left;
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
|
|
||||||
&.admin-page-action-button {
|
&.d-page-action-button {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|
||||||
@media (max-width: $mobile-breakpoint) {
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
@ -806,6 +806,7 @@
|
|||||||
.admin-permalinks {
|
.admin-permalinks {
|
||||||
@include breakpoint(tablet) {
|
@include breakpoint(tablet) {
|
||||||
.admin-page-subheader,
|
.admin-page-subheader,
|
||||||
|
.d-page-subheader,
|
||||||
.admin-config-area,
|
.admin-config-area,
|
||||||
.admin-config-area__primary-content,
|
.admin-config-area__primary-content,
|
||||||
.loading-container {
|
.loading-container {
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
margin-bottom: var(--space-6);
|
margin-bottom: var(--space-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-nav-submenu {
|
.d-nav-submenu {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-bottom: 1px solid var(--primary-low);
|
border-bottom: 1px solid var(--primary-low);
|
||||||
|
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
body.login-page,
|
body.login-page,
|
||||||
body.signup-page,
|
body.signup-page,
|
||||||
body.invite-page {
|
body.invite-page,
|
||||||
|
body.password-reset-page {
|
||||||
& ~ .powered-by-discourse,
|
& ~ .powered-by-discourse,
|
||||||
.above-main-container-outlet {
|
.above-main-container-outlet {
|
||||||
display: none;
|
display: none;
|
||||||
@ -25,7 +26,8 @@ body.signup-page {
|
|||||||
|
|
||||||
.login-fullpage,
|
.login-fullpage,
|
||||||
.signup-fullpage,
|
.signup-fullpage,
|
||||||
.invites-show {
|
.invites-show,
|
||||||
|
.password-reset-page {
|
||||||
.signup-body,
|
.signup-body,
|
||||||
.login-body {
|
.login-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -241,8 +243,6 @@ body.signup-page {
|
|||||||
.caps-lock-warning {
|
.caps-lock-warning {
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
font-size: var(--font-down-1);
|
font-size: var(--font-down-1);
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-account__password-info {
|
.create-account__password-info {
|
||||||
|
@ -33,41 +33,39 @@ body.invite-page {
|
|||||||
// the second button can wrap in some locales, and this helps alignment
|
// the second button can wrap in some locales, and this helps alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-reset {
|
|
||||||
.instructions {
|
|
||||||
label {
|
|
||||||
color: var(--primary-medium);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#new-account-password {
|
|
||||||
width: 15em;
|
|
||||||
}
|
|
||||||
.tip {
|
|
||||||
margin: 0 0 0.5em;
|
|
||||||
}
|
|
||||||
.toggle-password-mask {
|
|
||||||
margin-left: 0.25em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.password-reset-page {
|
.password-reset-page {
|
||||||
.caps-lock-warning {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
.change-password-form {
|
.change-password-form {
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 400px;
|
||||||
|
input {
|
||||||
|
padding: 0.75em 0.77em;
|
||||||
|
min-width: 250px;
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.input {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
.tip {
|
.tip {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.signup-fullpage .input-group input[type="password"] {
|
||||||
|
padding-right: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
.toggle-password-mask {
|
.toggle-password-mask {
|
||||||
align-self: start;
|
position: absolute;
|
||||||
line-height: 1.4; // aligns with input description text
|
right: 0;
|
||||||
|
padding: 0.75em 0.77em; // alligns with input padding
|
||||||
.ios-device & {
|
.ios-device & {
|
||||||
// reset form-item-sizing mixin styles
|
// reset form-item-sizing mixin styles
|
||||||
padding-top: 0;
|
padding: 0.7em;
|
||||||
padding-bottom: 0;
|
|
||||||
font-size: var(--font-0);
|
font-size: var(--font-0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
@import "badges";
|
@import "badges";
|
||||||
@import "banner";
|
@import "banner";
|
||||||
@import "d-breadcrumbs";
|
@import "d-breadcrumbs";
|
||||||
|
@import "d-page-header";
|
||||||
@import "d-stat-tiles";
|
@import "d-stat-tiles";
|
||||||
@import "bookmark-list";
|
@import "bookmark-list";
|
||||||
@import "bookmark-modal";
|
@import "bookmark-modal";
|
||||||
|
92
app/assets/stylesheets/common/components/d-page-header.scss
Normal file
92
app/assets/stylesheets/common/components/d-page-header.scss
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
$mobile-breakpoint: 700px;
|
||||||
|
|
||||||
|
.d-page-header,
|
||||||
|
.d-page-subheader {
|
||||||
|
&__title-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: stretch;
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: var(--font-up-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-page-header__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: var(--space-2);
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-nav-submenu {
|
||||||
|
background: transparent;
|
||||||
|
border-bottom: 1px solid var(--primary-low);
|
||||||
|
|
||||||
|
.horizontal-overflow-nav {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-pills {
|
||||||
|
width: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-page-header {
|
||||||
|
&__title-row {
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.d-page-header__actions {
|
||||||
|
button {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-page-subheader {
|
||||||
|
&__title-row {
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-page-action-list-item {
|
||||||
|
.btn-primary {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,22 @@
|
|||||||
$progress-bar-line-width: 2px;
|
:root {
|
||||||
$progress-bar-circle-size: 1.2rem;
|
--progress-bar-line-width: 1px;
|
||||||
$progress-bar-icon-size: 0.8rem;
|
--progress-bar-circle-size: 0.5rem;
|
||||||
|
--progress-bar-icon-size: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.signup-progress-bar {
|
.signup-progress-bar {
|
||||||
width: 100%;
|
width: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-bottom: 1.2em;
|
margin-bottom: 1.2em;
|
||||||
|
gap: 1rem;
|
||||||
|
.account-created &,
|
||||||
|
.activate-account & {
|
||||||
|
margin-inline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__segment {
|
&__segment {
|
||||||
width: 100%;
|
width: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
@ -21,7 +28,7 @@ $progress-bar-icon-size: 0.8rem;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
width: $progress-bar-circle-size;
|
width: var(--progress-bar-circle-size);
|
||||||
.signup-progress-bar__circle {
|
.signup-progress-bar__circle {
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@ -33,66 +40,29 @@ $progress-bar-icon-size: 0.8rem;
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__step-text {
|
|
||||||
color: var(--primary-high);
|
|
||||||
white-space: nowrap;
|
|
||||||
width: fit-content;
|
|
||||||
transform: translateX(calc(calc($progress-bar-circle-size / 2) - 50%));
|
|
||||||
|
|
||||||
.signup-progress-bar__segment:first-child & {
|
|
||||||
transform: translateX(0%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-progress-bar__segment:last-child & {
|
|
||||||
transform: translateX(
|
|
||||||
calc(calc($progress-bar-circle-size + $progress-bar-line-width) - 100%)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.--active & {
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__line {
|
|
||||||
transform: translateY(
|
|
||||||
calc(calc($progress-bar-circle-size + $progress-bar-line-width) / 2)
|
|
||||||
);
|
|
||||||
height: $progress-bar-line-width;
|
|
||||||
width: 100%;
|
|
||||||
background-color: var(--primary-low-mid);
|
|
||||||
|
|
||||||
.--completed & {
|
|
||||||
background-color: var(--success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__circle {
|
&__circle {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: $progress-bar-icon-size;
|
font-size: var(--progress-bar-icon-size);
|
||||||
color: var(--secondary);
|
color: var(--secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: $progress-bar-circle-size;
|
height: var(--progress-bar-circle-size);
|
||||||
width: $progress-bar-circle-size;
|
width: var(--progress-bar-circle-size);
|
||||||
|
transform: none;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: $progress-bar-line-width solid var(--primary-low-mid);
|
border: var(--progress-bar-line-width) solid var(--primary-low-mid);
|
||||||
background-color: var(--secondary);
|
background-color: var(--secondary);
|
||||||
|
|
||||||
.--active & {
|
.--active & {
|
||||||
|
background-color: var(--success);
|
||||||
border-color: var(--success);
|
border-color: var(--success);
|
||||||
background: var(--success);
|
box-shadow: 0 0 1px calc(var(--progress-bar-circle-size) / 2)
|
||||||
box-shadow: 0 0 1px 5px var(--success-low);
|
var(--success-low);
|
||||||
}
|
}
|
||||||
.--completed & {
|
.--completed & {
|
||||||
background-color: var(--success);
|
background-color: var(--success);
|
||||||
border-color: var(--success);
|
border-color: var(--success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__line.--completed {
|
|
||||||
background-color: var(--success);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,22 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invited-by {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 1em 0;
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.invited-by p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
@ -55,3 +68,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invite-page {
|
||||||
|
background: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.invites-show,
|
||||||
|
#simple-container .invite-error {
|
||||||
|
max-width: 500px;
|
||||||
|
padding: 2rem 3rem;
|
||||||
|
background: var(--secondary);
|
||||||
|
margin: 0 auto;
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
margin: 1em auto 1em auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#simple-container .invite-error {
|
||||||
|
.error-info {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-image {
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
@import "compose";
|
@import "compose";
|
||||||
@import "discourse";
|
@import "discourse";
|
||||||
@import "header";
|
@import "header";
|
||||||
@import "invite-signup";
|
|
||||||
@import "latest-topic-list";
|
@import "latest-topic-list";
|
||||||
@import "login-signup-page";
|
@import "login-signup-page";
|
||||||
@import "menu-panel";
|
@import "menu-panel";
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
.invite-page {
|
|
||||||
background: var(--secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.invites-show,
|
|
||||||
#simple-container .invite-error {
|
|
||||||
max-width: 500px;
|
|
||||||
padding: 2rem 3rem;
|
|
||||||
background: var(--secondary);
|
|
||||||
margin: 10vh auto 1em auto;
|
|
||||||
@media screen and (max-height: 700px) {
|
|
||||||
margin: 1em auto 1em auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#simple-container .invite-error {
|
|
||||||
.error-info {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-image {
|
|
||||||
text-align: center;
|
|
||||||
padding-bottom: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,10 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
|
||||||
|
.signup-progress-bar {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.invitation-cta {
|
.invitation-cta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -97,11 +97,15 @@ class PostMover
|
|||||||
@first_post_number_moved =
|
@first_post_number_moved =
|
||||||
posts.first.is_first_post? ? posts[1]&.post_number : posts.first.post_number
|
posts.first.is_first_post? ? posts[1]&.post_number : posts.first.post_number
|
||||||
|
|
||||||
if @options[:freeze_original] # in this case we need to add the moderator post after the last copied post
|
if @options[:freeze_original]
|
||||||
from_posts = @original_topic.ordered_posts.where("post_number > ?", posts.last.post_number)
|
# in this case we need to add the moderator post after the last copied post
|
||||||
shift_post_numbers(from_posts) if !@full_move
|
if @full_move
|
||||||
|
@first_post_number_moved = @original_topic.ordered_posts.last.post_number + 1
|
||||||
@first_post_number_moved = posts.last.post_number + 1
|
else
|
||||||
|
from_posts = @original_topic.ordered_posts.where("post_number > ?", posts.last.post_number)
|
||||||
|
shift_post_numbers(from_posts)
|
||||||
|
@first_post_number_moved = posts.last.post_number + 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
move_each_post
|
move_each_post
|
||||||
@ -478,7 +482,9 @@ class PostMover
|
|||||||
def copy_shifted_post_timings_from_temp
|
def copy_shifted_post_timings_from_temp
|
||||||
DB.exec <<~SQL
|
DB.exec <<~SQL
|
||||||
INSERT INTO post_timings (topic_id, user_id, post_number, msecs)
|
INSERT INTO post_timings (topic_id, user_id, post_number, msecs)
|
||||||
SELECT DISTINCT topic_id, user_id, post_number, msecs FROM temp_post_timings
|
SELECT DISTINCT ON (topic_id, post_number, user_id) topic_id, user_id, post_number, msecs
|
||||||
|
FROM temp_post_timings
|
||||||
|
ORDER BY topic_id, post_number, user_id, msecs DESC
|
||||||
ON CONFLICT (topic_id, post_number, user_id) DO UPDATE
|
ON CONFLICT (topic_id, post_number, user_id) DO UPDATE
|
||||||
SET msecs = GREATEST(post_timings.msecs, excluded.msecs)
|
SET msecs = GREATEST(post_timings.msecs, excluded.msecs)
|
||||||
SQL
|
SQL
|
||||||
|
@ -235,7 +235,11 @@ module Discourse
|
|||||||
|
|
||||||
# Use discourse-fonts gem to symlink fonts and generate .scss file
|
# Use discourse-fonts gem to symlink fonts and generate .scss file
|
||||||
fonts_path = File.join(config.root, "public/fonts")
|
fonts_path = File.join(config.root, "public/fonts")
|
||||||
Discourse::Utils.atomic_ln_s(DiscourseFonts.path_for_fonts, fonts_path)
|
if !File.exist?(fonts_path) || File.realpath(fonts_path) != DiscourseFonts.path_for_fonts
|
||||||
|
puts "Symlinking fonts from discourse-fonts gem"
|
||||||
|
File.delete(fonts_path) if File.exist?(fonts_path)
|
||||||
|
Discourse::Utils.atomic_ln_s(DiscourseFonts.path_for_fonts, fonts_path)
|
||||||
|
end
|
||||||
|
|
||||||
require "stylesheet/manager"
|
require "stylesheet/manager"
|
||||||
require "svg_sprite"
|
require "svg_sprite"
|
||||||
|
@ -3275,6 +3275,7 @@ en:
|
|||||||
other: "%{count} posts in topic"
|
other: "%{count} posts in topic"
|
||||||
create: "New Topic"
|
create: "New Topic"
|
||||||
create_disabled_category: "You're not allowed to create topics in this category"
|
create_disabled_category: "You're not allowed to create topics in this category"
|
||||||
|
create_disabled_tag: "You're not allowed to create topics with this tag"
|
||||||
create_long: "Create a new Topic"
|
create_long: "Create a new Topic"
|
||||||
open_draft: "Open Draft"
|
open_draft: "Open Draft"
|
||||||
private_message: "Start a message"
|
private_message: "Start a message"
|
||||||
|
@ -621,7 +621,7 @@ login:
|
|||||||
list_type: simple
|
list_type: simple
|
||||||
hide_email_address_taken:
|
hide_email_address_taken:
|
||||||
client: true
|
client: true
|
||||||
default: false
|
default: true
|
||||||
log_out_strict: false
|
log_out_strict: false
|
||||||
pending_users_reminder_delay_minutes:
|
pending_users_reminder_delay_minutes:
|
||||||
min: -1
|
min: -1
|
||||||
|
@ -422,7 +422,7 @@ class TopicQuery
|
|||||||
|
|
||||||
def list_new_in_category(category)
|
def list_new_in_category(category)
|
||||||
create_list(:new_in_category, unordered: true, category: category.id) do |list|
|
create_list(:new_in_category, unordered: true, category: category.id) do |list|
|
||||||
list.by_newest.first(25)
|
list.by_newest.limit(25)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -119,6 +119,8 @@ describe "Core extensions" do
|
|||||||
|
|
||||||
describe "user custom fields" do
|
describe "user custom fields" do
|
||||||
it "supports discourse_automation_ids" do
|
it "supports discourse_automation_ids" do
|
||||||
|
SiteSetting.hide_email_address_taken = false
|
||||||
|
|
||||||
user = create_user
|
user = create_user
|
||||||
automation_1.add_id_to_custom_field(user, DiscourseAutomation::AUTOMATION_IDS_CUSTOM_FIELD)
|
automation_1.add_id_to_custom_field(user, DiscourseAutomation::AUTOMATION_IDS_CUSTOM_FIELD)
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="discourse-chat-incoming-webhooks admin-detail">
|
<div class="discourse-chat-incoming-webhooks admin-detail">
|
||||||
<AdminPageSubheader
|
<DPageSubheader
|
||||||
@titleLabel="chat.incoming_webhooks.title"
|
@titleLabel={{i18n "chat.incoming_webhooks.title"}}
|
||||||
@descriptionLabel="chat.incoming_webhooks.instructions"
|
@descriptionLabel={{i18n "chat.incoming_webhooks.instructions"}}
|
||||||
>
|
>
|
||||||
<:actions as |actions|>
|
<:actions as |actions|>
|
||||||
<actions.Primary
|
<actions.Primary
|
||||||
@ -18,7 +18,7 @@
|
|||||||
class="admin-incoming-webhooks-new"
|
class="admin-incoming-webhooks-new"
|
||||||
/>
|
/>
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageSubheader>
|
</DPageSubheader>
|
||||||
|
|
||||||
<div class="incoming-chat-webhooks">
|
<div class="incoming-chat-webhooks">
|
||||||
{{#if this.model.incoming_chat_webhooks}}
|
{{#if this.model.incoming_chat_webhooks}}
|
||||||
|
@ -6,7 +6,7 @@ describe "Admin Chat Incoming Webhooks", type: :system do
|
|||||||
|
|
||||||
let(:dialog) { PageObjects::Components::Dialog.new }
|
let(:dialog) { PageObjects::Components::Dialog.new }
|
||||||
let(:admin_incoming_webhooks_page) { PageObjects::Pages::AdminIncomingWebhooks.new }
|
let(:admin_incoming_webhooks_page) { PageObjects::Pages::AdminIncomingWebhooks.new }
|
||||||
let(:admin_header) { PageObjects::Components::AdminHeader.new }
|
let(:d_page_header) { PageObjects::Components::DPageHeader.new }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
chat_system_bootstrap(current_user)
|
chat_system_bootstrap(current_user)
|
||||||
@ -16,11 +16,11 @@ describe "Admin Chat Incoming Webhooks", type: :system do
|
|||||||
it "can create incoming webhooks" do
|
it "can create incoming webhooks" do
|
||||||
admin_incoming_webhooks_page.visit
|
admin_incoming_webhooks_page.visit
|
||||||
|
|
||||||
expect(admin_header).to be_visible
|
expect(d_page_header).to be_visible
|
||||||
|
|
||||||
admin_incoming_webhooks_page.click_new
|
admin_incoming_webhooks_page.click_new
|
||||||
|
|
||||||
expect(admin_header).to be_hidden
|
expect(d_page_header).to be_hidden
|
||||||
|
|
||||||
admin_incoming_webhooks_page.form.field("name").fill_in("Test webhook")
|
admin_incoming_webhooks_page.form.field("name").fill_in("Test webhook")
|
||||||
admin_incoming_webhooks_page.form.field("description").fill_in("Some test content")
|
admin_incoming_webhooks_page.form.field("description").fill_in("Some test content")
|
||||||
|
@ -3,14 +3,12 @@ import { hbs } from "ember-cli-htmlbars";
|
|||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
import sinon from "sinon";
|
import sinon from "sinon";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
import { i18n } from 'discourse-i18n';
|
import { i18n } from "discourse-i18n";
|
||||||
import { HEADER_INDICATOR_PREFERENCE_ALL_NEW } from "discourse/plugins/chat/discourse/controllers/preferences-chat";
|
import { HEADER_INDICATOR_PREFERENCE_ALL_NEW } from "discourse/plugins/chat/discourse/controllers/preferences-chat";
|
||||||
|
|
||||||
module("Discourse Chat | Component | chat-header-icon", function (hooks) {
|
module("Discourse Chat | Component | chat-header-icon", function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
hooks.beforeEach(function () {});
|
|
||||||
|
|
||||||
test("full page - never separated sidebar mode", async function (assert) {
|
test("full page - never separated sidebar mode", async function (assert) {
|
||||||
this.currentUser.user_option.chat_separate_sidebar_mode = "never";
|
this.currentUser.user_option.chat_separate_sidebar_mode = "never";
|
||||||
sinon
|
sinon
|
||||||
|
@ -5,6 +5,7 @@ RSpec.describe "invite only" do
|
|||||||
describe "#create invite only" do
|
describe "#create invite only" do
|
||||||
it "can create user via API" do
|
it "can create user via API" do
|
||||||
SiteSetting.invite_only = true
|
SiteSetting.invite_only = true
|
||||||
|
SiteSetting.hide_email_address_taken = false
|
||||||
Jobs.run_immediately!
|
Jobs.run_immediately!
|
||||||
|
|
||||||
admin = Fabricate(:admin)
|
admin = Fabricate(:admin)
|
||||||
|
@ -5,6 +5,8 @@ RSpec.describe EmailUpdater do
|
|||||||
let(:new_email) { "new.email@example.com" }
|
let(:new_email) { "new.email@example.com" }
|
||||||
|
|
||||||
it "provides better error message when a staged user has the same email" do
|
it "provides better error message when a staged user has the same email" do
|
||||||
|
SiteSetting.hide_email_address_taken = false
|
||||||
|
|
||||||
Fabricate(:user, staged: true, email: new_email)
|
Fabricate(:user, staged: true, email: new_email)
|
||||||
|
|
||||||
user = Fabricate(:user, email: old_email)
|
user = Fabricate(:user, email: old_email)
|
||||||
|
@ -20,6 +20,30 @@ RSpec.describe TopicQuery do
|
|||||||
fab!(:moderator)
|
fab!(:moderator)
|
||||||
fab!(:admin)
|
fab!(:admin)
|
||||||
|
|
||||||
|
before do
|
||||||
|
@plugin_instance = Plugin::Instance.new
|
||||||
|
@validator_blk =
|
||||||
|
lambda do |topics, options, query|
|
||||||
|
# this is notable, we do not send in a relation for suggested
|
||||||
|
# it would force us to completely rewrite SuggestedTopicsBuilder
|
||||||
|
expect(topics.is_a?(ActiveRecord::Relation)).to eq(true) if options[:filter] != :suggested
|
||||||
|
topics
|
||||||
|
end
|
||||||
|
DiscoursePluginRegistry.register_modifier(
|
||||||
|
@plugin_instance,
|
||||||
|
:topic_query_create_list_topics,
|
||||||
|
&@validator_blk
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
DiscoursePluginRegistry.unregister_modifier(
|
||||||
|
@plugin_instance,
|
||||||
|
:topic_query_create_list_topics,
|
||||||
|
&@validator_blk
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
describe "secure category" do
|
describe "secure category" do
|
||||||
it "filters categories out correctly" do
|
it "filters categories out correctly" do
|
||||||
category = Fabricate(:category_with_definition)
|
category = Fabricate(:category_with_definition)
|
||||||
@ -2228,22 +2252,28 @@ RSpec.describe TopicQuery do
|
|||||||
fab!(:topic1) { Fabricate(:topic, created_at: 3.days.ago, bumped_at: 1.hour.ago) }
|
fab!(:topic1) { Fabricate(:topic, created_at: 3.days.ago, bumped_at: 1.hour.ago) }
|
||||||
fab!(:topic2) { Fabricate(:topic, created_at: 2.days.ago, bumped_at: 3.hour.ago) }
|
fab!(:topic2) { Fabricate(:topic, created_at: 2.days.ago, bumped_at: 3.hour.ago) }
|
||||||
|
|
||||||
after { DiscoursePluginRegistry.clear_modifiers! }
|
|
||||||
|
|
||||||
it "allows changing" do
|
it "allows changing" do
|
||||||
original_topic_query = TopicQuery.new(user)
|
original_topic_query = TopicQuery.new(user)
|
||||||
|
plugin_instance = Plugin::Instance.new
|
||||||
Plugin::Instance
|
blk =
|
||||||
.new
|
lambda do |topics, options, topic_query|
|
||||||
.register_modifier(:topic_query_create_list_topics) do |topics, options, topic_query|
|
|
||||||
expect(topic_query).to eq(topic_query)
|
expect(topic_query).to eq(topic_query)
|
||||||
topic_query.options[:order] = "created"
|
topic_query.options[:order] = "created"
|
||||||
topics
|
topics
|
||||||
end
|
end
|
||||||
|
|
||||||
|
DiscoursePluginRegistry.register_modifier(
|
||||||
|
plugin_instance,
|
||||||
|
:topic_query_create_list_topics,
|
||||||
|
&blk
|
||||||
|
)
|
||||||
expect(original_topic_query.list_latest.topics.map(&:id)).to eq([topic1, topic2].map(&:id))
|
expect(original_topic_query.list_latest.topics.map(&:id)).to eq([topic1, topic2].map(&:id))
|
||||||
|
|
||||||
DiscoursePluginRegistry.clear_modifiers!
|
DiscoursePluginRegistry.unregister_modifier(
|
||||||
|
plugin_instance,
|
||||||
|
:topic_query_create_list_topics,
|
||||||
|
&blk
|
||||||
|
)
|
||||||
|
|
||||||
expect(original_topic_query.list_latest.topics.map(&:id)).to eq([topic2, topic1].map(&:id))
|
expect(original_topic_query.list_latest.topics.map(&:id)).to eq([topic2, topic1].map(&:id))
|
||||||
end
|
end
|
||||||
|
@ -117,6 +117,8 @@ RSpec.describe Invite do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "escapes the email_address when raising an existing user error" do
|
it "escapes the email_address when raising an existing user error" do
|
||||||
|
SiteSetting.hide_email_address_taken = false
|
||||||
|
|
||||||
user.email = xss_email
|
user.email = xss_email
|
||||||
user.save(validate: false)
|
user.save(validate: false)
|
||||||
|
|
||||||
|
@ -2898,7 +2898,7 @@ RSpec.describe PostMover do
|
|||||||
).to eq(true)
|
).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates the moderator message in the correct position" do
|
it "creates the moderator message in the correct position for partial move" do
|
||||||
PostMover.new(
|
PostMover.new(
|
||||||
original_topic,
|
original_topic,
|
||||||
Discourse.system_user,
|
Discourse.system_user,
|
||||||
@ -2908,11 +2908,34 @@ RSpec.describe PostMover do
|
|||||||
},
|
},
|
||||||
).to_topic(destination_topic.id)
|
).to_topic(destination_topic.id)
|
||||||
|
|
||||||
moderator_post =
|
# Moderator post is right after the second_post since it was the last post moved
|
||||||
original_topic.reload.ordered_posts.find_by(post_number: second_post.post_number + 1) # the next post
|
expect(
|
||||||
expect(moderator_post).to be_present
|
original_topic.ordered_posts.find_by(
|
||||||
expect(moderator_post.post_type).to eq(Post.types[:small_action])
|
post_number: second_post.post_number + 1,
|
||||||
expect(moderator_post.action_code).to eq("split_topic")
|
post_type: Post.types[:small_action],
|
||||||
|
action_code: "split_topic",
|
||||||
|
),
|
||||||
|
).to be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates the moderator message in the correct position for full move" do
|
||||||
|
small_action = Fabricate(:small_action, topic: original_topic)
|
||||||
|
PostMover.new(
|
||||||
|
original_topic,
|
||||||
|
Discourse.system_user,
|
||||||
|
[op.id, first_post.id, second_post.id, third_post.id],
|
||||||
|
options: {
|
||||||
|
freeze_original: true,
|
||||||
|
},
|
||||||
|
).to_topic(destination_topic.id)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
original_topic.ordered_posts.find_by(
|
||||||
|
post_number: small_action.post_number + 1,
|
||||||
|
post_type: Post.types[:small_action],
|
||||||
|
action_code: "split_topic",
|
||||||
|
),
|
||||||
|
).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with `post_mover_create_moderator_post` modifier" do
|
context "with `post_mover_create_moderator_post` modifier" do
|
||||||
|
@ -656,6 +656,8 @@ RSpec.describe "users" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
path "/session/forgot_password.json" do
|
path "/session/forgot_password.json" do
|
||||||
|
SiteSetting.hide_email_address_taken = false
|
||||||
|
|
||||||
post "Send password reset email" do
|
post "Send password reset email" do
|
||||||
tags "Users"
|
tags "Users"
|
||||||
operationId "sendPasswordResetEmail"
|
operationId "sendPasswordResetEmail"
|
||||||
|
@ -16,6 +16,8 @@ RSpec.describe SessionController do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before { SiteSetting.hide_email_address_taken = false }
|
||||||
|
|
||||||
describe "#email_login_info" do
|
describe "#email_login_info" do
|
||||||
let(:email_token) do
|
let(:email_token) do
|
||||||
Fabricate(:email_token, user: user, scope: EmailToken.scopes[:email_login])
|
Fabricate(:email_token, user: user, scope: EmailToken.scopes[:email_login])
|
||||||
|
@ -19,6 +19,8 @@ RSpec.describe UsersController do
|
|||||||
# late for fab! to work.
|
# late for fab! to work.
|
||||||
let(:user_deferred) { Fabricate(:user, refresh_auto_groups: true) }
|
let(:user_deferred) { Fabricate(:user, refresh_auto_groups: true) }
|
||||||
|
|
||||||
|
before { SiteSetting.hide_email_address_taken = false }
|
||||||
|
|
||||||
describe "#full account registration flow" do
|
describe "#full account registration flow" do
|
||||||
it "will correctly handle honeypot and challenge" do
|
it "will correctly handle honeypot and challenge" do
|
||||||
get "/session/hp.json"
|
get "/session/hp.json"
|
||||||
|
@ -207,12 +207,28 @@ RSpec.describe UsersEmailController do
|
|||||||
context "when new email is different case of existing email" do
|
context "when new email is different case of existing email" do
|
||||||
fab!(:other_user) { Fabricate(:user, email: "case.insensitive@gmail.com") }
|
fab!(:other_user) { Fabricate(:user, email: "case.insensitive@gmail.com") }
|
||||||
|
|
||||||
it "raises an error" do
|
context "when hiding taken e-mails" do
|
||||||
put "/u/#{user.username}/preferences/email.json",
|
before { SiteSetting.hide_email_address_taken = true }
|
||||||
params: {
|
|
||||||
email: other_user.email.upcase,
|
it "raises an error" do
|
||||||
}
|
put "/u/#{user.username}/preferences/email.json",
|
||||||
expect(response).to_not be_successful
|
params: {
|
||||||
|
email: other_user.email.upcase,
|
||||||
|
}
|
||||||
|
expect(response).to be_successful
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when revealing taken e-mails" do
|
||||||
|
before { SiteSetting.hide_email_address_taken = false }
|
||||||
|
|
||||||
|
it "raises an error" do
|
||||||
|
put "/u/#{user.username}/preferences/email.json",
|
||||||
|
params: {
|
||||||
|
email: other_user.email.upcase,
|
||||||
|
}
|
||||||
|
expect(response).to_not be_successful
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ describe "Admin Flags Page", type: :system do
|
|||||||
let(:admin_flags_page) { PageObjects::Pages::AdminFlags.new }
|
let(:admin_flags_page) { PageObjects::Pages::AdminFlags.new }
|
||||||
let(:admin_flag_form_page) { PageObjects::Pages::AdminFlagForm.new }
|
let(:admin_flag_form_page) { PageObjects::Pages::AdminFlagForm.new }
|
||||||
let(:flag_modal) { PageObjects::Modals::Flag.new }
|
let(:flag_modal) { PageObjects::Modals::Flag.new }
|
||||||
let(:admin_header) { PageObjects::Components::AdminHeader.new }
|
let(:d_page_header) { PageObjects::Components::DPageHeader.new }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in(admin)
|
sign_in(admin)
|
||||||
@ -27,7 +27,7 @@ describe "Admin Flags Page", type: :system do
|
|||||||
)
|
)
|
||||||
|
|
||||||
admin_flags_page.visit
|
admin_flags_page.visit
|
||||||
expect(admin_header).to be_visible
|
expect(d_page_header).to be_visible
|
||||||
|
|
||||||
admin_flags_page.toggle("spam")
|
admin_flags_page.toggle("spam")
|
||||||
topic_page.visit_topic(post.topic).open_flag_topic_modal
|
topic_page.visit_topic(post.topic).open_flag_topic_modal
|
||||||
@ -81,8 +81,7 @@ describe "Admin Flags Page", type: :system do
|
|||||||
expect(admin_flags_page).to have_add_flag_button_enabled
|
expect(admin_flags_page).to have_add_flag_button_enabled
|
||||||
|
|
||||||
admin_flags_page.click_add_flag
|
admin_flags_page.click_add_flag
|
||||||
|
expect(d_page_header).to be_hidden
|
||||||
expect(admin_header).to be_hidden
|
|
||||||
|
|
||||||
admin_flag_form_page
|
admin_flag_form_page
|
||||||
.fill_in_name("Vulgar")
|
.fill_in_name("Vulgar")
|
||||||
@ -115,7 +114,7 @@ describe "Admin Flags Page", type: :system do
|
|||||||
|
|
||||||
# update
|
# update
|
||||||
admin_flags_page.visit.click_edit_flag("custom_vulgar")
|
admin_flags_page.visit.click_edit_flag("custom_vulgar")
|
||||||
expect(admin_header).to be_hidden
|
expect(d_page_header).to be_hidden
|
||||||
admin_flag_form_page.fill_in_name("Tasteless").click_save
|
admin_flag_form_page.fill_in_name("Tasteless").click_save
|
||||||
|
|
||||||
expect(admin_flags_page).to have_flags(
|
expect(admin_flags_page).to have_flags(
|
||||||
@ -158,7 +157,7 @@ describe "Admin Flags Page", type: :system do
|
|||||||
it "has settings tab" do
|
it "has settings tab" do
|
||||||
admin_flags_page.visit
|
admin_flags_page.visit
|
||||||
|
|
||||||
expect(admin_header).to have_tabs(
|
expect(d_page_header).to have_tabs(
|
||||||
[I18n.t("admin_js.settings"), I18n.t("admin_js.admin.config_areas.flags.flags_tab")],
|
[I18n.t("admin_js.settings"), I18n.t("admin_js.admin.config_areas.flags.flags_tab")],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,11 +6,11 @@ describe "Admin User Fields", type: :system do
|
|||||||
before { sign_in(current_user) }
|
before { sign_in(current_user) }
|
||||||
|
|
||||||
let(:user_fields_page) { PageObjects::Pages::AdminUserFields.new }
|
let(:user_fields_page) { PageObjects::Pages::AdminUserFields.new }
|
||||||
let(:admin_header) { PageObjects::Components::AdminHeader.new }
|
let(:page_header) { PageObjects::Components::DPageHeader.new }
|
||||||
|
|
||||||
it "correctly saves user fields" do
|
it "correctly saves user fields" do
|
||||||
user_fields_page.visit
|
user_fields_page.visit
|
||||||
expect(admin_header).to be_visible
|
expect(page_header).to be_visible
|
||||||
user_fields_page.add_field(name: "Occupation", description: "What you do for work")
|
user_fields_page.add_field(name: "Occupation", description: "What you do for work")
|
||||||
|
|
||||||
expect(user_fields_page).to have_user_field("Occupation")
|
expect(user_fields_page).to have_user_field("Occupation")
|
||||||
@ -32,7 +32,7 @@ describe "Admin User Fields", type: :system do
|
|||||||
user_fields_page.visit
|
user_fields_page.visit
|
||||||
user_fields_page.click_add_field
|
user_fields_page.click_add_field
|
||||||
|
|
||||||
expect(admin_header).to be_hidden
|
expect(page_header).to be_hidden
|
||||||
|
|
||||||
form = page.find(".user-field")
|
form = page.find(".user-field")
|
||||||
editable_label = I18n.t("admin_js.admin.user_fields.editable.title")
|
editable_label = I18n.t("admin_js.admin.user_fields.editable.title")
|
||||||
@ -73,7 +73,7 @@ describe "Admin User Fields", type: :system do
|
|||||||
|
|
||||||
form.find(".user-field-name").fill_in(with: "Favourite Transformer")
|
form.find(".user-field-name").fill_in(with: "Favourite Transformer")
|
||||||
|
|
||||||
expect(admin_header).to be_hidden
|
expect(page_header).to be_hidden
|
||||||
|
|
||||||
form.find(".btn-primary").click
|
form.find(".btn-primary").click
|
||||||
|
|
||||||
|
@ -51,6 +51,8 @@ describe "Changing email", type: :system do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "works when user has totp 2fa" do
|
it "works when user has totp 2fa" do
|
||||||
|
SiteSetting.hide_email_address_taken = false
|
||||||
|
|
||||||
second_factor = Fabricate(:user_second_factor_totp, user: user)
|
second_factor = Fabricate(:user_second_factor_totp, user: user)
|
||||||
sign_in user
|
sign_in user
|
||||||
|
|
||||||
|
@ -10,7 +10,10 @@ shared_examples "login scenarios" do |login_page_object|
|
|||||||
fab!(:admin) { Fabricate(:admin, username: "admin", password: "supersecurepassword") }
|
fab!(:admin) { Fabricate(:admin, username: "admin", password: "supersecurepassword") }
|
||||||
let(:user_menu) { PageObjects::Components::UserMenu.new }
|
let(:user_menu) { PageObjects::Components::UserMenu.new }
|
||||||
|
|
||||||
before { Jobs.run_immediately! }
|
before do
|
||||||
|
SiteSetting.hide_email_address_taken = false
|
||||||
|
Jobs.run_immediately!
|
||||||
|
end
|
||||||
|
|
||||||
def wait_for_email_link(user, type)
|
def wait_for_email_link(user, type)
|
||||||
wait_for(timeout: 5) { ActionMailer::Base.deliveries.count != 0 }
|
wait_for(timeout: 5) { ActionMailer::Base.deliveries.count != 0 }
|
||||||
|
@ -2,17 +2,18 @@
|
|||||||
|
|
||||||
module PageObjects
|
module PageObjects
|
||||||
module Components
|
module Components
|
||||||
|
# TODO (martin) Delete this after plugins have been updated to use DPageHeader
|
||||||
class AdminHeader < PageObjects::Pages::Base
|
class AdminHeader < PageObjects::Pages::Base
|
||||||
def has_tabs?(names)
|
def has_tabs?(names)
|
||||||
expect(page.all(".admin-nav-submenu__tabs a").map(&:text)).to eq(names)
|
expect(page.all(".d-nav-submenu__tabs a").map(&:text)).to eq(names)
|
||||||
end
|
end
|
||||||
|
|
||||||
def visible?
|
def visible?
|
||||||
has_css?(".admin-page-header")
|
has_css?(".d-page-header")
|
||||||
end
|
end
|
||||||
|
|
||||||
def hidden?
|
def hidden?
|
||||||
has_no_css?(".admin-page-header")
|
has_no_css?(".d-page-header")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
19
spec/system/page_objects/components/d_page_header.rb
Normal file
19
spec/system/page_objects/components/d_page_header.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PageObjects
|
||||||
|
module Components
|
||||||
|
class DPageHeader < PageObjects::Pages::Base
|
||||||
|
def has_tabs?(names)
|
||||||
|
expect(page.all(".d-nav-submenu__tabs a").map(&:text)).to eq(names)
|
||||||
|
end
|
||||||
|
|
||||||
|
def visible?
|
||||||
|
has_css?(".d-page-header")
|
||||||
|
end
|
||||||
|
|
||||||
|
def hidden?
|
||||||
|
has_no_css?(".d-page-header")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -21,7 +21,7 @@ module PageObjects
|
|||||||
end
|
end
|
||||||
|
|
||||||
def plugin_nav_tab_selector(plugin)
|
def plugin_nav_tab_selector(plugin)
|
||||||
".admin-nav-submenu__tabs .admin-plugin-tab-nav-item[data-plugin-nav-tab-id=\"#{plugin}\"]"
|
".d-nav-submenu__tabs .admin-plugin-tab-nav-item[data-plugin-nav-tab-id=\"#{plugin}\"]"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -19,7 +19,7 @@ module PageObjects
|
|||||||
end
|
end
|
||||||
|
|
||||||
def click_add_field
|
def click_add_field
|
||||||
page.find(".admin-page-header__actions .btn-primary").click
|
page.find(".d-page-header__actions .btn-primary").click
|
||||||
end
|
end
|
||||||
|
|
||||||
def click_edit
|
def click_edit
|
||||||
|
@ -221,7 +221,10 @@ shared_examples "signup scenarios" do |signup_page_object, login_page_object|
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "when the email domain is blocked" do
|
context "when the email domain is blocked" do
|
||||||
before { SiteSetting.blocked_email_domains = "example.com" }
|
before do
|
||||||
|
SiteSetting.hide_email_address_taken = false
|
||||||
|
SiteSetting.blocked_email_domains = "example.com"
|
||||||
|
end
|
||||||
|
|
||||||
it "cannot signup" do
|
it "cannot signup" do
|
||||||
signup_form
|
signup_form
|
||||||
|
Loading…
x
Reference in New Issue
Block a user