diff --git a/app/assets/javascripts/discourse/app/components/char-counter.hbs b/app/assets/javascripts/discourse/app/components/char-counter.hbs new file mode 100644 index 00000000000..3ab7e455dd7 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/char-counter.hbs @@ -0,0 +1,12 @@ +
+ {{yield}} + + {{@value.length}}/{{@max}} + + + {{if (gt @value.length @max) (i18n "char_counter.exceeded")}} + +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/tests/integration/components/char-counter-test.js b/app/assets/javascripts/discourse/tests/integration/components/char-counter-test.js new file mode 100644 index 00000000000..86520006133 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/char-counter-test.js @@ -0,0 +1,48 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import { fillIn, render } from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; + +module("Integration | Component | char-counter", function (hooks) { + setupRenderingTest(hooks); + + test("shows the number of characters", async function (assert) { + this.value = "Hello World"; + this.max = 12; + + await render( + hbs`` + ); + + assert.dom(this.element).includesText("11/12"); + }); + + test("updating value updates counter", async function (assert) { + this.max = 50; + + await render( + hbs`` + ); + + assert + .dom(this.element) + .includesText("/50", "initial value appears as expected"); + + await fillIn("textarea", "Hello World, this is a longer string"); + + assert + .dom(this.element) + .includesText("36/50", "updated value appears as expected"); + }); + + test("exceeding max length", async function (assert) { + this.max = 10; + this.value = "Hello World"; + + await render( + hbs`` + ); + + assert.dom(".char-counter.exceeded").exists("exceeded class is applied"); + }); +}); diff --git a/app/assets/stylesheets/common/components/_index.scss b/app/assets/stylesheets/common/components/_index.scss index b33546edc10..f9201fa36ce 100644 --- a/app/assets/stylesheets/common/components/_index.scss +++ b/app/assets/stylesheets/common/components/_index.scss @@ -4,6 +4,7 @@ @import "bookmark-modal"; @import "buttons"; @import "color-input"; +@import "char-counter"; @import "conditional-loading-section"; @import "convert-to-public-topic-modal"; @import "d-tooltip"; diff --git a/app/assets/stylesheets/common/components/char-counter.scss b/app/assets/stylesheets/common/components/char-counter.scss new file mode 100644 index 00000000000..bb95b30d146 --- /dev/null +++ b/app/assets/stylesheets/common/components/char-counter.scss @@ -0,0 +1,18 @@ +.char-counter { + &__ratio { + display: block; + text-align: right; + margin-top: 0.5rem; + } + + &.exceeded { + > textarea { + border-color: var(--danger); + outline-color: var(--danger); + } + + &__ratio { + color: var(--danger); + } + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index d855d9c87aa..b2c7ed75611 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -4453,6 +4453,9 @@ en: until: "Until:" + char_counter: + exceeded: "The maximum number of characters allowed has been exceeded." + # This section is exported to the javascript for i18n in the admin section admin_js: type_to_filter: "type to filter..." diff --git a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-edit-description.js b/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-edit-description.js index 85e834963ae..664a1993bad 100644 --- a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-edit-description.js +++ b/plugins/chat/assets/javascripts/discourse/controllers/chat-channel-edit-description.js @@ -1,28 +1,30 @@ import Controller from "@ember/controller"; import { action, computed } from "@ember/object"; import ModalFunctionality from "discourse/mixins/modal-functionality"; +import { tracked } from "@glimmer/tracking"; import { inject as service } from "@ember/service"; +const DESCRIPTION_MAX_LENGTH = 280; + export default class ChatChannelEditDescriptionController extends Controller.extend( ModalFunctionality ) { @service chatApi; - editedDescription = ""; + @tracked editedDescription = this.model.description || ""; @computed("model.description", "editedDescription") get isSaveDisabled() { return ( this.model.description === this.editedDescription || - this.editedDescription?.length > 280 + this.editedDescription?.length > DESCRIPTION_MAX_LENGTH ); } - onShow() { - this.set("editedDescription", this.model.description || ""); + get descriptionMaxLength() { + return DESCRIPTION_MAX_LENGTH; } onClose() { - this.set("editedDescription", ""); this.clearFlash(); } @@ -46,6 +48,6 @@ export default class ChatChannelEditDescriptionController extends Controller.ext @action onChangeChatChannelDescription(description) { this.clearFlash(); - this.set("editedDescription", description); + this.editedDescription = description; } } diff --git a/plugins/chat/assets/javascripts/discourse/templates/modal/chat-channel-edit-description.hbs b/plugins/chat/assets/javascripts/discourse/templates/modal/chat-channel-edit-description.hbs index b988acd671e..059e2d147bf 100644 --- a/plugins/chat/assets/javascripts/discourse/templates/modal/chat-channel-edit-description.hbs +++ b/plugins/chat/assets/javascripts/discourse/templates/modal/chat-channel-edit-description.hbs @@ -1,15 +1,24 @@ - {{i18n "chat.channel_edit_description_modal.description"}} + + +