UX: refactor .d-modal to use BEM and improve styling (#23967)

This PR refactors the following:
* leaving all the CSS applied to the old `modal-body` classes in their respective files
* made  new clean styling for `.d-modal` and refactored the template to use the new BEM classes
  * `inner-`, `middle-`, `outer-` container classes are gone and replaced with simplified `wrapper` and `container` classes  
  * use standardised max-sizes with modifiers `-large` and `-max`
  * lighter backdrop,
  * min-width to prevent puny modals
  * other styling changes regarding padding, close button,…
* pulled out all modal overrides into a general `modal-overrides` file + cleanup of outdated CSS
* pulled out login and create account modal styling into their own file, cause it's such a big override 
* removed old general login.scss file for mobile & desktop
* only kept some remainders I don't want to touch in `app/assets/stylesheets/common/base/login.scss`
This commit is contained in:
chapoi 2023-11-15 11:14:47 +01:00 committed by GitHub
parent 38e53b5e8e
commit f72899401d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 1362 additions and 1748 deletions

View File

@ -7,11 +7,7 @@
@append={{true}}
>
<this.dynamicElement
class={{concat-class
"modal"
"d-modal"
(if @inline "inline-modal" "fixed-modal")
}}
class={{concat-class "modal" "d-modal" (if @inline "-inline")}}
data-keyboard="false"
aria-modal="true"
role="dialog"
@ -21,87 +17,87 @@
{{will-destroy this.cleanupListeners}}
{{trap-tab preventScroll=false}}
>
<div class="modal-outer-container">
<div class="modal-middle-container">
<div class="modal-inner-container">
{{yield to="aboveHeader"}}
{{#if
(and
(not @hideHeader)
(or
this.dismissable
@title
(has-block "headerBelowTitle")
(has-block "headerAboveTitle")
)
<div class="d-modal__wrapper">
<div class="d-modal__container">
{{yield to="aboveHeader"}}
{{#if
(and
(not @hideHeader)
(or
this.dismissable
@title
(has-block "headerBelowTitle")
(has-block "headerAboveTitle")
)
}}
<div class={{concat-class "modal-header" @headerClass}}>
{{#if this.dismissable}}
<DButton
@icon="times"
@action={{this.handleCloseButton}}
@title="modal.close"
class="btn-flat modal-close close"
/>
{{/if}}
)
}}
<div class={{concat-class "d-modal__header" @headerClass}}>
{{yield to="headerAboveTitle"}}
{{yield to="headerAboveTitle"}}
<div class="modal-title-wrapper">
{{#if @title}}
<div class="title">
<h3 id="discourse-modal-title">{{@title}}</h3>
{{#if @title}}
<div class="d-modal__title">
<h3
id="discourse-modal-title"
class="d-modal__title-text"
>{{@title}}</h3>
{{#if @subtitle}}
<p class="subtitle">{{@subtitle}}</p>
{{/if}}
</div>
{{#if @subtitle}}
<p class="d-modal__subtitle-text">{{@subtitle}}</p>
{{/if}}
{{yield to="belowModalTitle"}}
</div>
{{/if}}
{{yield to="headerBelowTitle"}}
{{yield to="headerBelowTitle"}}
</div>
{{/if}}
{{yield to="belowHeader"}}
{{this.validateFlashType @flashType}}
{{#if @flash}}
<div
id="modal-alert"
role="alert"
class={{concat-class
"alert"
(if @flashType (concat "alert-" @flashType))
}}
>
{{~@flash~}}
</div>
{{/if}}
<div class={{concat-class "modal-body" @bodyClass}} tabindex="-1">
{{#if (has-block "body")}}
{{yield to="body"}}
{{else}}
{{yield}}
{{#if this.dismissable}}
<DButton
@icon="times"
@action={{this.handleCloseButton}}
@title="modal.close"
class="btn-flat modal-close"
/>
{{/if}}
</div>
{{/if}}
{{#if (has-block "footer")}}
<div class="modal-footer">
{{yield to="footer"}}
</div>
{{yield to="belowHeader"}}
{{this.validateFlashType @flashType}}
{{#if @flash}}
<div
id="modal-alert"
role="alert"
class={{concat-class
"alert"
(if @flashType (concat "alert-" @flashType))
}}
>
{{~@flash~}}
</div>
{{/if}}
<div class={{concat-class "d-modal__body" @bodyClass}} tabindex="-1">
{{#if (has-block "body")}}
{{yield to="body"}}
{{else}}
{{yield}}
{{/if}}
{{yield to="belowFooter"}}
</div>
{{#if (has-block "footer")}}
<div class="d-modal__footer">
{{yield to="footer"}}
</div>
{{/if}}
{{yield to="belowFooter"}}
</div>
</div>
</this.dynamicElement>
{{#unless @inline}}
<div class="modal-backdrop" {{on "click" this.handleWrapperClick}}></div>
<div class="d-modal__backdrop" {{on "click" this.handleWrapperClick}}></div>
{{/unless}}
</ConditionalInElement>

View File

@ -85,7 +85,9 @@ export default class DModal extends Component {
}
if (event.key === "Enter" && this.shouldTriggerClickOnEnter(event)) {
this.wrapperElement.querySelector(".modal-footer .btn-primary")?.click();
this.wrapperElement
.querySelector(".d-modal__footer .btn-primary")
?.click();
event.preventDefault();
}
}

View File

@ -1,4 +1,5 @@
<DModal
class="create-account -large"
{{on "keydown" this.actionOnEnter}}
{{on "click" this.selectKitFocus}}
@closeModal={{@closeModal}}
@ -6,7 +7,6 @@
@flash={{this.flash}}
@flashType="error"
aria-labelledby="create-account-title"
class="create-account"
>
<:body>
<PluginOutlet
@ -16,7 +16,7 @@
<div
class={{concat-class
"create-account-form"
(if this.site.desktopView "login-left-side")
this.model.authOptions.auth_provider
}}
>
@ -31,244 +31,243 @@
</div>
{{#if this.showCreateForm}}
<div class="login-form">
<form>
{{#if this.associateHtml}}
<div class="input-group create-account-associate-link">
<span>{{html-safe this.associateHtml}}</span>
</div>
{{/if}}
<div class="input-group create-account-email">
<Input
{{on "focusout" this.checkEmailAvailability}}
@type="email"
@value={{this.model.accountEmail}}
disabled={{this.emailDisabled}}
autofocus="autofocus"
aria-describedby="account-email-validation"
aria-invalid={{this.emailValidation.failed}}
name="email"
id="new-account-email"
class={{value-entered this.model.accountEmail}}
/>
<label class="alt-placeholder" for="new-account-email">
{{i18n "user.email.title"}}
{{~#if this.userFields~}}
<span class="required">*</span>
{{/if}}
</label>
<InputTip
@validation={{this.emailValidation}}
@id="account-email-validation"
/>
<span class="more-info">{{i18n "user.email.instructions"}}</span>
<form id="login-form">
{{#if this.associateHtml}}
<div class="input-group create-account-associate-link">
<span>{{html-safe this.associateHtml}}</span>
</div>
<div class="input-group create-account__username">
<Input
@value={{this.model.accountUsername}}
disabled={{this.usernameDisabled}}
maxlength={{this.maxUsernameLength}}
aria-describedby="username-validation"
aria-invalid={{this.usernameValidation.failed}}
autocomplete="off"
name="username"
id="new-account-username"
class={{value-entered this.model.accountUsername}}
/>
<label class="alt-placeholder" for="new-account-username">
{{i18n "user.username.title"}}
{{~#if this.userFields~}}
<span class="required">*</span>
{{/if}}
</label>
<InputTip
@validation={{this.usernameValidation}}
@id="username-validation"
/>
<span class="more-info">
{{i18n "user.username.instructions"}}
</span>
</div>
<div class="input-group create-account__fullname">
{{#if this.fullnameRequired}}
<TextField
@disabled={{this.nameDisabled}}
@value={{this.model.accountName}}
@id="new-account-name"
@class={{value-entered this.model.accountName}}
aria-describedby="fullname-validation"
aria-invalid={{this.nameValidation.failed}}
/>
<label class="alt-placeholder" for="new-account-name">
{{i18n "user.name.title"}}
{{#if this.siteSettings.full_name_required}}
{{~#if this.userFields~}}
<span class="required">*</span>
{{/if}}
{{/if}}
</label>
<InputTip
@validation={{this.nameValidation}}
@id="fullname-validation"
/>
<span class="more-info">{{this.nameInstructions}}</span>
{{/if}}
</div>
<PluginOutlet
@name="create-account-before-password"
@outletArgs={{hash
accountName=this.model.accountName
accountUsername=this.model.accountUsername
accountPassword=this.accountPassword
userFields=this.userFields
authOptions=this.model.authOptions
}}
{{/if}}
<div class="input-group create-account-email">
<Input
{{on "focusout" this.checkEmailAvailability}}
@type="email"
@value={{this.model.accountEmail}}
disabled={{this.emailDisabled}}
autofocus="autofocus"
aria-describedby="account-email-validation"
aria-invalid={{this.emailValidation.failed}}
name="email"
id="new-account-email"
class={{value-entered this.model.accountEmail}}
/>
<label class="alt-placeholder" for="new-account-email">
{{i18n "user.email.title"}}
{{~#if this.userFields~}}
<span class="required">*</span>
{{/if}}
</label>
<InputTip
@validation={{this.emailValidation}}
@id="account-email-validation"
/>
<span class="more-info">{{i18n "user.email.instructions"}}</span>
</div>
<div class="input-group create-account__password">
{{#if this.passwordRequired}}
<PasswordField
@value={{this.accountPassword}}
@class={{value-entered this.accountPassword}}
@type={{if this.maskPassword "password" "text"}}
@autocomplete="current-password"
@capsLockOn={{this.capsLockOn}}
aria-describedby="password-validation"
aria-invalid={{this.passwordValidation.failed}}
id="new-account-password"
/>
<label class="alt-placeholder" for="new-account-password">
{{i18n "user.password.title"}}
<div class="input-group create-account__username">
<Input
@value={{this.model.accountUsername}}
disabled={{this.usernameDisabled}}
maxlength={{this.maxUsernameLength}}
aria-describedby="username-validation"
aria-invalid={{this.usernameValidation.failed}}
autocomplete="off"
name="username"
id="new-account-username"
class={{value-entered this.model.accountUsername}}
/>
<label class="alt-placeholder" for="new-account-username">
{{i18n "user.username.title"}}
{{~#if this.userFields~}}
<span class="required">*</span>
{{/if}}
</label>
<InputTip
@validation={{this.usernameValidation}}
@id="username-validation"
/>
<span class="more-info">
{{i18n "user.username.instructions"}}
</span>
</div>
<div class="input-group create-account__fullname">
{{#if this.fullnameRequired}}
<TextField
@disabled={{this.nameDisabled}}
@value={{this.model.accountName}}
@id="new-account-name"
@class={{value-entered this.model.accountName}}
aria-describedby="fullname-validation"
aria-invalid={{this.nameValidation.failed}}
/>
<label class="alt-placeholder" for="new-account-name">
{{i18n "user.name.title"}}
{{#if this.siteSettings.full_name_required}}
{{~#if this.userFields~}}
<span class="required">*</span>
{{/if}}
</label>
{{/if}}
</label>
<div class="create-account__password-info">
<div class="create-account__password-tip-validation">
<InputTip
@validation={{this.passwordValidation}}
@id="password-validation"
/>
<span class="more-info">{{this.passwordInstructions}}</span>
<div
class={{concat-class
"caps-lock-warning"
(unless this.capsLockOn "hidden")
}}
>
{{d-icon "exclamation-triangle"}}
{{i18n "login.caps_lock_warning"}}
</div>
</div>
<TogglePasswordMask
@maskPassword={{this.maskPassword}}
@togglePasswordMask={{this.togglePasswordMask}}
<InputTip
@validation={{this.nameValidation}}
@id="fullname-validation"
/>
<span class="more-info">{{this.nameInstructions}}</span>
{{/if}}
</div>
<PluginOutlet
@name="create-account-before-password"
@outletArgs={{hash
accountName=this.model.accountName
accountUsername=this.model.accountUsername
accountPassword=this.accountPassword
userFields=this.userFields
authOptions=this.model.authOptions
}}
/>
<div class="input-group create-account__password">
{{#if this.passwordRequired}}
<PasswordField
@value={{this.accountPassword}}
@class={{value-entered this.accountPassword}}
@type={{if this.maskPassword "password" "text"}}
@autocomplete="current-password"
@capsLockOn={{this.capsLockOn}}
aria-describedby="password-validation"
aria-invalid={{this.passwordValidation.failed}}
id="new-account-password"
/>
<label class="alt-placeholder" for="new-account-password">
{{i18n "user.password.title"}}
{{~#if this.userFields~}}
<span class="required">*</span>
{{/if}}
</label>
<div class="create-account__password-info">
<div class="create-account__password-tip-validation">
<InputTip
@validation={{this.passwordValidation}}
@id="password-validation"
/>
<span class="more-info">{{this.passwordInstructions}}</span>
<div
class={{concat-class
"caps-lock-warning"
(unless this.capsLockOn "hidden")
}}
>
{{d-icon "exclamation-triangle"}}
{{i18n "login.caps_lock_warning"}}
</div>
</div>
{{/if}}
<div class="password-confirmation">
<label for="new-account-password-confirmation">
{{i18n "user.password_confirmation.title"}}
</label>
<HoneypotInput
@id="new-account-confirmation"
@autocomplete="new-password"
@value={{this.accountHoneypot}}
<TogglePasswordMask
@maskPassword={{this.maskPassword}}
@togglePasswordMask={{this.togglePasswordMask}}
/>
<Input
@value={{this.accountChallenge}}
id="new-account-challenge"
/>
</div>
</div>
{{#if this.requireInviteCode}}
<div class="input-group create-account__invite-code">
<Input
@value={{this.inviteCode}}
id="inviteCode"
class={{value-entered this.inviteCode}}
/>
<label class="alt-placeholder" for="invite-code">
{{i18n "user.invite_code.title"}}
</label>
<span class="more-info">
{{i18n "user.invite_code.instructions"}}
</span>
</div>
{{/if}}
<PluginOutlet
@name="create-account-after-password"
@outletArgs={{hash
accountName=this.model.accountName
accountUsername=this.model.accountUsername
accountPassword=this.accountPassword
userFields=this.userFields
}}
/>
<div class="password-confirmation">
<label for="new-account-password-confirmation">
{{i18n "user.password_confirmation.title"}}
</label>
<HoneypotInput
@id="new-account-confirmation"
@autocomplete="new-password"
@value={{this.accountHoneypot}}
/>
<Input
@value={{this.accountChallenge}}
id="new-account-challenge"
/>
</div>
</div>
{{#if this.userFields}}
<div class="user-fields">
{{#each this.userFields as |f|}}
<div class="input-group">
{{! adding the value-entered class here to
{{#if this.requireInviteCode}}
<div class="input-group create-account__invite-code">
<Input
@value={{this.inviteCode}}
id="inviteCode"
class={{value-entered this.inviteCode}}
/>
<label class="alt-placeholder" for="invite-code">
{{i18n "user.invite_code.title"}}
</label>
<span class="more-info">
{{i18n "user.invite_code.instructions"}}
</span>
</div>
{{/if}}
<PluginOutlet
@name="create-account-after-password"
@outletArgs={{hash
accountName=this.model.accountName
accountUsername=this.model.accountUsername
accountPassword=this.accountPassword
userFields=this.userFields
}}
/>
{{#if this.userFields}}
<div class="user-fields">
{{#each this.userFields as |f|}}
<div class="input-group">
{{! adding the value-entered class here to
be able to detect if the user field has a value
entered }}
<UserField
{{on "focus" this.userInputFocus}}
{{on "focusout" this.userInputFocusOut}}
@field={{f.field}}
@value={{f.value}}
@class={{value-entered f.value}}
@validation={{f.validation}}
/>
</div>
{{/each}}
</div>
{{/if}}
<UserField
{{on "focus" this.userInputFocus}}
{{on "focusout" this.userInputFocusOut}}
@field={{f.field}}
@value={{f.value}}
@class={{value-entered f.value}}
@validation={{f.validation}}
/>
</div>
{{/each}}
</div>
{{/if}}
<PluginOutlet
@name="create-account-after-user-fields"
@outletArgs={{hash
accountName=this.model.accountName
accountUsername=this.model.accountUsername
accountPassword=this.accountPassword
userFields=this.userFields
}}
/>
</form>
</div>
<PluginOutlet
@name="create-account-after-user-fields"
@outletArgs={{hash
accountName=this.model.accountName
accountUsername=this.model.accountUsername
accountPassword=this.accountPassword
userFields=this.userFields
}}
/>
</form>
<div class="modal-footer">
<div class="d-modal__footer">
<div class="disclaimer">
{{html-safe this.disclaimerHtml}}
</div>
<DButton
@action={{this.createAccount}}
@disabled={{this.submitDisabled}}
@label="create_account.title"
@isLoading={{this.formSubmitted}}
class="btn-large btn-primary"
/>
{{#unless this.hasAuthOptions}}
<div class="d-modal__footer-buttons">
<DButton
@action={{route-action "showLogin"}}
@disabled={{this.formSubmitted}}
@label="log_in"
id="login-link"
class="btn-large"
@action={{this.createAccount}}
@disabled={{this.submitDisabled}}
@label="create_account.title"
@isLoading={{this.formSubmitted}}
class="btn-large btn-primary"
/>
{{/unless}}
{{#unless this.hasAuthOptions}}
<DButton
@action={{route-action "showLogin"}}
@disabled={{this.formSubmitted}}
@label="log_in"
id="login-link"
class="btn-large btn-flat"
/>
{{/unless}}
</div>
</div>
<PluginOutlet
@ -277,18 +276,17 @@
/>
{{/if}}
{{#if this.showExternalLoginButtons}}
<div class="create-account-login-buttons">
<LoginButtons
@externalLogin={{this.externalLogin}}
@context="create-account"
/>
</div>
{{/if}}
{{#if this.model.skipConfirmation}}
{{loading-spinner size="large"}}
{{/if}}
</div>
{{#if this.hasAtLeastOneLoginButton}}
<div class="login-right-side">
<LoginButtons
@externalLogin={{this.externalLogin}}
@context="create-account"
/>
</div>
{{/if}}
</:body>
</DModal>

View File

@ -1,7 +1,7 @@
<CreateInviteUploader @autoFindInput={{false}} as |uploader setElement|>
<DModal
@title={{i18n "user.invited.bulk_invite.text"}}
class="create-invite-bulk-modal"
class="create-invite-bulk-modal -large"
@closeModal={{@closeModal}}
>
<:body>

View File

@ -22,8 +22,9 @@
autocapitalize="off"
/>
{{else}}
<p>{{i18n "forgot_password.invite"}}</p>
<label for="username-or-email">
{{i18n "forgot_password.invite"}}
{{i18n "forgot_password.email-username"}}
</label>
<input
{{on "input" this.updateEmailOrUsername}}

View File

@ -1,7 +1,7 @@
<DModal
@title={{i18n this.modalTitleKey}}
@closeModal={{@closeModal}}
class="history-modal {{concat '--mode-' (dasherize this.viewMode)}}"
class="history-modal -max {{concat '--mode-' (dasherize this.viewMode)}}"
>
<:body>
<ConditionalLoadingSpinner @condition={{this.initialLoad}}>

View File

@ -1,7 +1,7 @@
<DModal
@title={{i18n "keyboard_shortcuts_help.title"}}
@closeModal={{@closeModal}}
class="keyboard-shortcuts-modal"
class="keyboard-shortcuts-modal -max"
>
<:body>
<div id="keyboard-shortcuts-help">

View File

@ -1,5 +1,5 @@
<DModal
class="login-modal"
class="login-modal -large"
@bodyClass={{this.modalBodyClasses}}
@closeModal={{@closeModal}}
@flash={{this.flash}}
@ -72,13 +72,15 @@
<Modal::Login::WelcomeHeader @wavingHandURL={{this.wavingHandURL}} />
</div>
{{/unless}}
<div class="login-right-side">
<LoginButtons
@externalLogin={{this.externalLoginAction}}
@passkeyLogin={{this.passkeyLogin}}
@context="login"
/>
</div>
{{#if this.hasAtLeastOneLoginButton}}
<div class="login-right-side">
<LoginButtons
@externalLogin={{this.externalLoginAction}}
@passkeyLogin={{this.passkeyLogin}}
@context="login"
/>
</div>
{{/if}}
{{/if}}
</:body>
</DModal>

View File

@ -1,4 +1,4 @@
<div class="modal-footer">
<div class="d-modal__footer">
{{#if @canLoginLocal}}
{{#unless @showSecurityKey}}
<DButton
@ -15,7 +15,7 @@
{{#if @showSignupLink}}
<DButton
class="btn-large"
class="btn-large btn-flat"
id="new-account-link"
@action={{@createAccount}}
@label="create_account.title"

View File

@ -26,6 +26,7 @@
class="invite-link"
@value={{this.url}}
readonly={{true}}
size="200"
/>
<CopyButton @selector="input.invite-link" @ariaLabel="share.url" />
</div>

View File

@ -1,7 +1,7 @@
<DModal
@title={{i18n "topics.bulk.actions"}}
@closeModal={{@closeModal}}
class="topic-bulk-actions-modal small"
class="topic-bulk-actions-modal -large"
>
<p>
{{html-safe (i18n "topics.bulk.selected" count=@model.topics.length)}}

View File

@ -62,7 +62,7 @@ export default class extends Component {
if (this.observer) {
this.observer.disconnect();
} else {
const root = document.querySelector(".modal-body");
const root = document.querySelector(".d-modal__body");
const style = window.getComputedStyle(root);
const marginTop = parseFloat(style.marginTop);
const paddingTop = parseFloat(style.paddingTop);
@ -76,7 +76,7 @@ export default class extends Component {
});
},
{
root: document.querySelector(".modal-body"),
root: document.querySelector(".d-modal__body"),
rootMargin: `0px 0px ${marginTop + paddingTop}px 0px`,
threshold: 1.0,
}

View File

@ -26,7 +26,7 @@ acceptance("Admin - Themes - Install modal", function (needs) {
);
assert.ok(query(publicKey), "shows public key");
await click(".modal-footer .d-modal-cancel");
await click(".d-modal__footer .d-modal-cancel");
await click(".create-actions .btn-primary");
await click("#remote");

View File

@ -124,17 +124,20 @@ acceptance("Admin - Watched Words", function (needs) {
test("test modal - replace", async function (assert) {
await visit("/admin/customize/watched_words/action/replace");
await click(".watched-word-test");
await fillIn(".modal-body textarea", "Hi there!");
assert.strictEqual(query(".modal-body li .match").innerText, "Hi");
assert.strictEqual(query(".modal-body li .replacement").innerText, "hello");
await fillIn(".d-modal__body textarea", "Hi there!");
assert.strictEqual(query(".d-modal__body li .match").innerText, "Hi");
assert.strictEqual(
query(".d-modal__body li .replacement").innerText,
"hello"
);
});
test("test modal - tag", async function (assert) {
await visit("/admin/customize/watched_words/action/tag");
await click(".watched-word-test");
await fillIn(".modal-body textarea", "Hello world!");
assert.strictEqual(query(".modal-body li .match").innerText, "Hello");
assert.strictEqual(query(".modal-body li .tag").innerText, "greeting");
await fillIn(".d-modal__body textarea", "Hello world!");
assert.strictEqual(query(".d-modal__body li .match").innerText, "Hello");
assert.strictEqual(query(".d-modal__body li .tag").innerText, "greeting");
});
});

View File

@ -11,15 +11,15 @@ acceptance("Composer - Hyperlink", function (needs) {
await fillIn(".d-editor-input", "This is a link to ");
assert
.dom(".insert-link.modal-body")
.dom(".insert-link.d-modal__body")
.doesNotExist("no hyperlink modal by default");
await click(".d-editor button.link");
assert.dom(".insert-link.modal-body").exists("hyperlink modal visible");
assert.dom(".insert-link.d-modal__body").exists("hyperlink modal visible");
await fillIn(".modal-body .link-url", "google.com");
await fillIn(".modal-body .link-text", "Google");
await click(".modal-footer button.btn-primary");
await fillIn(".d-modal__body .link-url", "google.com");
await fillIn(".d-modal__body .link-text", "Google");
await click(".d-modal__footer button.btn-primary");
assert
.dom(".d-editor-input")
@ -29,15 +29,15 @@ acceptance("Composer - Hyperlink", function (needs) {
);
assert
.dom(".insert-link.modal-body")
.dom(".insert-link.d-modal__body")
.doesNotExist("modal dismissed after submitting link");
await fillIn(".d-editor-input", "Reset textarea contents.");
await click(".d-editor button.link");
await fillIn(".modal-body .link-url", "google.com");
await fillIn(".modal-body .link-text", "Google");
await click(".modal-footer button.btn-danger");
await fillIn(".d-modal__body .link-url", "google.com");
await fillIn(".d-modal__body .link-text", "Google");
await click(".d-modal__footer button.btn-danger");
assert
.dom(".d-editor-input")
@ -47,7 +47,7 @@ acceptance("Composer - Hyperlink", function (needs) {
);
assert
.dom(".insert-link.modal-body")
.dom(".insert-link.d-modal__body")
.doesNotExist("modal dismissed after cancelling");
const textarea = query("#reply-control .d-editor-input");
@ -55,8 +55,8 @@ acceptance("Composer - Hyperlink", function (needs) {
textarea.selectionEnd = 6;
await click(".d-editor button.link");
await fillIn(".modal-body .link-url", "somelink.com");
await click(".modal-footer button.btn-primary");
await fillIn(".d-modal__body .link-url", "somelink.com");
await click(".d-modal__footer button.btn-primary");
assert
.dom(".d-editor-input")
@ -68,16 +68,16 @@ acceptance("Composer - Hyperlink", function (needs) {
await fillIn(".d-editor-input", "");
await click(".d-editor button.link");
await fillIn(".modal-body .link-url", "http://google.com");
await triggerKeyEvent(".modal-body .link-url", "keyup", "Space");
await fillIn(".d-modal__body .link-url", "http://google.com");
await triggerKeyEvent(".d-modal__body .link-url", "keyup", "Space");
assert
.dom(".internal-link-results")
.doesNotExist(
"does not show internal links search dropdown when inputting a url"
);
await fillIn(".modal-body .link-url", "local");
await triggerKeyEvent(".modal-body .link-url", "keyup", "Space");
await fillIn(".d-modal__body .link-url", "local");
await triggerKeyEvent(".d-modal__body .link-url", "keyup", "Space");
assert
.dom(".internal-link-results")
.exists("shows internal links search dropdown when entering keywords");

View File

@ -229,8 +229,8 @@ acceptance("Composer", function (needs) {
await click("#reply-control a.cancel");
assert.ok(exists(".d-modal"), "it pops up a confirmation dialog");
await click(".modal-footer .discard-draft");
assert.ok(!exists(".modal-body"), "the confirmation can be cancelled");
await click(".d-modal__footer .discard-draft");
assert.ok(!exists(".d-modal__body"), "the confirmation can be cancelled");
});
test("Create a topic with server side errors", async function (assert) {
@ -286,7 +286,7 @@ acceptance("Composer", function (needs) {
assert.ok(visible(".d-modal"), "it pops up a modal");
assert.strictEqual(currentURL(), "/", "it doesn't change routes");
await click(".modal-footer button");
await click(".d-modal__footer button");
assert.ok(invisible(".d-modal"), "the modal can be dismissed");
});
@ -359,7 +359,7 @@ acceptance("Composer", function (needs) {
await click(".topic-post:nth-of-type(1) button.show-more-actions");
await click(".topic-post:nth-of-type(1) button.edit");
await click(".modal-footer button.keep-editing");
await click(".d-modal__footer button.keep-editing");
assert.ok(invisible(".discard-draft-modal.modal"));
assert.strictEqual(
query(".d-editor-input").value,
@ -368,8 +368,8 @@ acceptance("Composer", function (needs) {
);
await click(".topic-post:nth-of-type(1) button.edit");
assert.ok(invisible(".modal-footer button.save-draft"));
await click(".modal-footer button.discard-draft");
assert.ok(invisible(".d-modal__footer button.save-draft"));
await click(".d-modal__footer button.discard-draft");
assert.ok(invisible(".discard-draft-modal.modal"));
assert.strictEqual(
@ -389,7 +389,7 @@ acceptance("Composer", function (needs) {
await click("#topic-footer-buttons .create");
assert.ok(visible(".discard-draft-modal.modal"));
await click(".modal-footer button.keep-editing");
await click(".d-modal__footer button.keep-editing");
assert.ok(invisible(".discard-draft-modal.modal"));
assert.strictEqual(
@ -438,7 +438,7 @@ acceptance("Composer", function (needs) {
"it pops up the discard drafts modal"
);
await click(".modal-footer button.keep-editing");
await click(".d-modal__footer button.keep-editing");
assert.ok(invisible(".discard-draft-modal.modal"), "hides modal");
await click("#topic-footer-buttons .btn.create");
@ -447,7 +447,7 @@ acceptance("Composer", function (needs) {
"it pops up the modal again"
);
await click(".modal-footer button.discard-draft");
await click(".d-modal__footer button.discard-draft");
assert.strictEqual(
query(".d-editor-input").value,
@ -487,7 +487,7 @@ acceptance("Composer", function (needs) {
);
assert.ok(visible(".d-modal"), "it pops up a modal");
await click(".modal-footer button");
await click(".d-modal__footer button");
assert.ok(invisible(".d-modal"), "the modal can be dismissed");
assert.ok(exists(".pending-posts .reviewable-item"));
});
@ -581,7 +581,7 @@ acceptance("Composer", function (needs) {
"it pops up a confirmation dialog"
);
await click(".modal-footer button.discard-draft");
await click(".d-modal__footer button.discard-draft");
assert.ok(
query(".d-editor-input").value.startsWith("This is the second post."),
"it populates the input with the post text"
@ -809,13 +809,13 @@ acceptance("Composer", function (needs) {
exists(".discard-draft-modal.modal"),
"it pops up a confirmation dialog"
);
assert.ok(invisible(".modal-footer button.save-draft"));
assert.ok(invisible(".d-modal__footer button.save-draft"));
assert.strictEqual(
query(".modal-footer button.keep-editing").innerText.trim(),
query(".d-modal__footer button.keep-editing").innerText.trim(),
I18n.t("post.cancel_composer.keep_editing"),
"has keep editing button"
);
await click(".modal-footer button.discard-draft");
await click(".d-modal__footer button.discard-draft");
assert.ok(
query(".d-editor-input").value.startsWith("This is the second post."),
"it populates the input with the post text"
@ -836,16 +836,16 @@ acceptance("Composer", function (needs) {
"it pops up a confirmation dialog"
);
assert.strictEqual(
query(".modal-footer button.save-draft").innerText.trim(),
query(".d-modal__footer button.save-draft").innerText.trim(),
I18n.t("post.cancel_composer.save_draft"),
"has save draft button"
);
assert.strictEqual(
query(".modal-footer button.keep-editing").innerText.trim(),
query(".d-modal__footer button.keep-editing").innerText.trim(),
I18n.t("post.cancel_composer.keep_editing"),
"has keep editing button"
);
await click(".modal-footer button.save-draft");
await click(".d-modal__footer button.save-draft");
assert.strictEqual(
query(".d-editor-input").value,
"",
@ -898,7 +898,7 @@ acceptance("Composer", function (needs) {
await composerActions.expand();
await composerActions.selectRowByValue("reply_as_new_topic");
assert.ok(!exists(".modal-body"), "abandon popup shouldn't come");
assert.ok(!exists(".d-modal__body"), "abandon popup shouldn't come");
assert.ok(
query(".d-editor-input").value.includes(longText),

View File

@ -7,7 +7,7 @@ acceptance("Create Account Fields - From Login Form", function () {
await visit("/");
await click("header .login-button");
await fillIn("#login-account-name", "isaac@foo.com");
await click(".modal-footer #new-account-link");
await click(".d-modal__footer #new-account-link");
assert.dom("#new-account-username").hasText("");
assert
@ -19,7 +19,7 @@ acceptance("Create Account Fields - From Login Form", function () {
await visit("/");
await click("header .login-button");
await fillIn("#login-account-name", "isaac");
await click(".modal-footer #new-account-link");
await click(".d-modal__footer #new-account-link");
assert.dom("#new-account-email").hasText("");
assert

View File

@ -29,7 +29,7 @@ acceptance("Create Account", function () {
assert
.dom("#username-validation.bad")
.exists("the username validation is bad");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
await fillIn("#new-account-username", "good-tuna");
assert
@ -46,9 +46,9 @@ acceptance("Create Account", function () {
return response({ success: true });
});
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert
.dom(".modal-footer .btn-primary:disabled")
.dom(".d-modal__footer .btn-primary:disabled")
.exists("create account is disabled");
assert.verifySteps(["request"]);
@ -59,7 +59,7 @@ acceptance("Create Account", function () {
await click("header .sign-up-button");
await fillIn("#new-account-email", "z@z.co");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert
.dom("#username-validation")
@ -91,7 +91,7 @@ acceptance("Create Account - full_name_required", function (needs) {
await fillIn("#new-account-username", "good-tuna");
await fillIn("#new-account-password", "cool password bro");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert.dom("#fullname-validation").hasText(I18n.t("user.name.required"));
await fillIn("#new-account-name", "Full Name");
@ -106,9 +106,9 @@ acceptance("Create Account - full_name_required", function (needs) {
return response({ success: true });
});
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert
.dom(".modal-footer .btn-primary:disabled")
.dom(".d-modal__footer .btn-primary:disabled")
.exists("create account is disabled");
assert.verifySteps(["request"]);
@ -123,10 +123,10 @@ acceptance("Create Account - passkeys enabled", function (needs) {
await click("header .sign-up-button");
assert
.dom(".create-account-form .btn-primary")
.dom(".d-modal.create-account .btn-primary")
.exists("create account button exists");
assert.dom(".create-account-form .btn-primary").exists();
assert.dom(".d-modal.create-account .btn-primary").exists();
assert.dom(".passkey-login-button").doesNotExist();
});
});

View File

@ -34,7 +34,7 @@ acceptance("Create Account - User Fields", function (needs) {
assert.dom(".create-account").exists("it shows the create account modal");
assert.dom(".user-field").exists("it has at least one user field");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert
.dom("#account-email-validation")
.hasText(I18n.t("user.email.required"));
@ -51,10 +51,10 @@ acceptance("Create Account - User Fields", function (needs) {
.dom("#account-email-validation.good")
.exists("the email validation is good");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
await fillIn(".user-field input[type=text]:nth-of-type(1)", "Barky");
await click(".user-field input[type=checkbox]");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
});
test("can submit with enter", async function (assert) {
@ -74,7 +74,7 @@ acceptance("Create Account - User Fields", function (needs) {
await fillIn("#new-account-password", "cool password bro");
await fillIn(".user-field-whats-your-dad-like input", "cool password bro");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert
.dom(".user-field-what-is-your-pets-name .tip.bad")

View File

@ -130,11 +130,11 @@ acceptance("flagging", function (needs) {
const silenceUntilCombobox = selectKit(".silence-until .combobox");
await silenceUntilCombobox.expand();
await silenceUntilCombobox.selectRowByValue("tomorrow");
assert.ok(exists(".modal-body"));
assert.ok(exists(".d-modal__body"));
await fillIn("input.silence-reason", "for breaking the rules");
await click(".perform-penalize");
assert.ok(!exists(".modal-body"));
assert.ok(!exists(".d-modal__body"));
});
test("Can delete spammer from spam", async function (assert) {

View File

@ -62,7 +62,7 @@ acceptance("Forgot password", function (needs) {
);
assert.strictEqual(
query(".modal-body").innerHTML.trim(),
query(".d-modal__body").innerHTML.trim(),
I18n.t("forgot_password.complete_username_found", {
username: "someuser",
}),
@ -76,7 +76,7 @@ acceptance("Forgot password", function (needs) {
await click(".forgot-password-reset");
assert.strictEqual(
query(".modal-body").innerHTML.trim(),
query(".d-modal__body").innerHTML.trim(),
I18n.t("forgot_password.complete_email_found", {
email: "someuser@gmail.com",
}),
@ -108,7 +108,7 @@ acceptance(
await click(".forgot-password-reset");
assert.strictEqual(
query(".modal-body").innerHTML.trim(),
query(".d-modal__body").innerHTML.trim(),
I18n.t("forgot_password.complete_username", {
username: "someuser",
}),

View File

@ -191,7 +191,7 @@ acceptance("Group - Authenticated", function (needs) {
await click(".group-index-request");
assert.strictEqual(
query(".modal-header .title").innerText.trim(),
query(".d-modal__header .d-modal__title-text").innerText.trim(),
I18n.t("groups.membership_request.title", { group_name: "Macdonald" })
);
@ -200,7 +200,7 @@ acceptance("Group - Authenticated", function (needs) {
"Please add me"
);
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert.strictEqual(
query(".fancy-title").innerText.trim(),

View File

@ -29,7 +29,7 @@ acceptance("Groups", function () {
await click(".group-index-join");
assert.ok(exists(".modal.login-modal"), "it shows the login modal");
await click(".login-modal .close");
await click(".login-modal .modal-close");
assert.ok(invisible(".modal.login-modal"), "it closes the login modal");
await click(".group-index-request");

View File

@ -147,7 +147,7 @@ acceptance("Keyboard Shortcuts - Authenticated Users", function (needs) {
"confirmation modal to dismiss unread is present"
);
assert.strictEqual(
query(".modal-body").innerText,
query(".d-modal__body").innerText,
I18n.t("topics.bulk.also_dismiss_topics")
);
await click("#dismiss-read-confirm");
@ -177,7 +177,7 @@ acceptance("Keyboard Shortcuts - Authenticated Users", function (needs) {
"confirmation modal to dismiss unread is present"
);
assert.strictEqual(
query(".modal-body").innerText,
query(".d-modal__body").innerText,
"Stop tracking these topics so they never show up as unread for me again"
);

View File

@ -121,7 +121,7 @@ acceptance("Topic - Bulk Actions - Mobile", function (needs) {
await click(queryAll("input.bulk-select")[1]);
await click(".bulk-select-actions");
await click(".modal-body .delete-topics");
await click(".d-modal__body .delete-topics");
assert
.dom(".topic-bulk-actions-modal")

View File

@ -51,10 +51,10 @@ acceptance("Modal service: component-based API", function () {
await settled();
assert.dom(".d-modal").exists("modal should appear");
assert.dom(".d-modal .title h3").hasText("Hello World");
assert.dom(".d-modal .modal-body").hasText("Modal content is working");
assert.dom(".d-modal__title-text").hasText("Hello World");
assert.dom(".d-modal .d-modal__body").hasText("Modal content is working");
await click(".modal-backdrop");
await click(".d-modal__backdrop");
assert.dom(".d-modal").doesNotExist("disappears on click outside");
assert.deepEqual(
await promise,
@ -105,7 +105,7 @@ acceptance("Modal service: component-based API", function () {
modalService.show(MyModalClass, { model: { text: "second" } });
await settled();
assert
.dom(".d-modal .modal-body")
.dom(".d-modal .d-modal__body")
.hasText("Modal content is second", "new modal replaces old");
assert.deepEqual(
await promise,

View File

@ -11,7 +11,7 @@ acceptance("Modal - Login", function () {
// you have to press the tab key twice to get to the login button
await tab({ unRestrainTabIndex: true });
await tab({ unRestrainTabIndex: true });
assert.dom(".modal-footer #login-button").isFocused();
assert.dom(".d-modal__footer #login-button").isFocused();
});
});

View File

@ -47,7 +47,7 @@ acceptance("Post Table Wrapper Test", function () {
"The table is present inside the modal"
);
await click(".fullscreen-table-modal .modal-close.close");
await click(".fullscreen-table-modal .modal-close");
await click(
`${postWithLargeTable} .fullscreen-table-wrapper .btn-expand-table svg`
);

View File

@ -70,21 +70,21 @@ acceptance("Review", function (needs) {
assert.ok(visible(".reject-reason-reviewable-modal"));
assert.ok(
query(".reject-reason-reviewable-modal .title").innerHTML.includes(
I18n.t("review.reject_reason.title")
),
query(
".reject-reason-reviewable-modal .d-modal__title"
).innerHTML.includes(I18n.t("review.reject_reason.title")),
"it opens reject reason modal when user is rejected"
);
await click(".modal-footer .cancel");
await click(".d-modal__footer .cancel");
await reviewableActionDropdown.expand();
await reviewableActionDropdown.selectRowByValue("reject_user_block");
assert.ok(visible(".reject-reason-reviewable-modal"));
assert.ok(
query(".reject-reason-reviewable-modal .title").innerHTML.includes(
I18n.t("review.reject_reason.title")
),
query(
".reject-reason-reviewable-modal .d-modal__title"
).innerHTML.includes(I18n.t("review.reject_reason.title")),
"it opens reject reason modal when user is rejected and blocked"
);
});

View File

@ -16,10 +16,10 @@ acceptance("Signing In", function () {
// Test invalid password first
await fillIn("#login-account-name", "eviltrout");
await fillIn("#login-account-password", "incorrect");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert.ok(exists("#modal-alert:visible"), "it displays the login error");
assert.notOk(
exists(".modal-footer .btn-primary:disabled"),
exists(".d-modal__footer .btn-primary:disabled"),
"enables the login button"
);
@ -36,9 +36,9 @@ acceptance("Signing In", function () {
// Use the correct password
await fillIn("#login-account-password", "correct");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert.ok(
exists(".modal-footer .btn-primary:disabled"),
exists(".d-modal__footer .btn-primary:disabled"),
"disables the login button"
);
});
@ -50,19 +50,19 @@ acceptance("Signing In", function () {
await fillIn("#login-account-name", "eviltrout");
await fillIn("#login-account-password", "not-activated");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert.strictEqual(
query(".modal-body b").innerText,
query(".d-modal__body b").innerText,
"<small>eviltrout@example.com</small>"
);
assert.ok(!exists(".modal-body small"), "it escapes the email address");
assert.ok(!exists(".d-modal__body small"), "it escapes the email address");
await click(".modal-footer button.resend");
await click(".d-modal__footer button.resend");
assert.strictEqual(
query(".modal-body b").innerText,
query(".d-modal__body b").innerText,
"<small>current@example.com</small>"
);
assert.ok(!exists(".modal-body small"), "it escapes the email address");
assert.ok(!exists(".d-modal__body small"), "it escapes the email address");
});
test("sign in - not activated - edit email", async function (assert) {
@ -72,22 +72,22 @@ acceptance("Signing In", function () {
await fillIn("#login-account-name", "eviltrout");
await fillIn("#login-account-password", "not-activated-edit");
await click(".modal-footer .btn-primary");
await click(".modal-footer button.edit-email");
await click(".d-modal__footer .btn-primary");
await click(".d-modal__footer button.edit-email");
assert.strictEqual(
query(".activate-new-email").value,
"current@example.com"
);
assert.strictEqual(
count(".modal-footer .btn-primary:disabled"),
count(".d-modal__footer .btn-primary:disabled"),
1,
"must change email"
);
await fillIn(".activate-new-email", "different@example.com");
assert.ok(!exists(".modal-footer .btn-primary:disabled"));
await click(".modal-footer .btn-primary");
assert.ok(!exists(".d-modal__footer .btn-primary:disabled"));
await click(".d-modal__footer .btn-primary");
assert.strictEqual(
query(".modal-body b").innerText,
query(".d-modal__body b").innerText,
"different@example.com"
);
});
@ -100,7 +100,7 @@ acceptance("Signing In", function () {
await fillIn("#login-account-name", "eviltrout");
await fillIn("#login-account-password", "need-second-factor");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert.notOk(
exists("#credentials:visible"),
@ -111,15 +111,15 @@ acceptance("Signing In", function () {
"it displays the second factor prompt"
);
assert.notOk(
exists(".modal-footer .btn-primary:disabled"),
exists(".d-modal__footer .btn-primary:disabled"),
"enables the login button"
);
await fillIn("#login-second-factor", "123456");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert.ok(
exists(".modal-footer .btn-primary:disabled"),
exists(".d-modal__footer .btn-primary:disabled"),
"disables the login button"
);
});
@ -132,7 +132,7 @@ acceptance("Signing In", function () {
await fillIn("#login-account-name", "eviltrout");
await fillIn("#login-account-password", "need-security-key");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert.notOk(
exists("#credentials:visible"),
@ -154,13 +154,13 @@ acceptance("Signing In", function () {
await click("header .login-button");
await fillIn("#login-account-name", "eviltrout");
await fillIn("#login-account-password", "need-second-factor");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
await click(".login-modal .toggle-second-factor-method");
await fillIn("#login-second-factor", "123456");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert.ok(
exists(".modal-footer .btn-primary:disabled"),
exists(".d-modal__footer .btn-primary:disabled"),
"it closes the modal when the code is valid"
);
});
@ -170,10 +170,10 @@ acceptance("Signing In", function () {
await click("header .login-button");
await fillIn("#login-account-name", "eviltrout");
await fillIn("#login-account-password", "need-second-factor");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
await click(".login-modal .toggle-second-factor-method");
await fillIn("#login-second-factor", "something");
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
assert.ok(
exists("#modal-alert:visible"),

View File

@ -190,17 +190,21 @@ acceptance("Theme", function (needs) {
await click(".install-theme-content button.advanced-repo");
assert.notOk(
exists(".admin-install-theme-modal .modal-footer .install-theme-warning"),
exists(
".admin-install-theme-modal .d-modal__footer .install-theme-warning"
),
"no Git warning is displayed"
);
await click(".admin-install-theme-modal .modal-footer .btn-primary");
await click(".admin-install-theme-modal .d-modal__footer .btn-primary");
assert.ok(
exists(".admin-install-theme-modal .modal-footer .install-theme-warning"),
exists(
".admin-install-theme-modal .d-modal__footer .install-theme-warning"
),
"Git warning is displayed"
);
await click(".admin-install-theme-modal .modal-footer .btn-danger");
await click(".admin-install-theme-modal .d-modal__footer .btn-danger");
assert.notOk(
exists(".admin-install-theme-modal:visible"),

View File

@ -123,7 +123,7 @@ acceptance("Topic - Bulk Actions", function (needs) {
await click(queryAll("input.bulk-select")[1]);
await click(".bulk-select-actions");
await click(".modal-body .delete-topics");
await click(".d-modal__body .delete-topics");
assert
.dom(".topic-bulk-actions-modal")

View File

@ -131,7 +131,7 @@ acceptance("Topic - Edit timer", function (needs) {
test("schedule publish to category - visible for a PM", async function (assert) {
updateCurrentUser({ moderator: true });
const timerType = selectKit(".select-kit.timer-type");
const categoryChooser = selectKit(".modal-body .category-chooser");
const categoryChooser = selectKit(".d-modal__body .category-chooser");
await visit("/t/pm-for-testing/12");
await click(".toggle-admin-menu");
@ -170,7 +170,7 @@ acceptance("Topic - Edit timer", function (needs) {
test("schedule publish to category - visible for a private category", async function (assert) {
updateCurrentUser({ moderator: true });
const timerType = selectKit(".select-kit.timer-type");
const categoryChooser = selectKit(".modal-body .category-chooser");
const categoryChooser = selectKit(".d-modal__body .category-chooser");
// has private category id 24 (shared drafts)
await visit("/t/some-topic/9");
@ -210,7 +210,7 @@ acceptance("Topic - Edit timer", function (needs) {
test("schedule publish to category - visible for an unlisted public topic", async function (assert) {
updateCurrentUser({ moderator: true });
const timerType = selectKit(".select-kit.timer-type");
const categoryChooser = selectKit(".modal-body .category-chooser");
const categoryChooser = selectKit(".d-modal__body .category-chooser");
await visit("/t/internationalization-localization/280");

View File

@ -32,7 +32,7 @@ acceptance("Topic move posts", function (needs) {
await click(".selected-posts .move-to-topic");
assert.ok(
query(".choose-topic-modal .title").innerHTML.includes(
query(".choose-topic-modal .d-modal__title").innerHTML.includes(
I18n.t("topic.move_to.title")
),
"it opens move to modal"
@ -67,7 +67,7 @@ acceptance("Topic move posts", function (needs) {
await click("#post_11 .select-below");
await click(".selected-posts .move-to-topic");
await fillIn(".choose-topic-modal #split-topic-name", "Existing topic");
await click(".choose-topic-modal .modal-footer .btn-primary");
await click(".choose-topic-modal .d-modal__footer .btn-primary");
assert.strictEqual(
query("#modal-alert").innerText.trim(),
I18n.t("topic.move_to.error")
@ -82,7 +82,7 @@ acceptance("Topic move posts", function (needs) {
await click(".selected-posts .move-to-topic");
assert.ok(
query(".choose-topic-modal .title").innerHTML.includes(
query(".choose-topic-modal .d-modal__title").innerHTML.includes(
I18n.t("topic.move_to.title")
),
"it opens move to modal"
@ -159,7 +159,7 @@ acceptance("Topic move posts", function (needs) {
await click(".selected-posts .move-to-topic");
assert.ok(
query(".choose-topic-modal .title").innerHTML.includes(
query(".choose-topic-modal .d-modal__title").innerHTML.includes(
I18n.t("topic.move_to.title")
),
"it opens move to modal"
@ -195,7 +195,7 @@ acceptance("Topic move posts", function (needs) {
await click(".selected-posts .move-to-topic");
assert.ok(
query(".choose-topic-modal .title").innerHTML.includes(
query(".choose-topic-modal .d-modal__title").innerHTML.includes(
I18n.t("topic.move_to.title")
),
"it opens move to modal"

View File

@ -67,7 +67,7 @@ acceptance("Topic - Slow Mode - enabled", function (needs) {
await click(".future-date-input-selector-header");
assert.strictEqual(
query("div.modal-footer button.btn-primary span").innerText,
query("div.d-modal__footer button.btn-primary span").innerText,
I18n.t("topic.slow_mode_update.enable"),
"shows 'Enable' button when slow mode is disabled"
);
@ -78,7 +78,7 @@ acceptance("Topic - Slow Mode - enabled", function (needs) {
await click(".future-date-input-selector-header");
assert.strictEqual(
query("div.modal-footer button.btn-primary span").innerText,
query("div.d-modal__footer button.btn-primary span").innerText,
I18n.t("topic.slow_mode_update.update"),
"shows 'Update' button when slow mode is enabled"
);

View File

@ -938,17 +938,17 @@ acceptance("User menu - Dismiss button", function (needs) {
await click(".user-menu .notifications-dismiss");
assert.strictEqual(
query(
".dismiss-notification-confirmation .modal-body"
".dismiss-notification-confirmation .d-modal__body"
).textContent.trim(),
I18n.t("notifications.dismiss_confirmation.body.default", { count: 10 }),
"confirmation modal is shown when there are unread high pri notifications"
);
await click(".modal-footer .btn-default"); // click cancel on the dismiss modal
await click(".d-modal__footer .btn-default"); // click cancel on the dismiss modal
assert.notOk(markRead, "mark-read request isn't sent");
await click(".user-menu .notifications-dismiss");
await click(".modal-footer .btn-primary"); // click confirm on the dismiss modal
await click(".d-modal__footer .btn-primary"); // click confirm on the dismiss modal
assert.ok(markRead, "mark-read request is sent");
});
@ -976,7 +976,7 @@ acceptance("User menu - Dismiss button", function (needs) {
assert.strictEqual(
query(
".dismiss-notification-confirmation .modal-body"
".dismiss-notification-confirmation .d-modal__body"
).textContent.trim(),
I18n.t("notifications.dismiss_confirmation.body.bookmarks", {
count: 103,
@ -985,7 +985,7 @@ acceptance("User menu - Dismiss button", function (needs) {
);
assert.notOk(markRead, "mark-read request isn't sent");
await click(".modal-footer .btn-primary"); // confirm dismiss on the dismiss modal
await click(".d-modal__footer .btn-primary"); // confirm dismiss on the dismiss modal
assert.notOk(
exists("#quick-access-bookmarks ul li.notification"),
@ -1032,7 +1032,7 @@ acceptance("User menu - Dismiss button", function (needs) {
assert.strictEqual(
query(
".dismiss-notification-confirmation .modal-body"
".dismiss-notification-confirmation .d-modal__body"
).textContent.trim(),
I18n.t("notifications.dismiss_confirmation.body.messages", {
count: 89,
@ -1041,7 +1041,7 @@ acceptance("User menu - Dismiss button", function (needs) {
);
assert.notOk(markRead, "mark-read request isn't sent");
await click(".modal-footer .btn-primary"); // confirm dismiss on the dismiss modal
await click(".d-modal__footer .btn-primary"); // confirm dismiss on the dismiss modal
assert.notOk(
exists("#quick-access-messages ul li.notification"),

View File

@ -295,7 +295,7 @@ acceptance("User Preferences - Account", function (needs) {
);
await click("#gravatar");
await click(".modal-footer .btn");
await click(".d-modal__footer .btn");
assert.deepEqual(
pickAvatarRequestData,

View File

@ -21,7 +21,7 @@ async function setStatus(status) {
await openUserStatusModal();
await pickEmoji(status.emoji);
await fillIn(".user-status-description", status.description);
await click(".modal-footer .btn-primary"); // save and close modal
await click(".d-modal__footer .btn-primary"); // save and close modal
}
acceptance("User Profile - Account - User Status", function (needs) {

View File

@ -323,7 +323,7 @@ acceptance(
);
await durationDropdown.expand();
await durationDropdown.selectRowByIndex(0);
await click(".modal-footer .btn-primary");
await click(".d-modal__footer .btn-primary");
await notificationLevelDropdown.expand();
assert.strictEqual(
notificationLevelDropdown.selectedRow().value(),

View File

@ -1,8 +1,9 @@
import { tracked } from "@glimmer/tracking";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { click, render, settled } from "@ember/test-helpers";
import { click, render, settled, triggerKeyEvent } from "@ember/test-helpers";
import { module, test } from "qunit";
import DButton from "discourse/components/d-button";
import DModal from "discourse/components/d-modal";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
@ -17,8 +18,8 @@ module("Integration | Component | d-modal", function (hooks) {
@subtitle="Modal Subtitle"
/>
</template>);
assert.dom(".d-modal .title h3").hasText("Modal Title");
assert.dom(".d-modal .subtitle").hasText("Modal Subtitle");
assert.dom(".d-modal .d-modal__title-text").hasText("Modal Title");
assert.dom(".d-modal .d-modal__subtitle-text").hasText("Modal Subtitle");
});
test("named blocks", async function (assert) {
@ -104,8 +105,8 @@ module("Integration | Component | d-modal", function (hooks) {
/>
</template>);
assert.dom(".d-modal .modal-header").hasClass("my-header-class");
assert.dom(".d-modal .modal-body").hasClass("my-body-class");
assert.dom(".d-modal .d-modal__header").hasClass("my-header-class");
assert.dom(".d-modal .d-modal__body").hasClass("my-body-class");
});
test("as a form", async function (assert) {
@ -130,4 +131,30 @@ module("Integration | Component | d-modal", function (hooks) {
await click(".d-modal button[type=submit]");
assert.deepEqual(submittedFormData.get("name"), "John Doe");
});
test("default action on enter", async function (assert) {
let actionCalled = false;
const someAction = () => {
actionCalled = true;
};
await render(<template>
<DModal @inline={{true}}>
<:body>
body content
</:body>
<:footer>
<DButton
@action={{someAction}}
@translatedLabel="Perform action"
class="btn-primary"
/>
</:footer>
</DModal>
</template>);
await triggerKeyEvent(".d-modal__body", "keydown", "Enter");
assert.true(actionCalled, "pressing enter triggers the default button");
});
});

View File

@ -10,6 +10,7 @@
position: absolute;
top: 0.265em;
right: 0.66em;
padding: 0;
.d-icon {
color: var(--primary-medium);
}

View File

@ -1,46 +1,10 @@
.caps-lock-warning {
color: var(--danger);
font-size: var(--font-down-1);
font-weight: bold;
margin-top: 0.5em;
}
.discourse-no-touch #login-form {
margin: 0;
}
.discourse-touch .caps-lock-warning {
display: none;
}
#discourse-modal.login-modal-body:not(.has-alt-auth) .modal-inner-container {
max-width: 28em; // prevents long alerts from expanding the modal width
}
.login-modal:not(.hidden).has-alt-auth.no-local-login {
min-width: fit-content;
background: var(--secondary);
#login-buttons:not(.hidden) {
.btn.btn-social,
.btn {
border: 1px solid var(--primary-low-mid);
&:hover,
&:focus {
background: var(--primary-very-low);
}
}
}
}
body.invite-page {
background-color: var(--primary-50);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen-Sans, Ubuntu, Cantarell, Arial, sans-serif;
}
// Create Account + Login
.d-modal.create-account,
.d-modal.login-modal,
.invites-show,
.admin-invite-page {
.modal-inner-container {
position: relative;
@ -72,214 +36,11 @@ body.invite-page {
overflow: hidden;
display: inline;
}
.login-welcome-header {
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
max-width: 33em;
word-break: break-word;
.login-title {
margin-bottom: 0;
}
.login-subheader {
align-self: start;
grid-row-start: 2;
margin: 0;
}
.waving-hand {
width: 35px;
height: 35px;
margin-left: 0.5em;
align-self: center;
}
}
#login-buttons {
.btn {
background-color: #ffffff;
color: #000000;
justify-content: flex-start;
min-width: fit-content;
margin-bottom: 0.5em;
}
}
.modal-footer {
.inline-spinner {
display: inline-flex;
}
}
.modal-body.no-local-login + .modal-footer {
display: none;
}
&.awaiting-approval {
display: none;
}
.no-local-logins {
// when third-party auth is available, but not local logins
.login-left-side,
.login-welcome-header {
padding: 3em 1em 3em 3em;
}
}
}
// Login Form Styles
.login-modal:not(.hidden),
.create-account,
.invites-show {
#login-form,
.login-form,
.invite-form {
.input-group {
position: relative;
display: flex;
flex-direction: column;
margin-bottom: 0.8em;
input,
.select-kit-header {
padding: 0.75em 0.77em;
min-width: 250px;
box-shadow: none;
margin-bottom: 0.25em;
width: 100%;
}
input:focus {
outline: none;
border: 1px solid var(--tertiary);
box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
}
input:disabled {
background-color: var(--primary-low);
}
span.more-info {
color: var(--primary-medium);
min-height: 1.4em; // prevents height increase due to tips
}
label.alt-placeholder,
.user-field.text label.control-label,
.user-field.dropdown label.control-label,
.user-field.multiselect label.control-label {
color: var(--primary-medium);
font-size: 16px;
font-weight: normal;
position: absolute;
pointer-events: none;
left: 1em;
top: 10px;
box-shadow: 0 0 0 0px rgba(var(--tertiary-rgb), 0);
transition: 0.2s ease all;
}
.user-field.text label.control-label,
.user-field.dropdown label.control-label,
.user-field.multiselect label.control-label {
z-index: 999;
top: -8px;
left: calc(1em - 0.25em);
background-color: var(--secondary);
font-size: $font-down-1;
}
.user-field.text:focus-within,
.user-field.dropdown:focus-within,
.user-field.multiselect:focus-within {
z-index: 1000; // ensures the active input is always on top of sibling input labels
}
input:focus + label.alt-placeholder,
input.value-entered + label.alt-placeholder {
top: -8px;
left: calc(1em - 0.25em);
background-color: var(--secondary);
padding: 0 0.25em 0 0.25em;
font-size: var(--font-down-1);
}
input.alt-placeholder:invalid {
color: var(--primary);
}
#email-login-link {
transition: opacity 0.5s;
&.no-login-filled {
opacity: 0;
visibility: hidden;
}
}
#email-login-link,
.login__password-links {
font-size: var(--font-down-1);
display: flex;
justify-content: space-between;
}
.tip:not(:empty) + label.more-info {
display: none;
}
}
#second-factor {
input {
width: 100%;
padding: 0.75em 0.5em;
min-width: 250px;
box-shadow: none;
margin-bottom: 2em;
}
input:focus {
outline: none;
border: 1px solid var(--tertiary);
box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
}
}
// user fields input groups will
// be styled differently
.user-fields .input-group {
.user-field {
&.text {
&.value-entered label.control-label {
top: -8px;
left: calc(1em - 0.25em);
background-color: var(--secondary);
padding: 0 0.25em 0 0.25em;
font-size: 14px;
color: var(--primary-medium);
}
label.control-label {
color: var(--primary-medium);
font-size: 16px;
position: absolute;
pointer-events: none;
left: 1em;
top: 12px;
transition: 0.2s ease all;
max-width: calc(100% - 2em);
white-space: nowrap;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
}
}
details:not(.has-selection) span.name,
details:not(.has-selection) span.formatted-selection {
color: var(--primary-medium);
}
.select-kit-row span.name {
color: var(--primary);
}
.select-kit.combo-box.is-expanded summary {
outline: none;
border: 1px solid var(--tertiary);
box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
}
.controls .checkbox-label {
input[type="checkbox"].ember-checkbox {
width: 1em !important;
min-width: unset;
}
}
}
}
}
}
#login-link,
@ -289,36 +50,6 @@ body.invite-page {
// the second button can wrap in some locales, and this helps alignment
}
.create-account {
.disclaimer {
color: var(--primary-medium);
margin-bottom: 0.5em;
}
@media screen and (min-width: 701px) {
.modal-body {
max-width: 40em;
}
}
.user-field {
input[type="text"] {
margin-bottom: 0;
width: 100%;
}
.select-kit {
width: 100%;
}
.controls {
.instructions {
margin-top: 0.15em;
color: var(--primary-medium);
line-height: var(--line-height-medium);
}
}
}
}
.password-reset {
.instructions {
label {
@ -354,14 +85,6 @@ body.invite-page {
}
}
// alternate login / create new account buttons should be de-emphasized
button#login-link,
button#new-account-link {
background: transparent;
color: var(--primary-high);
}
#security-key {
display: flex;
flex-wrap: wrap;
@ -414,8 +137,3 @@ button#new-account-link {
}
}
}
.create-account__password-info {
display: flex;
justify-content: space-between;
}

View File

@ -1,5 +1,104 @@
// Modal wrappers
.d-modal {
pointer-events: none; // Allow clicks through wrappers so they hit the adjacent backdrop element
&__wrapper {
display: flex;
align-items: center;
width: 100%;
height: 100%;
position: fixed;
top: 0;
z-index: z("modal", "content");
overflow: auto;
.-inline & {
position: relative;
}
html.keyboard-visible body.ios-safari-composer-hacks & {
height: calc(var(--composer-vh, 1vh) * 100);
}
}
&__container {
display: flex;
flex-direction: column;
pointer-events: auto;
box-sizing: border-box;
margin: 0 auto;
max-height: 80vh;
background-color: var(--secondary);
box-shadow: var(--shadow-modal);
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0.5rem 0.5rem 1.5rem; //offset for close button padding
border-bottom: 1px solid var(--primary-low);
.modal-close .d-icon {
font-size: var(--font-up-2);
color: var(--primary-high);
}
}
&__title-text,
&__subtitle-text {
margin: 0;
}
&__body {
overflow-y: auto;
padding: 1rem 1.5rem;
input {
width: auto;
&[type="text"] {
width: 100%;
}
}
textarea {
width: 100%;
height: 80px;
}
pre code {
white-space: pre-wrap;
max-width: 100%;
}
}
&__footer {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 1rem 1.5rem;
border-top: 1px solid var(--primary-low);
gap: 0.5rem;
}
&__backdrop {
user-select: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: z("modal", "overlay");
background-color: #000;
animation: fade 0.3s forwards;
filter: alpha(opacity=70);
@media (prefers-reduced-motion) {
animation-duration: 0s;
}
}
}
//legacy
.fixed-modal:not(#discourse-modal) {
// new <DModal /> only, not <DModalLegacy />
pointer-events: none; // Allow clicks through wrappers so they hit the adjacent backdrop element
@ -119,7 +218,7 @@
opacity: 0;
}
to {
opacity: 0.9;
opacity: 0.7;
}
}
@ -518,7 +617,7 @@
max-height: 450px;
.flag-action-type-details {
width: 100%;
max-width: 500px;
// max-width: 500px;
line-height: var(--line-height-large);
}
}
@ -532,143 +631,6 @@
font-size: var(--font-down-1);
}
.jump-to-post-modal {
.modal-inner-container {
max-width: 350px;
}
.modal-body {
overflow-y: visible;
#post-jump {
margin: 0;
width: 100px;
}
.date-picker {
margin: 0;
width: 180px;
}
.input-hint-text {
color: var(--primary);
}
.jump-to-post-control .index {
color: var(--primary-medium);
}
.jump-to-date-control {
display: flex;
align-items: center;
.input-hint-text {
margin-left: 0;
margin-right: 0.5em;
}
}
.separator {
display: flex;
align-items: center;
margin: 0.5em auto;
hr {
flex: 1 0 0px;
}
.text {
margin: 0 0.5em 0 0;
color: var(--primary-medium);
}
}
}
}
// move-to topic modal
.choose-topic-modal {
#split-topic-name,
#choose-topic-title,
#choose-message-title {
width: 100%;
}
.category-chooser {
margin-bottom: 9px;
width: 100% !important;
.category-row {
max-width: 485px;
}
}
.controls.existing-topic {
margin-bottom: 0.75em;
}
// move to existing topic
.existing-topic {
.radio {
flex-wrap: wrap;
}
.topic-title {
max-width: 90%;
}
.topic-categories {
display: flex;
font-weight: normal;
gap: 0.5em;
width: 100%;
}
}
#choosing-topic {
form {
hr {
margin-bottom: 0.5em;
}
}
}
}
.publish-page-modal {
.modal-body {
p.publish-description {
margin-top: 0;
}
input.publish-slug {
width: 100%;
}
.publish-url {
margin-bottom: 1em;
.example-url,
.invalid-slug {
font-weight: bold;
}
}
.publish-slug:disabled {
cursor: not-allowed;
}
.controls {
margin-bottom: 1em;
.description {
margin: 0;
display: flex;
align-items: center;
}
}
}
.modal-footer {
display: flex;
.close-publish-page {
margin-left: auto;
margin-right: 0;
}
}
}
.ignore-duration-with-username-modal {
.future-date-input {
margin-top: 1em;
@ -713,26 +675,6 @@
}
}
.topic-bulk-actions-modal {
.modal-inner-container {
min-width: 0;
width: 100%;
}
p {
margin-top: 0;
}
.bulk-buttons {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(12em, 1fr));
gap: 0.5em;
max-width: 100%;
min-width: 0;
.d-button-label {
@include ellipsis;
}
}
}
.bulk-notification-list {
margin-bottom: 1.5em;
}
@ -747,34 +689,6 @@
}
}
.modal.edit-slow-mode-modal {
.slow-mode-label {
display: inline-flex;
}
.alert.alert-info {
margin-bottom: 0;
}
.input-small {
width: 10%;
}
}
.group-add-members-modal {
.input-group {
margin-bottom: 0.5em;
&:last-child {
margin-bottom: 0;
}
}
.user-chooser {
width: calc(100%);
}
}
.modal-body .codeblock-buttons {
margin: 0;

View File

@ -43,32 +43,6 @@
}
}
// topic share modal
.share-topic-modal {
.modal-header {
.subtitle {
color: var(--primary-med-or-secondary-med);
}
}
form {
margin-bottom: 0;
}
.invite-users {
display: flex;
flex-direction: column;
margin: 1.5em 0 0.25em;
width: 100%;
button {
margin-top: 0;
margin-right: 0;
}
}
}
// topic invite modal
.create-invite-modal {

View File

@ -1,44 +1,25 @@
.bookmark-with-reminder.modal {
.modal-inner-container {
box-sizing: border-box;
min-width: 310px;
}
.bookmark-reminder-modal {
.d-modal {
&__body {
box-sizing: border-box;
.modal-footer {
margin: 0;
.control-label {
font-weight: 700;
}
.delete-bookmark {
margin-left: auto;
margin-right: 0;
.ember-text-field.bookmark-name {
width: 100%;
margin-bottom: 0.5em;
}
}
}
&__footer {
margin: 0;
.modal-inner-container {
max-width: 375px;
}
.modal-body {
width: 375px;
box-sizing: border-box;
max-height: 70dvh;
@media (max-width: 600px) {
width: 100%;
.delete-bookmark {
margin-left: auto;
margin-right: 0;
}
}
.control-label {
font-weight: 700;
}
.ember-text-field.bookmark-name {
min-width: 220px;
width: 100%;
margin-bottom: 0.5em;
}
}
.title {
max-width: 300px;
}
.existing-reminder-at-alert {

View File

@ -322,9 +322,8 @@
}
}
&.close {
padding: 0;
background: transparent;
font-size: var(--font-up-3);
font-size: var(--font-up-2);
.d-icon {
color: var(--primary-high);
}
@ -337,6 +336,12 @@
}
}
}
&:focus {
background: transparent;
.d-icon {
color: var(--primary);
}
}
}
&.btn-text {
color: var(--tertiary);

View File

@ -1 +1,3 @@
@import "login-modal";
@import "modal-overrides";
@import "user-status";

View File

@ -0,0 +1,299 @@
/*
* shared styles
*/
.d-modal.login-modal,
.d-modal.create-account {
&.awaiting-approval {
display: none;
}
.d-modal {
&__container {
position: relative;
}
&__header {
border-bottom: none;
padding: 0;
position: absolute;
top: 0.75em;
right: 0.75em;
z-index: z("max");
}
&__body {
display: flex;
gap: 2rem;
padding: 0;
}
&__footer {
flex-wrap: nowrap;
padding: 0;
border: 0;
.inline-spinner {
display: inline-flex;
}
}
}
.login-subheader {
font-size: var(--font-up-1);
margin: 0;
}
.login-left-side {
padding: 3rem;
}
.login-welcome-header {
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
overflow-wrap: anywhere;
}
.login-title {
font-size: var(--font-up-6);
margin: 0;
}
.waving-hand {
width: 35px;
height: 35px;
margin-left: 0.5em;
align-self: center;
}
.login-right-side {
background: var(--tertiary-or-tertiary-low);
padding: 3.5rem 3rem;
}
#login-buttons {
display: flex;
flex-direction: column;
justify-content: center;
gap: 1rem;
height: 100%;
}
#login-form {
margin: 3em 0 2em 0;
}
.tip {
&.bad {
color: var(--danger);
display: block;
}
}
.caps-lock-warning {
color: var(--danger);
font-size: var(--font-down-1);
font-weight: bold;
margin-top: 0.5em;
}
#modal-alert {
box-sizing: border-box;
display: inline-block;
// if you want to use flexbox here make sure child elements like <b> don't cause line breaks
padding: 1em 3.5em 1em 1em; // large right padding to make sure long text isn't under the close button
width: 100%;
max-width: 100%;
margin-bottom: 0;
min-height: 35px;
&:empty {
min-height: 0px;
padding: 0px;
overflow: hidden;
display: inline;
}
}
}
/* end shared styles */
.d-modal.create-account {
.d-modal {
&__footer {
flex-direction: column;
align-items: flex-start;
}
&__footer-buttons {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
}
}
.disclaimer {
color: var(--primary-medium);
margin-bottom: 0.5em;
}
.create-account__password-info {
display: flex;
justify-content: space-between;
}
}
// Login Form Styles
.login-modal,
.create-account,
.invites-show {
#login-form,
.login-form,
.invite-form {
.input-group {
position: relative;
display: flex;
flex-direction: column;
margin-bottom: 0.8em;
input,
.select-kit-header {
padding: 0.75em 0.77em;
min-width: 250px;
margin-bottom: 0.25em;
width: 100%;
}
input:focus {
outline: none;
border: 1px solid var(--tertiary);
box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
}
input:disabled {
background-color: var(--primary-low);
}
span.more-info {
color: var(--primary-medium);
min-height: 1.4em; // prevents height increase due to tips
overflow-wrap: anywhere;
}
label.alt-placeholder,
.user-field.text label.control-label,
.user-field.dropdown label.control-label,
.user-field.multiselect label.control-label {
color: var(--primary-medium);
font-size: 16px;
font-weight: normal;
position: absolute;
pointer-events: none;
left: 1em;
top: 10px;
box-shadow: 0 0 0 0px rgba(var(--tertiary-rgb), 0);
transition: 0.2s ease all;
}
.user-field.text label.control-label,
.user-field.dropdown label.control-label,
.user-field.multiselect label.control-label {
z-index: 999;
top: -8px;
left: calc(1em - 0.25em);
background-color: var(--secondary);
padding: 0 0.25em 0 0.25em;
font-size: $font-down-1;
}
.user-field.text:focus-within,
.user-field.dropdown:focus-within,
.user-field.multiselect:focus-within {
z-index: 1000; // ensures the active input is always on top of sibling input labels
}
input:focus + label.alt-placeholder,
input.value-entered + label.alt-placeholder {
top: -8px;
left: calc(1em - 0.25em);
background-color: var(--secondary);
padding: 0 0.25em 0 0.25em;
font-size: var(--font-down-1);
}
input.alt-placeholder:invalid {
color: var(--primary);
}
#email-login-link {
transition: opacity 0.5s;
&.no-login-filled {
opacity: 0;
visibility: hidden;
}
}
#email-login-link,
.login__password-links {
font-size: var(--font-down-1);
display: flex;
justify-content: space-between;
}
.tip:not(:empty) + label.more-info {
display: none;
}
}
#second-factor {
input {
width: 100%;
padding: 0.75em 0.5em;
min-width: 250px;
box-shadow: none;
}
input:focus {
outline: none;
border: 1px solid var(--tertiary);
box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
}
}
// user fields input groups will
// be styled differently
.user-fields .input-group {
.user-field {
&.text {
&.value-entered label.control-label {
top: -8px;
left: calc(1em - 0.25em);
background-color: var(--secondary);
padding: 0 0.25em 0 0.25em;
font-size: 14px;
color: var(--primary-medium);
}
label.control-label {
color: var(--primary-medium);
font-size: 16px;
position: absolute;
pointer-events: none;
left: 1em;
top: 12px;
transition: 0.2s ease all;
max-width: calc(100% - 2em);
white-space: nowrap;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
}
}
details:not(.has-selection) span.name,
details:not(.has-selection) span.formatted-selection {
color: var(--primary-medium);
}
.select-kit-row span.name {
color: var(--primary);
}
.select-kit.combo-box.is-expanded summary {
outline: none;
border: 1px solid var(--tertiary);
box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
}
.controls .checkbox-label {
input[type="checkbox"].ember-checkbox {
width: 1em !important;
min-width: unset;
}
}
}
}
}
}

View File

@ -0,0 +1,310 @@
.d-modal.insert-hyperlink-modal {
.insert-link {
overflow-y: visible;
input {
min-width: 300px;
}
.inputs {
position: relative;
.spinner {
position: absolute;
right: 8px;
top: -15px;
width: 10px;
height: 10px;
}
.internal-link-results {
position: absolute;
top: 70%;
padding: 5px 10px;
box-shadow: var(--shadow-card);
z-index: 5;
background-color: var(--secondary);
max-height: 150px;
width: 90%;
overflow-y: auto;
> .search-link {
padding: 6px;
border-bottom: 1px solid var(--primary-low);
cursor: pointer;
display: block;
&:hover,
&:focus {
background-color: var(--highlight-bg);
}
.search-category {
display: flex;
align-items: center;
}
.discourse-tags {
font-size: var(--font-down-1);
}
}
}
}
}
}
.sidebar-section-form-modal {
.sidebar-section-form-link {
.select-kit {
width: 100%;
}
}
}
.jump-to-post-modal {
.modal-body,
.d-modal__body {
overflow-y: visible;
#post-jump {
margin: 0;
width: 100px;
}
.date-picker {
margin: 0;
width: 180px;
}
.input-hint-text {
color: var(--primary);
}
.jump-to-post-control .index {
color: var(--primary-medium);
}
.jump-to-date-control {
display: flex;
align-items: center;
.input-hint-text {
margin-left: 0;
margin-right: 0.5em;
}
}
.separator {
position: relative;
margin: 0.5em auto;
hr {
flex: 1 0 0px;
}
.text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--secondary);
padding: 0.25rem 0.5rem;
color: var(--primary-medium);
}
}
}
}
.d-modal.choose-topic-modal {
.d-modal {
&__container {
width: 40em; //fixed width to avoid jumps when switching to different views
}
}
#split-topic-name,
#choose-topic-title,
#choose-message-title {
width: 100%;
}
.category-chooser {
margin-bottom: 9px;
}
.controls.existing-topic {
margin-bottom: 0.75em;
}
// move to existing topic
.existing-topic {
.radio {
flex-wrap: wrap;
}
.topic-title {
max-width: 90%;
}
.topic-categories {
display: flex;
font-weight: normal;
gap: 0.5em;
width: 100%;
}
}
#choosing-topic {
form {
hr {
margin-bottom: 0.5em;
}
}
}
}
.d-modal.publish-page-modal {
.d-modal {
&__body {
p.publish-description {
margin-top: 0;
}
.publish-url {
margin-bottom: 1em;
.example-url,
.invalid-slug {
font-weight: bold;
}
}
.publish-slug:disabled {
cursor: not-allowed;
}
.controls {
margin-bottom: 1em;
.description {
margin: 0;
display: flex;
align-items: center;
input[type="checkbox"] {
margin-top: 0;
}
}
}
}
&__footer {
display: flex;
.close-publish-page {
margin-left: auto;
margin-right: 0;
}
}
}
}
.d-modal.topic-bulk-actions-modal {
.d-modal {
&__container {
min-width: 0;
width: 100%;
}
}
p {
margin-top: 0;
}
.bulk-buttons {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(12em, 1fr));
gap: 0.5em;
max-width: 100%;
min-width: 0;
.d-button-label {
@include ellipsis;
}
}
}
.d-modal.edit-slow-mode-modal {
.slow-mode-label {
display: inline-flex;
}
.alert.alert-info {
margin-bottom: 0;
}
.input-small {
width: 10%;
}
}
.d-modal.group-add-members-modal {
.input-group {
margin-bottom: 0.5em;
&:last-child {
margin-bottom: 0;
}
}
.user-chooser {
width: calc(100%);
}
}
.d-modal.share-topic-modal {
.d-modal {
&__title {
display: flex;
align-items: center;
gap: 0.5rem;
}
&__subtitle-text {
color: var(--primary-med-or-secondary-med);
}
}
form {
margin-bottom: 0;
}
}
.d-modal.chat-modal-new-message {
.d-modal {
&__wrapper {
align-items: flex-start;
}
&__container {
width: var(--modal-max-width);
margin-top: 1rem;
}
&__body {
padding: 0;
input {
width: auto;
}
}
}
.chat-message-creator {
&__add-members-header-container {
padding-inline: 1rem 0.5rem;
}
&__add-members-header {
align-items: center;
}
&__members {
min-height: unset;
padding: 0.25rem 0.5rem;
}
&__add-members__close-btn {
margin-top: 0;
height: 33px;
}
}
@media screen and (max-width: 768px) {
.d-modal {
&__container {
width: 100%;
margin-top: 0;
}
}
}
}

View File

@ -1,38 +1,28 @@
.user-status.modal {
.modal-inner-container {
box-sizing: border-box;
min-width: 310px;
}
.modal-footer {
.delete-status {
margin-left: auto;
margin-right: 0;
}
}
.modal-body {
width: 375px;
box-sizing: border-box;
@media (max-width: 600px) {
width: 100%;
.d-modal {
&__footer {
.delete-status {
margin-left: auto;
margin-right: 0;
}
}
.control-group-remove-status {
margin-top: 25px;
}
&__body {
.control-group-remove-status {
margin-top: 25px;
}
.pause-notifications {
margin-top: 1.5em;
}
.pause-notifications {
margin-top: 1.5em;
}
.control-label {
font-weight: 700;
}
.control-label {
font-weight: 700;
}
.tap-tile:last-child {
border: 0;
.tap-tile:last-child {
border: 0;
}
}
}
}

View File

@ -8,7 +8,6 @@
@import "header";
@import "invite-signup";
@import "latest-topic-list";
@import "login";
@import "menu-panel";
@import "modal";
@import "topic-list";

View File

@ -1,450 +0,0 @@
// shared styles used
// on both the login and
// create account modals
.login-modal,
.create-account {
.modal-body {
&.has-alt-auth {
background: var(--tertiary-or-tertiary-low);
}
}
.btn-flat.btn.modal-close svg {
color: rgba(var(--primary-rgb), 0.5);
&:hover {
color: rgba(var(--primary-rgb), 0.85);
}
}
h2.login-with {
color: var(--secondary);
}
#login-buttons:not(.hidden) {
display: flex;
flex: 0 1 auto;
flex-direction: column;
align-items: stretch;
min-height: 75px;
min-width: 200px;
order: 2;
&:focus-within,
&:hover {
button:not(:hover):not(:focus) {
opacity: 0.75;
}
}
button {
margin: 0.35em 0;
&:first-of-type {
margin-top: 0;
}
&:last-of-type {
margin-bottom: 0;
}
&:lang(zh_CN) {
min-width: 200px;
}
&:first-of-type:last-of-type {
&.oauth2_basic {
// Handles a strange fringe case where OAuth2 and account approval are enabled
margin-left: auto;
margin-right: auto;
justify-content: center;
}
}
}
}
.tip {
display: block;
overflow: hidden;
word-wrap: break-word;
margin-bottom: 0;
max-width: 280px;
@media screen and (min-width: 500px) {
max-width: 400px;
}
}
}
.has-alt-auth {
.tip {
max-width: 100%;
}
}
.login-modal:not(.hidden).has-alt-auth.no-local-login {
padding: 0 2em !important;
min-width: 350px !important;
.login-right-side {
width: 100%;
padding: 2em 0;
#login-buttons:not(.hidden) {
width: 80%;
.btn.btn-social:first-of-type {
margin-top: 2em;
}
.btn.btn-social,
.btn,
.login-with-email-button {
border: 1px solid var(--primary-low-mid);
margin-bottom: 0.5em;
&:hover,
&:focus {
background: var(--primary-very-low) !important;
}
}
}
}
}
// styles used on
// login modal only
.login-modal:not(.hidden) {
padding: 0 !important;
.login-modal-body.has-alt-auth {
min-width: 100%;
.login-left-side {
align-self: stretch;
}
.login-right-side {
padding: 0 3em;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
#login-buttons {
button {
&.login-with-email-button {
border-radius: 4px;
.d-icon {
color: #000 !important;
}
&:hover,
&:focus {
box-shadow: 0 0 0 2px rgba(var(--primary-rgb), 0.25);
.d-icon {
color: #000 !important;
}
}
}
}
}
}
}
.login-left-side {
background: var(--secondary);
width: calc(100% - 4em);
display: flex;
flex-direction: column;
padding: 3em 3em 2.5em;
.login-welcome-header {
.login-title {
line-height: var(--line-height-small);
font-size: var(--font-up-6);
}
p.login-subheader {
font-size: var(--font-up-1);
}
}
#credentials {
display: flex;
flex-direction: column;
align-items: flex-start !important;
}
#login-form {
margin: 3em 0 1.25em 0;
}
.modal-footer {
display: flex;
flex-direction: row;
width: 100%;
padding: 0;
border: 0;
}
}
.modal-body,
#credentials {
display: flex;
align-items: center;
&.hidden {
display: none;
}
}
}
.login-modal-body.second-factor {
max-width: 30em;
.login-left-side {
width: 100%;
#login-form {
margin: 2em 0 0 0;
}
}
#new-account-link {
display: none;
}
.modal-footer {
margin-top: 1em;
}
}
.login-form {
.tip {
&:not(:empty) {
min-height: 1.75em;
&.bad .svg-icon {
vertical-align: middle;
}
}
}
}
// styles used on the
// create account
// modal only
.d-modal.create-account {
.modal-body {
min-width: 100%;
overflow: hidden;
}
.has-alt-auth .create-account-form {
display: grid;
grid-template-columns: 60% 40%;
grid-template-rows: auto 1fr;
grid-template-areas:
"header login-buttons"
"form login-buttons"
"footer login-buttons";
}
.create-account-form {
display: flex;
flex-direction: column;
width: 100%;
position: relative;
}
.create-account-login-buttons {
display: none;
}
.has-alt-auth .create-account-login-buttons {
grid-area: login-buttons;
padding: 1em;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
h3 {
color: var(--secondary);
text-align: center;
}
#login-buttons {
button:first-of-type {
margin-top: 0;
}
}
}
.modal-body {
display: flex;
padding: 0;
}
.modal-footer {
grid-area: footer;
background-color: var(--secondary);
position: relative;
&:before {
content: "";
display: block;
position: absolute;
width: 100%;
pointer-events: none;
top: calc(-2em - 1px);
left: 0;
height: 2em;
background-image: linear-gradient(
to bottom,
rgba(var(--secondary-rgb), 0),
rgba(var(--secondary-rgb), 1)
);
}
}
.login-welcome-header {
grid-area: header;
padding: 3em 2em 1em;
background-color: var(--secondary);
.login-title {
font-size: var(--font-up-6);
line-height: var(--line-height-medium);
}
p.login-subheader {
font-size: var(--font-up-1);
align-self: start;
}
}
.modal-body:not(.has-alt-auth) .login-form {
&:after {
width: 100%;
}
}
.has-alt-auth .login-form,
.login-form {
background: var(--secondary);
padding: 1em 0 0 0;
grid-area: form;
overflow-y: scroll;
.input-group {
padding: 0 2em;
margin-bottom: 1em;
input {
width: 100%;
}
input:not(.value-entered):not(:focus) + label.alt-placeholder {
top: 12px;
left: 3em;
}
input:focus + label.alt-placeholder,
input.value-entered + label.alt-placeholder {
top: -8px;
left: calc(3em - 0.25em);
font-size: var(--font-down-1);
}
}
.password-confirmation {
display: none;
}
scrollbar-color: rgba(var(--primary-rgb), 0.5) transparent;
&::-webkit-scrollbar {
width: 15px;
}
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 14px 14px transparent;
border: 4px solid transparent;
}
&::-webkit-scrollbar-thumb {
box-shadow: inset 0 0 14px 14px rgba(var(--primary-rgb), 0.5);
border: 4px solid transparent;
border-radius: 14px;
}
/* set button(top and bottom of the scrollbar) */
&::-webkit-scrollbar-button {
display: none;
}
}
.user-fields {
margin-bottom: 1em;
}
.user-field {
&.text {
label.control-label {
left: 3em !important;
max-width: calc(100% - 6em) !important;
}
&.value-entered label.control-label,
label.control-label.value-entered {
left: calc(3em - 0.25em) !important;
}
}
display: flex;
flex-direction: column;
}
.tos-agree {
margin-bottom: 12px;
}
}
.password-reset {
.col-form {
padding: 0.5em 2em 1em;
max-width: 30em;
}
.col-image {
width: 175px;
img {
width: 100%;
}
}
details {
width: 100%;
}
h2 {
margin-bottom: 12px;
}
input:not([type="checkbox"]) {
// 80% width on checkboxes messes up the layout for custom fields
width: 80%;
}
}
.password-reset {
.col-form {
padding-top: 40px;
}
}
// styling for small-width desktop screens
// when editing, test with DiscourseHub auth window on iPadOS
// it has a window width of ~650px
@media screen and (max-width: 700px) {
.d-modal.create-account,
.d-modal.login-modal {
.modal-inner-container {
width: 100%;
}
.modal-body:not(.reorder-categories):not(.poll-ui-builder):not(
.poll-breakdown
) {
max-height: 90vh !important; // overrides base/modal.scss
}
.login-welcome-header,
.login-left-side .login-welcome-header {
padding-top: 1em;
.login-title {
font-size: var(--font-up-4);
}
p.login-subheader {
font-size: var(--font-0);
}
}
.modal-footer .btn:last-of-type {
margin-right: 0;
}
}
.d-modal.login-modal .login-modal-body.has-alt-auth {
.login-left-side {
padding-left: 1.5em;
padding-right: 1em;
padding-top: 1em;
.modal-footer {
width: auto;
.btn-large {
padding-left: 12px;
padding-right: 12px;
}
}
}
.login-right-side {
padding: 1em;
}
}
}
.email-login {
width: 550px;
.password-reset-img {
height: 150px;
}
}

View File

@ -1,3 +1,19 @@
.d-modal {
--modal-max-width: 600px;
--modal-width: 30em; // set in ems to scale with user font-size
--modal-min-width: 400px;
&__container {
max-width: var(--modal-max-width);
min-width: var(--modal-min-width);
.d-modal.-large & {
max-width: 800px;
}
.d-modal.-max & {
max-width: 90vw;
}
}
}
//legacy
// Hardcode to be the same as before for now. I would recommend not using bootbox, or finding a way so the html structure can be the same
.bootbox.modal {
top: 50%;

View File

@ -16,8 +16,8 @@
@import "header";
@import "invite-signup";
@import "lightbox";
@import "login";
@import "menu-panel";
@import "login-modal";
@import "modal";
@import "new-user";
@import "onebox";

View File

@ -0,0 +1,76 @@
.discourse-touch {
.d-modal.login-modal,
.d-modal.create-account {
.d-modal {
&__body {
flex-direction: column;
gap: unset;
padding-inline: 0.5rem;
}
&__footer {
border-top: 1px solid var(--primary-low);
padding: 1rem;
font-size: var(--font-down-1);
}
}
.close {
font-size: var(--font-up-3);
}
.login-welcome-header {
padding: 1rem;
}
#login-buttons {
flex-direction: row;
flex-wrap: wrap;
padding: 0 1rem;
gap: 0.25em;
margin-bottom: 1rem;
.btn {
margin: 0;
padding-block: 0.65rem;
border: 1px solid var(--primary-low);
flex: 1 1 calc(50% - 0.25em);
font-size: var(--font-down-1);
white-space: nowrap;
max-width: calc(50% - 0.125em);
&:last-child {
margin-right: auto;
}
}
}
#login-form,
.login-form {
margin: 0;
padding: 1rem;
.input-group {
input {
height: 2.5em;
margin-bottom: 0.25em;
}
margin-bottom: 1em;
label.alt-placeholder {
top: 8px;
}
input:focus + label,
input.value-entered + label.alt-placeholder {
top: -10px;
}
input.alt-placeholder:invalid {
color: var(--primary);
}
label.more-info {
color: var(--primary-medium);
}
}
}
.caps-lock-warning {
display: none;
}
}
}

View File

@ -1,266 +0,0 @@
// shared styles
// used in both login and
// create account modals
.d-modal.login-modal:not(.hidden),
.d-modal.create-account {
.modal-body {
padding: 0;
}
&.fixed-modal .modal-inner-container {
max-width: 350px;
max-height: 90vh;
position: relative;
overflow: hidden;
}
.close {
padding: 0;
}
label {
margin: 0;
}
.tip:not(:empty) {
min-height: 1.4em; // prevents height jump when tips appear
}
.login-welcome-header {
padding: 1.5em 1em;
margin: 0 auto;
}
#login-buttons:not(.hidden) {
display: flex;
flex: 1 0 auto;
flex-wrap: wrap;
justify-content: center;
padding: 0 1em;
margin: 0;
gap: 0 0.25em;
.btn {
padding: 0.5em;
border: 1px solid var(--primary-low);
flex: 1 1 47%;
font-size: var(--font-down-1);
white-space: nowrap;
&:last-child {
margin-right: 0;
}
}
}
@media screen and (max-width: 750px) {
#login-buttons:not(.hidden) {
justify-content: center;
.btn {
&:nth-child(even) {
margin: 0 0 0.5em 0;
}
}
}
}
#login-form,
.login-form {
padding: 1em;
form {
display: flex;
justify-content: center;
flex-direction: column;
}
.input-group {
input {
height: 2.5em;
margin-bottom: 0.25em;
}
margin-bottom: 1em;
label.alt-placeholder {
top: 8px;
}
input:focus + label,
input.value-entered + label.alt-placeholder {
top: -10px;
}
input.alt-placeholder:invalid {
color: var(--primary);
}
label.more-info {
color: var(--primary-medium);
}
}
}
.user-fields .input-group {
.text.user-field {
label.control-label {
top: 8px;
}
&.value-entered label.control-label,
label.control-label.value-entered {
top: -10px;
left: calc(1em - 0.25em);
background-color: var(--secondary);
padding: 0 0.25em 0 0.25em;
font-size: 14px;
color: var(--primary-medium);
}
}
.controls .checkbox-label {
align-items: center;
input[type="checkbox"].ember-checkbox {
height: 15px;
}
}
}
.modal-body input[type="text"],
.modal-body input[type="email"],
.modal-body input[type="password"],
.modal-body input[type="tel"] {
margin-top: 0;
width: 100%;
box-sizing: border-box;
}
.modal-footer {
box-sizing: border-box;
margin: 0 auto;
font-size: var(--font-down-1);
padding: 1.25em;
}
.alert.alert-error {
padding: 0.5em 1em;
margin: 0;
}
#new-account-link {
white-space: nowrap;
}
}
// Styles for the
// login modal only
#discourse-modal {
&.d-modal.login-modal:not(.hidden) .modal-body {
max-height: 68vh !important; // overrides another important
}
}
.d-modal.login-modal {
#credentials {
width: 100%;
}
#login-form {
label {
float: left;
display: block;
}
}
}
.d-modal .login-modal {
&.second-factor + .modal-footer {
#new-account-link {
display: none;
}
}
}
.login-modal:not(.hidden).has-alt-auth.no-local-login {
#login-buttons:not(.hidden) {
width: 100%;
}
}
// styles for the
// create account
// modal only
.d-modal.create-account {
.modal-body {
max-height: 60vh !important;
overflow: hidden;
display: flex;
flex-direction: column;
@media screen and (max-height: 575px) {
max-height: 50vh !important;
}
#login-buttons {
border-bottom: 1px solid var(--primary-low);
}
.login-form {
margin-bottom: 0;
padding-bottom: 0;
}
}
.create-account-form {
overflow-y: auto;
.login-welcome-header {
width: calc(100% - 2em);
position: sticky;
position: -webkit-sticky;
z-index: z("header");
top: 0;
background-color: var(--secondary);
}
}
}
.create-account {
.user-fields {
display: flex;
flex-direction: column;
}
.user-field {
display: flex;
flex-direction: column;
}
}
.password-reset,
.email-login {
margin-top: 30px;
.col-image {
padding-top: 12px;
}
.password-reset-img {
width: 50px;
height: 50px;
}
.col-form {
padding-left: 8px;
}
h2 {
margin-bottom: 12px;
}
.tip {
display: block;
margin: 6px 0;
}
}
.password-reset .tip {
max-width: 180px;
}
.discourse-touch .password-reset {
.instructions {
margin-bottom: 16px;
}
}
.email-login {
width: 90%;
}

View File

@ -1,5 +1,16 @@
// base styles for every modal popup used in Discourse
.d-modal {
&__container {
width: 100%;
max-width: 100%;
}
.modal-inner-container {
width: 100%;
}
}
//legacy
.modal-open #main {
overflow: hidden;
}

View File

@ -1618,14 +1618,14 @@ en:
name:
title: "Name"
instructions: "your full name (optional)"
instructions_required: "Your full name"
instructions: "Your full name (optional)."
instructions_required: "Your full name."
required: "Please enter a name"
too_short: "Your name is too short"
ok: "Your name looks good"
username:
title: "Username"
instructions: "unique, no spaces, short"
instructions: "Unique, no spaces, short."
short_instructions: "People can mention you as @%{username}"
available: "Your username is available"
not_available: "Not available. Try %{suggestion}?"
@ -1857,7 +1857,7 @@ en:
same_as_username: "Your password is the same as your username."
same_as_email: "Your password is the same as your email."
ok: "Your password looks good."
instructions: "at least %{count} characters"
instructions: "At least %{count} characters."
required: "Please enter a password"
confirm: "Confirm"
incorrect_password: "The entered password is incorrect."
@ -2156,6 +2156,7 @@ en:
action: "I forgot my password"
invite: "Enter your username or email address, and we'll send you a password reset email."
invite_no_username: "Enter your email address, and we'll send you a password reset email."
email-username: "Email or username"
reset: "Reset Password"
complete_username: "If an account matches the username <b>%{username}</b>, you should receive an email with instructions on how to reset your password shortly."
complete_email: "If an account matches <b>%{email}</b>, you should receive an email with instructions on how to reset your password shortly."

View File

@ -10,7 +10,6 @@
@import "chat-side-panel";
@import "chat-thread";
@import "chat-threads-list";
@import "chat-modal-thread-settings";
@import "chat-message-thread-indicator";
@import "chat-message-creator";
@import "chat-channel-row";

View File

@ -196,7 +196,7 @@ describe "Thread list in side panel | full page", type: :system do
thread_list_page.item_by_id(thread_1.id).click
thread_page.header.open_settings
find(".chat-modal-thread-settings__title-input").fill_in(with: new_title)
find(".modal-footer .btn-primary").click
find(".d-modal__footer .btn-primary").click
expect(thread_page.header).to have_title_content(new_title)
end
@ -208,7 +208,7 @@ describe "Thread list in side panel | full page", type: :system do
thread_list_page.item_by_id(thread_1.id).click
thread_page.header.open_settings
find(".chat-modal-thread-settings__title-input").fill_in(with: new_title)
find(".modal-footer .btn-primary").click
find(".d-modal__footer .btn-primary").click
expect(thread_page.header).to have_title_content(new_title)
end

View File

@ -16,7 +16,7 @@ module PageObjects
end
def grant
within(modal) { find(".modal-footer .btn").click }
within(modal) { find(".d-modal__footer .btn").click }
end
def has_success_flash_visible?

View File

@ -9,11 +9,11 @@ module PageObjects
BODY_SELECTOR = ""
def body
find(".modal-body#{BODY_SELECTOR}")
find(".d-modal__body#{BODY_SELECTOR}")
end
def footer
find(".modal-footer")
find(".d-modal__footer")
end
def close
@ -25,7 +25,7 @@ module PageObjects
end
def click_outside
find(".modal-outer-container").click(x: 0, y: 0)
find(".d-modal__wrapper").click(x: 0, y: 0)
end
def click_primary_button

View File

@ -17,7 +17,7 @@ module PageObjects
end
def confirm_new_owner
within(modal) { find(".modal-footer .btn").click }
within(modal) { find(".d-modal__footer .btn").click }
end
def users_dropdown

View File

@ -8,7 +8,7 @@ module PageObjects
end
def confirm
find(".modal-footer .btn-primary").click
find(".d-modal__footer .btn-primary").click
end
end
end

View File

@ -16,7 +16,7 @@ module PageObjects
def take_action(action)
select_kit =
PageObjects::Components::SelectKit.new(".modal-footer .reviewable-action-dropdown")
PageObjects::Components::SelectKit.new(".d-modal__footer .reviewable-action-dropdown")
select_kit.expand
select_kit.select_row_by_value(action)
end

View File

@ -18,7 +18,7 @@ module PageObjects
end
def delete_user
modal.find(".modal-footer .btn.btn-danger").click
modal.find(".d-modal__footer .btn.btn-danger").click
end
end
end

View File

@ -8,7 +8,7 @@ module PageObjects
end
def has_right_title?(title)
has_css?(".sidebar__edit-navigation-menu__modal .title", text: title)
has_css?(".sidebar__edit-navigation-menu__modal .d-modal__title-text", text: title)
end
def has_focus_on_filter_input?

View File

@ -26,7 +26,7 @@ describe "User preferences for Security", type: :system do
find(".security-key .new-security-key").click
expect(user_preferences_security_page).to have_css("input#security-key-name")
find(".modal-body input#security-key-name").fill_in(with: "First Key")
find(".d-modal__body input#security-key-name").fill_in(with: "First Key")
find(".add-security-key").click
expect(user_preferences_security_page).to have_css(".security-key .second-factor-item")
@ -38,7 +38,7 @@ describe "User preferences for Security", type: :system do
find("input#login-account-name").fill_in(with: user.username)
find("input#login-account-password").fill_in(with: password)
find(".modal-footer .btn-primary").click
find(".d-modal__footer .btn-primary").click
find("#security-key .btn-primary").click
expect(page).to have_css(".header-dropdown-toggle.current-user")