FEATURE: fullscreen composer mode on desktop
Adds keyboard shortcut and icon that allows expanding composer to full screen.
This commit is contained in:
parent
57b52cd1de
commit
2acb885c72
|
@ -13,7 +13,7 @@ export default Ember.Component.extend({
|
||||||
_yourselfConfirm: null,
|
_yourselfConfirm: null,
|
||||||
similarTopics: null,
|
similarTopics: null,
|
||||||
|
|
||||||
hidden: Ember.computed.not("composer.viewOpen"),
|
hidden: Ember.computed.not("composer.viewOpenOrFullscreen"),
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
this._super();
|
this._super();
|
||||||
|
|
|
@ -4,18 +4,28 @@ export default Ember.Component.extend({
|
||||||
tagName: "",
|
tagName: "",
|
||||||
|
|
||||||
@computed("composeState")
|
@computed("composeState")
|
||||||
title(composeState) {
|
toggleTitle(composeState) {
|
||||||
if (composeState === "draft" || composeState === "saving") {
|
return composeState === "draft" || composeState === "saving"
|
||||||
return "composer.abandon";
|
? "composer.abandon"
|
||||||
}
|
: "composer.collapse";
|
||||||
return "composer.collapse";
|
},
|
||||||
|
|
||||||
|
@computed("composeState")
|
||||||
|
fullscreenTitle(composeState) {
|
||||||
|
return composeState === "fullscreen"
|
||||||
|
? "composer.exit_fullscreen"
|
||||||
|
: "composer.enter_fullscreen";
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("composeState")
|
@computed("composeState")
|
||||||
toggleIcon(composeState) {
|
toggleIcon(composeState) {
|
||||||
if (composeState === "draft" || composeState === "saving") {
|
return composeState === "draft" || composeState === "saving"
|
||||||
return "times";
|
? "times"
|
||||||
}
|
: "chevron-down";
|
||||||
return "chevron-down";
|
},
|
||||||
|
|
||||||
|
@computed("composeState")
|
||||||
|
fullscreenIcon(composeState) {
|
||||||
|
return composeState === "fullscreen" ? "compress" : "expand";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -231,7 +231,7 @@ export default Ember.Controller.extend({
|
||||||
|
|
||||||
@computed("model.composeState", "model.creatingTopic")
|
@computed("model.composeState", "model.creatingTopic")
|
||||||
popupMenuOptions(composeState) {
|
popupMenuOptions(composeState) {
|
||||||
if (composeState === "open") {
|
if (composeState === "open" || composeState === "fullscreen") {
|
||||||
let options = [];
|
let options = [];
|
||||||
|
|
||||||
options.push(
|
options.push(
|
||||||
|
@ -386,7 +386,10 @@ export default Ember.Controller.extend({
|
||||||
) {
|
) {
|
||||||
this.close();
|
this.close();
|
||||||
} else {
|
} else {
|
||||||
if (this.get("model.composeState") === Composer.OPEN) {
|
if (
|
||||||
|
this.get("model.composeState") === Composer.OPEN ||
|
||||||
|
this.get("model.composeState") === Composer.FULLSCREEN
|
||||||
|
) {
|
||||||
this.shrink();
|
this.shrink();
|
||||||
} else {
|
} else {
|
||||||
this.cancelComposer();
|
this.cancelComposer();
|
||||||
|
@ -396,6 +399,11 @@ export default Ember.Controller.extend({
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fullscreenComposer() {
|
||||||
|
this.toggleFullscreen();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
// Import a quote from the post
|
// Import a quote from the post
|
||||||
importQuote(toolbarEvent) {
|
importQuote(toolbarEvent) {
|
||||||
const postStream = this.get("topic.postStream");
|
const postStream = this.get("topic.postStream");
|
||||||
|
@ -457,7 +465,7 @@ export default Ember.Controller.extend({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.get("model.viewOpen")) {
|
if (this.get("model.viewOpen") || this.get("model.viewFullscreen")) {
|
||||||
this.shrink();
|
this.shrink();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -881,6 +889,10 @@ export default Ember.Controller.extend({
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
// in case the composer is
|
||||||
|
// cancelled while in fullscreen
|
||||||
|
$("html").removeClass("fullscreen-composer");
|
||||||
|
|
||||||
// it is possible there is some sort of crazy draft with no body ... just give up on it
|
// it is possible there is some sort of crazy draft with no body ... just give up on it
|
||||||
this.destroyDraft();
|
this.destroyDraft();
|
||||||
this.get("model").clearState();
|
this.get("model").clearState();
|
||||||
|
@ -947,6 +959,15 @@ export default Ember.Controller.extend({
|
||||||
this.set("model.composeState", Composer.DRAFT);
|
this.set("model.composeState", Composer.DRAFT);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleFullscreen() {
|
||||||
|
this._saveDraft();
|
||||||
|
if (this.get("model.composeState") === Composer.FULLSCREEN) {
|
||||||
|
this.set("model.composeState", Composer.OPEN);
|
||||||
|
} else {
|
||||||
|
this.set("model.composeState", Composer.FULLSCREEN);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.setProperties({ model: null, lastValidatedAt: null });
|
this.setProperties({ model: null, lastValidatedAt: null });
|
||||||
},
|
},
|
||||||
|
|
|
@ -65,6 +65,7 @@ const bindings = {
|
||||||
"shift+s": { click: "#topic-footer-buttons button.share", anonymous: true }, // share topic
|
"shift+s": { click: "#topic-footer-buttons button.share", anonymous: true }, // share topic
|
||||||
"shift+u": { handler: "goToUnreadPost" },
|
"shift+u": { handler: "goToUnreadPost" },
|
||||||
"shift+z shift+z": { handler: "logout" },
|
"shift+z shift+z": { handler: "logout" },
|
||||||
|
"shift+f11": { handler: "fullscreenComposer" },
|
||||||
t: { postAction: "replyAsNewTopic" },
|
t: { postAction: "replyAsNewTopic" },
|
||||||
u: { handler: "goBack", anonymous: true },
|
u: { handler: "goBack", anonymous: true },
|
||||||
"x r": {
|
"x r": {
|
||||||
|
@ -212,6 +213,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fullscreenComposer() {
|
||||||
|
const composer = this.container.lookup("controller:composer");
|
||||||
|
if (composer.get("model")) {
|
||||||
|
composer.toggleFullscreen();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
pinUnpinTopic() {
|
pinUnpinTopic() {
|
||||||
this.container.lookup("controller:topic").togglePinnedState();
|
this.container.lookup("controller:topic").togglePinnedState();
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,6 +26,7 @@ const CLOSED = "closed",
|
||||||
SAVING = "saving",
|
SAVING = "saving",
|
||||||
OPEN = "open",
|
OPEN = "open",
|
||||||
DRAFT = "draft",
|
DRAFT = "draft",
|
||||||
|
FULLSCREEN = "fullscreen",
|
||||||
// When creating, these fields are moved into the post model from the composer model
|
// When creating, these fields are moved into the post model from the composer model
|
||||||
_create_serializer = {
|
_create_serializer = {
|
||||||
raw: "reply",
|
raw: "reply",
|
||||||
|
@ -144,15 +145,24 @@ const Composer = RestModel.extend({
|
||||||
|
|
||||||
viewOpen: Em.computed.equal("composeState", OPEN),
|
viewOpen: Em.computed.equal("composeState", OPEN),
|
||||||
viewDraft: Em.computed.equal("composeState", DRAFT),
|
viewDraft: Em.computed.equal("composeState", DRAFT),
|
||||||
|
viewFullscreen: Em.computed.equal("composeState", FULLSCREEN),
|
||||||
|
viewOpenOrFullscreen: Em.computed.or("viewOpen", "viewFullscreen"),
|
||||||
|
|
||||||
composeStateChanged: function() {
|
composeStateChanged: function() {
|
||||||
var oldOpen = this.get("composerOpened");
|
let oldOpen = this.get("composerOpened"),
|
||||||
|
elem = $("html");
|
||||||
|
|
||||||
|
if (this.get("composeState") === FULLSCREEN) {
|
||||||
|
elem.addClass("fullscreen-composer");
|
||||||
|
} else {
|
||||||
|
elem.removeClass("fullscreen-composer");
|
||||||
|
}
|
||||||
|
|
||||||
if (this.get("composeState") === OPEN) {
|
if (this.get("composeState") === OPEN) {
|
||||||
this.set("composerOpened", oldOpen || new Date());
|
this.set("composerOpened", oldOpen || new Date());
|
||||||
} else {
|
} else {
|
||||||
if (oldOpen) {
|
if (oldOpen) {
|
||||||
var oldTotal = this.get("composerTotalOpened") || 0;
|
let oldTotal = this.get("composerTotalOpened") || 0;
|
||||||
this.set("composerTotalOpened", oldTotal + (new Date() - oldOpen));
|
this.set("composerTotalOpened", oldTotal + (new Date() - oldOpen));
|
||||||
}
|
}
|
||||||
this.set("composerOpened", null);
|
this.set("composerOpened", null);
|
||||||
|
@ -160,9 +170,8 @@ const Composer = RestModel.extend({
|
||||||
}.observes("composeState"),
|
}.observes("composeState"),
|
||||||
|
|
||||||
composerTime: function() {
|
composerTime: function() {
|
||||||
var total = this.get("composerTotalOpened") || 0;
|
let total = this.get("composerTotalOpened") || 0,
|
||||||
|
oldOpen = this.get("composerOpened");
|
||||||
var oldOpen = this.get("composerOpened");
|
|
||||||
if (oldOpen) {
|
if (oldOpen) {
|
||||||
total += new Date() - oldOpen;
|
total += new Date() - oldOpen;
|
||||||
}
|
}
|
||||||
|
@ -183,7 +192,7 @@ const Composer = RestModel.extend({
|
||||||
// view detected user is typing
|
// view detected user is typing
|
||||||
typing: _.throttle(
|
typing: _.throttle(
|
||||||
function() {
|
function() {
|
||||||
var typingTime = this.get("typingTime") || 0;
|
let typingTime = this.get("typingTime") || 0;
|
||||||
this.set("typingTime", typingTime + 100);
|
this.set("typingTime", typingTime + 100);
|
||||||
},
|
},
|
||||||
100,
|
100,
|
||||||
|
@ -1041,6 +1050,7 @@ Composer.reopenClass({
|
||||||
SAVING,
|
SAVING,
|
||||||
OPEN,
|
OPEN,
|
||||||
DRAFT,
|
DRAFT,
|
||||||
|
FULLSCREEN,
|
||||||
|
|
||||||
// The actions the composer can take
|
// The actions the composer can take
|
||||||
CREATE_TOPIC,
|
CREATE_TOPIC,
|
||||||
|
|
|
@ -10,5 +10,13 @@
|
||||||
class="toggler"
|
class="toggler"
|
||||||
icon=toggleIcon
|
icon=toggleIcon
|
||||||
action=toggleComposer
|
action=toggleComposer
|
||||||
title=title}}
|
title=toggleTitle}}
|
||||||
|
|
||||||
|
{{#unless site.mobileView}}
|
||||||
|
{{flat-button
|
||||||
|
class="toggle-fullscreen"
|
||||||
|
icon=fullscreenIcon
|
||||||
|
action=toggleFullscreen
|
||||||
|
title=fullscreenTitle}}
|
||||||
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,11 +9,12 @@
|
||||||
{{composer-messages composer=model
|
{{composer-messages composer=model
|
||||||
messageCount=messageCount
|
messageCount=messageCount
|
||||||
addLinkLookup="addLinkLookup"}}
|
addLinkLookup="addLinkLookup"}}
|
||||||
{{#if model.viewOpen}}
|
{{#if model.viewOpenOrFullscreen}}
|
||||||
<div class="reply-area {{if canEditTags 'with-tags'}}">
|
<div class="reply-area {{if canEditTags 'with-tags'}}">
|
||||||
<div class='composer-fields'>
|
<div class='composer-fields'>
|
||||||
{{plugin-outlet name="composer-open" args=(hash model=model)}}
|
{{plugin-outlet name="composer-open" args=(hash model=model)}}
|
||||||
<div class='reply-to'>
|
<div class='reply-to'>
|
||||||
|
{{#unless model.viewFullscreen}}
|
||||||
<div class="reply-details">
|
<div class="reply-details">
|
||||||
{{composer-action-title model=model canWhisper=canWhisper tabindex=8}}
|
{{composer-action-title model=model canWhisper=canWhisper tabindex=8}}
|
||||||
|
|
||||||
|
@ -32,11 +33,13 @@
|
||||||
{{/link-to-input}}
|
{{/link-to-input}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
{{composer-toggles composeState=model.composeState
|
{{composer-toggles composeState=model.composeState
|
||||||
toggleComposer=(action "toggle")
|
toggleComposer=(action "toggle")
|
||||||
toggleToolbar=(action "toggleToolbar")}}
|
toggleToolbar=(action "toggleToolbar")
|
||||||
|
toggleFullscreen=(action "fullscreenComposer")}}
|
||||||
</div>
|
</div>
|
||||||
|
{{#unless model.viewFullscreen}}
|
||||||
{{#if model.canEditTitle}}
|
{{#if model.canEditTitle}}
|
||||||
{{#if model.creatingPrivateMessage}}
|
{{#if model.creatingPrivateMessage}}
|
||||||
<div class='user-selector'>
|
<div class='user-selector'>
|
||||||
|
@ -76,6 +79,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{plugin-outlet name="composer-fields" args=(hash model=model)}}
|
{{plugin-outlet name="composer-fields" args=(hash model=model)}}
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -104,10 +108,12 @@
|
||||||
{{plugin-outlet name="composer-fields-below" args=(hash model=model)}}
|
{{plugin-outlet name="composer-fields-below" args=(hash model=model)}}
|
||||||
|
|
||||||
<div class='save-or-cancel'>
|
<div class='save-or-cancel'>
|
||||||
|
{{#unless model.viewFullscreen}}
|
||||||
{{composer-save-button action=(action "save")
|
{{composer-save-button action=(action "save")
|
||||||
icon=model.saveIcon
|
icon=model.saveIcon
|
||||||
label=model.saveLabel
|
label=model.saveLabel
|
||||||
disableSubmit=disableSubmit}}
|
disableSubmit=disableSubmit}}
|
||||||
|
|
||||||
{{#if site.mobileView}}
|
{{#if site.mobileView}}
|
||||||
<a href {{action "cancel"}} class='cancel' tabindex="6" title="{{i18n 'cancel'}}">
|
<a href {{action "cancel"}} class='cancel' tabindex="6" title="{{i18n 'cancel'}}">
|
||||||
{{#if canEdit}}
|
{{#if canEdit}}
|
||||||
|
@ -119,6 +125,7 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
<a href {{action "cancel"}} class='cancel' tabindex="6" >{{i18n 'cancel'}}</a>
|
<a href {{action "cancel"}} class='cancel' tabindex="6" >{{i18n 'cancel'}}</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
|
||||||
{{#if site.mobileView}}
|
{{#if site.mobileView}}
|
||||||
|
@ -183,6 +190,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{composer-toggles composeState=model.composeState
|
{{composer-toggles composeState=model.composeState
|
||||||
|
toggleFullscreen=(action "fullscreenComposer")
|
||||||
toggleComposer=(action "toggle")
|
toggleComposer=(action "toggle")
|
||||||
toggleToolbar=(action "toggleToolbar")}}
|
toggleToolbar=(action "toggleToolbar")}}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
<h4>{{i18n 'keyboard_shortcuts_help.composing.title'}}</h4>
|
<h4>{{i18n 'keyboard_shortcuts_help.composing.title'}}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>{{{i18n 'keyboard_shortcuts_help.composing.return'}}}</li>
|
<li>{{{i18n 'keyboard_shortcuts_help.composing.return'}}}</li>
|
||||||
|
<li>{{{i18n 'keyboard_shortcuts_help.composing.fullscreen'}}}</li>
|
||||||
<li>{{{i18n 'keyboard_shortcuts_help.application.create'}}}</li>
|
<li>{{{i18n 'keyboard_shortcuts_help.application.create'}}}</li>
|
||||||
<li>{{{i18n 'keyboard_shortcuts_help.actions.reply_as_new_topic'}}}</li>
|
<li>{{{i18n 'keyboard_shortcuts_help.actions.reply_as_new_topic'}}}</li>
|
||||||
<li>{{{i18n 'keyboard_shortcuts_help.actions.reply_topic'}}}</li>
|
<li>{{{i18n 'keyboard_shortcuts_help.actions.reply_topic'}}}</li>
|
||||||
|
|
|
@ -147,7 +147,7 @@
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: -5px;
|
margin-right: -5px;
|
||||||
button {
|
button {
|
||||||
padding: 0 8px;
|
padding: 0 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,3 +203,53 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fullscreen composer styles
|
||||||
|
.fullscreen-composer {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.profiler-results {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#reply-control {
|
||||||
|
&.fullscreen {
|
||||||
|
// important needed because of inline styles when height is changed manually with grippie
|
||||||
|
height: 100vh !important;
|
||||||
|
z-index: z("header") + 1;
|
||||||
|
.d-editor-preview-wrapper {
|
||||||
|
margin-top: 1%;
|
||||||
|
}
|
||||||
|
.reply-to {
|
||||||
|
border-bottom: 1px solid $primary-low;
|
||||||
|
padding-bottom: 3px;
|
||||||
|
margin: 0;
|
||||||
|
.composer-controls {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.d-editor-textarea-wrapper {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
&.show-preview .d-editor-textarea-wrapper {
|
||||||
|
border-right: 1px solid $primary-low;
|
||||||
|
}
|
||||||
|
#draft-status,
|
||||||
|
#file-uploading {
|
||||||
|
margin-left: 0;
|
||||||
|
text-align: initial;
|
||||||
|
}
|
||||||
|
.composer-popup {
|
||||||
|
top: 30px;
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
background: $secondary;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: -1;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1427,6 +1427,8 @@ en:
|
||||||
help: "Markdown Editing Help"
|
help: "Markdown Editing Help"
|
||||||
collapse: "minimize the composer panel"
|
collapse: "minimize the composer panel"
|
||||||
abandon: "close composer and discard draft"
|
abandon: "close composer and discard draft"
|
||||||
|
enter_fullscreen: "enter fullscreen composer"
|
||||||
|
exit_fullscreen: "exit fullscreen composer"
|
||||||
modal_ok: "OK"
|
modal_ok: "OK"
|
||||||
modal_cancel: "Cancel"
|
modal_cancel: "Cancel"
|
||||||
cant_send_pm: "Sorry, you can't send a message to %{username}."
|
cant_send_pm: "Sorry, you can't send a message to %{username}."
|
||||||
|
@ -2613,6 +2615,7 @@ en:
|
||||||
composing:
|
composing:
|
||||||
title: 'Composing'
|
title: 'Composing'
|
||||||
return: '<b>shift</b>+<b>c</b> Return to composer'
|
return: '<b>shift</b>+<b>c</b> Return to composer'
|
||||||
|
fullscreen: '<b>shift</b>+<b>F11</b> Fullscreen composer'
|
||||||
actions:
|
actions:
|
||||||
title: 'Actions'
|
title: 'Actions'
|
||||||
bookmark_topic: '<b>f</b> Toggle bookmark topic'
|
bookmark_topic: '<b>f</b> Toggle bookmark topic'
|
||||||
|
|
Loading…
Reference in New Issue