mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 19:43:38 +08:00
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;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
removeFullScreenExitPrompt() {
|
||||||
|
this.set("model.showFullScreenExitPrompt", false);
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
togglePreview() {
|
togglePreview() {
|
||||||
this.toggleProperty("showPreview");
|
this.toggleProperty("showPreview");
|
||||||
|
@ -655,16 +660,12 @@ export default Controller.extend({
|
||||||
toggle() {
|
toggle() {
|
||||||
this.closeAutocomplete();
|
this.closeAutocomplete();
|
||||||
|
|
||||||
if (
|
const composer = this.model;
|
||||||
isEmpty(this.get("model.reply")) &&
|
|
||||||
isEmpty(this.get("model.title"))
|
if (isEmpty(composer?.reply) && isEmpty(composer?.title)) {
|
||||||
) {
|
|
||||||
this.close();
|
this.close();
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (composer?.viewOpenOrFullscreen) {
|
||||||
this.get("model.composeState") === Composer.OPEN ||
|
|
||||||
this.get("model.composeState") === Composer.FULLSCREEN
|
|
||||||
) {
|
|
||||||
this.shrink();
|
this.shrink();
|
||||||
} else {
|
} else {
|
||||||
this.cancelComposer();
|
this.cancelComposer();
|
||||||
|
@ -747,9 +748,16 @@ export default Controller.extend({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.get("model.viewOpen") || this.get("model.viewFullscreen")) {
|
const composer = this.model;
|
||||||
|
|
||||||
|
if (composer?.viewOpen) {
|
||||||
this.shrink();
|
this.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (composer?.viewFullscreen) {
|
||||||
|
this.toggleFullscreen();
|
||||||
|
this.focusComposer();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
groupsMentioned(groups) {
|
groupsMentioned(groups) {
|
||||||
|
@ -839,7 +847,11 @@ export default Controller.extend({
|
||||||
|
|
||||||
const composer = this.model;
|
const composer = this.model;
|
||||||
|
|
||||||
if (composer.cantSubmitPost) {
|
if (composer?.cantSubmitPost) {
|
||||||
|
if (composer?.viewFullscreen) {
|
||||||
|
this.toggleFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
this.set("lastValidatedAt", Date.now());
|
this.set("lastValidatedAt", Date.now());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1481,13 +1493,22 @@ export default Controller.extend({
|
||||||
|
|
||||||
toggleFullscreen() {
|
toggleFullscreen() {
|
||||||
this._saveDraft();
|
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 {
|
} 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() {
|
close() {
|
||||||
// the 'fullscreen-composer' class is added to remove scrollbars from the
|
// the 'fullscreen-composer' class is added to remove scrollbars from the
|
||||||
// document while in fullscreen mode. If the composer is closed for any reason
|
// document while in fullscreen mode. If the composer is closed for any reason
|
||||||
|
|
|
@ -124,6 +124,7 @@ const Composer = RestModel.extend({
|
||||||
noBump: false,
|
noBump: false,
|
||||||
draftSaving: false,
|
draftSaving: false,
|
||||||
draftForceSave: false,
|
draftForceSave: false,
|
||||||
|
showFullScreenExitPrompt: false,
|
||||||
|
|
||||||
archetypes: reads("site.archetypes"),
|
archetypes: reads("site.archetypes"),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
{{#if this.visible}}
|
{{#if this.visible}}
|
||||||
<ComposerMessages @composer={{this.model}} @messageCount={{this.messageCount}} @addLinkLookup={{action "addLinkLookup"}} />
|
<ComposerMessages @composer={{this.model}} @messageCount={{this.messageCount}} @addLinkLookup={{action "addLinkLookup"}} />
|
||||||
|
|
||||||
|
{{#if this.showFullScreenPrompt}}
|
||||||
|
<ComposerFullscreenPrompt @removeFullScreenExitPrompt={{action "removeFullScreenExitPrompt"}}/>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.model.viewOpenOrFullscreen}}
|
{{#if this.model.viewOpenOrFullscreen}}
|
||||||
<div role="form" aria-label={{I18n this.saveLabel}} class="reply-area {{if this.canEditTags "with-tags" "without-tags"}}">
|
<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}} />
|
<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}} />
|
<PluginOutlet @name="composer-fields-below" @tagName="span" @connectorTagName="div" @args={{hash model=this.model}} />
|
||||||
|
|
||||||
<div class="save-or-cancel">
|
<div class="save-or-cancel">
|
||||||
{{#unless this.model.viewFullscreen}}
|
|
||||||
<ComposerSaveButton @action={{action "save"}} @icon={{this.saveIcon}} @label={{this.saveLabel}} @forwardEvent={{true}} @disableSubmit={{this.disableSubmit}} />
|
<ComposerSaveButton @action={{action "save"}} @icon={{this.saveIcon}} @label={{this.saveLabel}} @forwardEvent={{true}} @disableSubmit={{this.disableSubmit}} />
|
||||||
|
|
||||||
{{#if this.site.mobileView}}
|
{{#if this.site.mobileView}}
|
||||||
|
@ -105,7 +108,6 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
<a href {{action "cancel"}} class="cancel" >{{i18n "close"}}</a>
|
<a href {{action "cancel"}} class="cancel" >{{i18n "close"}}</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
{{#if this.site.mobileView}}
|
{{#if this.site.mobileView}}
|
||||||
{{#if this.whisperOrUnlistTopic}}
|
{{#if this.whisperOrUnlistTopic}}
|
||||||
|
|
|
@ -509,6 +509,12 @@ acceptance("Composer", function (needs) {
|
||||||
"it expands composer to full screen"
|
"it expands composer to full screen"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
count(".composer-fullscreen-prompt"),
|
||||||
|
1,
|
||||||
|
"the exit fullscreen prompt is visible"
|
||||||
|
);
|
||||||
|
|
||||||
await click(".toggle-fullscreen");
|
await click(".toggle-fullscreen");
|
||||||
|
|
||||||
assert.strictEqual(
|
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) {
|
test("Composer can toggle between reply and createTopic", async function (assert) {
|
||||||
await visit("/t/this-is-a-test-topic/9");
|
await visit("/t/this-is-a-test-topic/9");
|
||||||
await click(".topic-post:nth-of-type(1) button.reply");
|
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"
|
abandon: "close composer and discard draft"
|
||||||
enter_fullscreen: "enter fullscreen composer"
|
enter_fullscreen: "enter fullscreen composer"
|
||||||
exit_fullscreen: "exit fullscreen composer"
|
exit_fullscreen: "exit fullscreen composer"
|
||||||
|
exit_fullscreen_prompt: "Press <kbd>ESC</kbd> to exit full screen"
|
||||||
show_toolbar: "show composer toolbar"
|
show_toolbar: "show composer toolbar"
|
||||||
hide_toolbar: "hide composer toolbar"
|
hide_toolbar: "hide composer toolbar"
|
||||||
modal_ok: "OK"
|
modal_ok: "OK"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user