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 <kbd>F11</kbd> function. It looks like so

    <img width="500" src="https://user-images.githubusercontent.com/33972521/183529813-71a20167-a661-466c-b9ef-c4d34e231000.png">
    
    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
This commit is contained in:
Joe 2022-08-11 14:38:56 +08:00 committed by GitHub
parent 5ee2741a4c
commit c85921a548
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 135 additions and 28 deletions

View File

@ -0,0 +1,3 @@
<div class="composer-fullscreen-prompt" {{did-insert this.registerAnimationListener}}>
{{html-safe this.exitPrompt}}
</div>

View File

@ -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");
}
}

View File

@ -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

View File

@ -124,6 +124,7 @@ const Composer = RestModel.extend({
noBump: false,
draftSaving: false,
draftForceSave: false,
showFullScreenExitPrompt: false,
archetypes: reads("site.archetypes"),

View File

@ -4,6 +4,10 @@
{{#if this.visible}}
<ComposerMessages @composer={{this.model}} @messageCount={{this.messageCount}} @addLinkLookup={{action "addLinkLookup"}} />
{{#if this.showFullScreenPrompt}}
<ComposerFullscreenPrompt @removeFullScreenExitPrompt={{action "removeFullScreenExitPrompt"}}/>
{{/if}}
{{#if this.model.viewOpenOrFullscreen}}
<div role="form" aria-label={{I18n this.saveLabel}} class="reply-area {{if this.canEditTags "with-tags" "without-tags"}}">
<PluginOutlet @name="composer-open" @tagName="span" @connectorTagName="div" @args={{hash model=this.model}} />
@ -91,7 +95,6 @@
<PluginOutlet @name="composer-fields-below" @tagName="span" @connectorTagName="div" @args={{hash model=this.model}} />
<div class="save-or-cancel">
{{#unless this.model.viewFullscreen}}
<ComposerSaveButton @action={{action "save"}} @icon={{this.saveIcon}} @label={{this.saveLabel}} @forwardEvent={{true}} @disableSubmit={{this.disableSubmit}} />
{{#if this.site.mobileView}}
@ -105,7 +108,6 @@
{{else}}
<a href {{action "cancel"}} class="cancel" >{{i18n "close"}}</a>
{{/if}}
{{/unless}}
{{#if this.site.mobileView}}
{{#if this.whisperOrUnlistTopic}}

View File

@ -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");

View File

@ -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;
}

View File

@ -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 <kbd>ESC</kbd> to exit full screen"
show_toolbar: "show composer toolbar"
hide_toolbar: "hide composer toolbar"
modal_ok: "OK"