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:
parent
5ee2741a4c
commit
c85921a548
|
@ -0,0 +1,3 @@
|
|||
<div class="composer-fullscreen-prompt" {{did-insert this.registerAnimationListener}}>
|
||||
{{html-safe this.exitPrompt}}
|
||||
</div>
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -124,6 +124,7 @@ const Composer = RestModel.extend({
|
|||
noBump: false,
|
||||
draftSaving: false,
|
||||
draftForceSave: false,
|
||||
showFullScreenExitPrompt: false,
|
||||
|
||||
archetypes: reads("site.archetypes"),
|
||||
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue