From c85921a5484f8c8084b7fc6e096deb6171b66d2e Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Thu, 11 Aug 2022 14:38:56 +0800 Subject: [PATCH] FEATURE: Adds full screen composer submit button and prompt (#17839) Context: https://meta.discourse.org/t/still-display-the-reply-create-topic-button-when-using-full-screen-composition/123597/6?u=johani Right now, we don't show the submit buttons when you enter the full-screen composer. The reasons for that are described in the context link above. This PR adds the improvements highlighted here: https://meta.discourse.org/t/still-display-the-reply-create-topic-button-when-using-full-screen-composition/123597/12?u=johani Here's a list of the changes this PR introduces: 1. When you enter full-screen mode, we will now add a prompt that matches the browser fullscreen F11 function. It looks like so The prompt fades away after a couple of seconds. 2. This PR adds the submit buttons to the full-screen composer mode. The submit buttons should work like normal if the post has no errors. If the post has errors (title too short, body too short, required categories/tags), then the button will make the composer exit the full-screen mode so that users will see the errors and fix them. The error logic is based on what we currently have; this PR doesn't add any new validation. Here's a video of what that looks like: https://meta.discourse.org/t/-/127948/14?u=johani --- .../components/composer-fullscreen-prompt.hbs | 3 ++ .../components/composer-fullscreen-prompt.js | 24 ++++++++++ .../discourse/app/controllers/composer.js | 47 ++++++++++++++----- .../discourse/app/models/composer.js | 1 + .../discourse/app/templates/composer.hbs | 30 ++++++------ .../tests/acceptance/composer-test.js | 34 ++++++++++++++ app/assets/stylesheets/desktop/compose.scss | 21 +++++++++ config/locales/client.en.yml | 3 +- 8 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs create mode 100644 app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js diff --git a/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs b/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs new file mode 100644 index 00000000000..c9fc1d04e6f --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs @@ -0,0 +1,3 @@ +
+ {{html-safe this.exitPrompt}} +
diff --git a/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js b/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js new file mode 100644 index 00000000000..5504224eb4e --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js @@ -0,0 +1,24 @@ +import { action } from "@ember/object"; +import GlimmerComponent from "@glimmer/component"; +import I18n from "I18n"; + +export default class ComposerFullscreenPrompt extends GlimmerComponent { + @action + registerAnimationListener(element) { + this.#addAnimationEventListener(element); + } + + #addAnimationEventListener(element) { + element.addEventListener( + "animationend", + () => { + this.args.removeFullScreenExitPrompt(); + }, + { once: true } + ); + } + + get exitPrompt() { + return I18n.t("composer.exit_fullscreen_prompt"); + } +} diff --git a/app/assets/javascripts/discourse/app/controllers/composer.js b/app/assets/javascripts/discourse/app/controllers/composer.js index f315a8218d4..a3af7e6307b 100644 --- a/app/assets/javascripts/discourse/app/controllers/composer.js +++ b/app/assets/javascripts/discourse/app/controllers/composer.js @@ -502,6 +502,11 @@ export default Controller.extend({ return false; }, + @action + removeFullScreenExitPrompt() { + this.set("model.showFullScreenExitPrompt", false); + }, + actions: { togglePreview() { this.toggleProperty("showPreview"); @@ -655,16 +660,12 @@ export default Controller.extend({ toggle() { this.closeAutocomplete(); - if ( - isEmpty(this.get("model.reply")) && - isEmpty(this.get("model.title")) - ) { + const composer = this.model; + + if (isEmpty(composer?.reply) && isEmpty(composer?.title)) { this.close(); } else { - if ( - this.get("model.composeState") === Composer.OPEN || - this.get("model.composeState") === Composer.FULLSCREEN - ) { + if (composer?.viewOpenOrFullscreen) { this.shrink(); } else { this.cancelComposer(); @@ -747,9 +748,16 @@ export default Controller.extend({ return; } - if (this.get("model.viewOpen") || this.get("model.viewFullscreen")) { + const composer = this.model; + + if (composer?.viewOpen) { this.shrink(); } + + if (composer?.viewFullscreen) { + this.toggleFullscreen(); + this.focusComposer(); + } }, groupsMentioned(groups) { @@ -839,7 +847,11 @@ export default Controller.extend({ const composer = this.model; - if (composer.cantSubmitPost) { + if (composer?.cantSubmitPost) { + if (composer?.viewFullscreen) { + this.toggleFullscreen(); + } + this.set("lastValidatedAt", Date.now()); return; } @@ -1481,13 +1493,22 @@ export default Controller.extend({ toggleFullscreen() { this._saveDraft(); - if (this.get("model.composeState") === Composer.FULLSCREEN) { - this.set("model.composeState", Composer.OPEN); + + const composer = this.model; + + if (composer?.viewFullscreen) { + composer?.set("composeState", Composer.OPEN); } else { - this.set("model.composeState", Composer.FULLSCREEN); + composer?.set("composeState", Composer.FULLSCREEN); + composer?.set("showFullScreenExitPrompt", true); } }, + @discourseComputed("model.viewFullscreen", "model.showFullScreenExitPrompt") + showFullScreenPrompt(isFullscreen, showExitPrompt) { + return isFullscreen && showExitPrompt && !this.capabilities.touch; + }, + close() { // the 'fullscreen-composer' class is added to remove scrollbars from the // document while in fullscreen mode. If the composer is closed for any reason diff --git a/app/assets/javascripts/discourse/app/models/composer.js b/app/assets/javascripts/discourse/app/models/composer.js index 7149ec27129..9b4854a638c 100644 --- a/app/assets/javascripts/discourse/app/models/composer.js +++ b/app/assets/javascripts/discourse/app/models/composer.js @@ -124,6 +124,7 @@ const Composer = RestModel.extend({ noBump: false, draftSaving: false, draftForceSave: false, + showFullScreenExitPrompt: false, archetypes: reads("site.archetypes"), diff --git a/app/assets/javascripts/discourse/app/templates/composer.hbs b/app/assets/javascripts/discourse/app/templates/composer.hbs index e0c1f52ed1c..abfa412e0af 100644 --- a/app/assets/javascripts/discourse/app/templates/composer.hbs +++ b/app/assets/javascripts/discourse/app/templates/composer.hbs @@ -4,6 +4,10 @@ {{#if this.visible}} + {{#if this.showFullScreenPrompt}} + + {{/if}} + {{#if this.model.viewOpenOrFullscreen}}
@@ -91,21 +95,19 @@
- {{#unless this.model.viewFullscreen}} - + - {{#if this.site.mobileView}} - - {{#if this.canEdit}} - {{d-icon "times"}} - {{else}} - {{d-icon "far-trash-alt"}} - {{/if}} - - {{else}} - {{i18n "close"}} - {{/if}} - {{/unless}} + {{#if this.site.mobileView}} + + {{#if this.canEdit}} + {{d-icon "times"}} + {{else}} + {{d-icon "far-trash-alt"}} + {{/if}} + + {{else}} + {{i18n "close"}} + {{/if}} {{#if this.site.mobileView}} {{#if this.whisperOrUnlistTopic}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js index 5c60c6086b0..705caa4a8dc 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js @@ -509,6 +509,12 @@ acceptance("Composer", function (needs) { "it expands composer to full screen" ); + assert.strictEqual( + count(".composer-fullscreen-prompt"), + 1, + "the exit fullscreen prompt is visible" + ); + await click(".toggle-fullscreen"); assert.strictEqual( @@ -535,6 +541,34 @@ acceptance("Composer", function (needs) { ); }); + test("Composer fullscreen submit button", async function (assert) { + await visit("/t/this-is-a-test-topic/9"); + await click(".topic-post:nth-of-type(1) button.reply"); + + assert.strictEqual( + count("#reply-control.open"), + 1, + "it starts in open state by default" + ); + + await click(".toggle-fullscreen"); + + assert.strictEqual( + count("#reply-control button.create"), + 1, + "it shows composer submit button in fullscreen" + ); + + await fillIn(".d-editor-input", "too short"); + await click("#reply-control button.create"); + + assert.strictEqual( + count("#reply-control.open"), + 1, + "it goes back to open state if there's errors" + ); + }); + test("Composer can toggle between reply and createTopic", async function (assert) { await visit("/t/this-is-a-test-topic/9"); await click(".topic-post:nth-of-type(1) button.reply"); diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index 31b430a09fe..8b45b63e566 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -288,3 +288,24 @@ a.toggle-preview { } } } + +.composer-fullscreen-prompt { + animation: fadeIn 1s ease-in-out; + animation-delay: 1.5s; + animation-direction: reverse; + animation-fill-mode: forwards; + position: fixed; + left: 50%; + top: 10%; + transform: translate(-50%, 0); + .rtl & { + // R2 is not smart enough to support this swap + transform: translate(50%, 0); + } + z-index: z("header") + 1; + background: var(--primary-very-high); + color: var(--secondary); + padding: 0.5em 0.75em; + pointer-events: none; + border-radius: 2px; +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 3b776812efb..a4b5a3d4965 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2259,6 +2259,7 @@ en: abandon: "close composer and discard draft" enter_fullscreen: "enter fullscreen composer" exit_fullscreen: "exit fullscreen composer" + exit_fullscreen_prompt: "Press ESC to exit full screen" show_toolbar: "show composer toolbar" hide_toolbar: "hide composer toolbar" modal_ok: "OK" @@ -2307,7 +2308,7 @@ en: image_alt_text: aria_label: Alt text for image - delete_image_button: Delete Image + delete_image_button: Delete Image notifications: tooltip: