FEATURE: fullscreen composer mode on desktop

Adds keyboard shortcut and icon that allows expanding composer to full screen.
This commit is contained in:
Joe 2018-10-15 10:59:49 +08:00 committed by Sam
parent 57b52cd1de
commit 2acb885c72
11 changed files with 209 additions and 90 deletions

View File

@ -13,7 +13,7 @@ export default Ember.Component.extend({
_yourselfConfirm: null,
similarTopics: null,
hidden: Ember.computed.not("composer.viewOpen"),
hidden: Ember.computed.not("composer.viewOpenOrFullscreen"),
didInsertElement() {
this._super();

View File

@ -4,18 +4,28 @@ export default Ember.Component.extend({
tagName: "",
@computed("composeState")
title(composeState) {
if (composeState === "draft" || composeState === "saving") {
return "composer.abandon";
}
return "composer.collapse";
toggleTitle(composeState) {
return composeState === "draft" || composeState === "saving"
? "composer.abandon"
: "composer.collapse";
},
@computed("composeState")
fullscreenTitle(composeState) {
return composeState === "fullscreen"
? "composer.exit_fullscreen"
: "composer.enter_fullscreen";
},
@computed("composeState")
toggleIcon(composeState) {
if (composeState === "draft" || composeState === "saving") {
return "times";
}
return "chevron-down";
return composeState === "draft" || composeState === "saving"
? "times"
: "chevron-down";
},
@computed("composeState")
fullscreenIcon(composeState) {
return composeState === "fullscreen" ? "compress" : "expand";
}
});

View File

@ -231,7 +231,7 @@ export default Ember.Controller.extend({
@computed("model.composeState", "model.creatingTopic")
popupMenuOptions(composeState) {
if (composeState === "open") {
if (composeState === "open" || composeState === "fullscreen") {
let options = [];
options.push(
@ -386,7 +386,10 @@ export default Ember.Controller.extend({
) {
this.close();
} else {
if (this.get("model.composeState") === Composer.OPEN) {
if (
this.get("model.composeState") === Composer.OPEN ||
this.get("model.composeState") === Composer.FULLSCREEN
) {
this.shrink();
} else {
this.cancelComposer();
@ -396,6 +399,11 @@ export default Ember.Controller.extend({
return false;
},
fullscreenComposer() {
this.toggleFullscreen();
return false;
},
// Import a quote from the post
importQuote(toolbarEvent) {
const postStream = this.get("topic.postStream");
@ -457,7 +465,7 @@ export default Ember.Controller.extend({
return;
}
if (this.get("model.viewOpen")) {
if (this.get("model.viewOpen") || this.get("model.viewFullscreen")) {
this.shrink();
}
},
@ -881,6 +889,10 @@ export default Ember.Controller.extend({
}
]);
} 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
this.destroyDraft();
this.get("model").clearState();
@ -947,6 +959,15 @@ export default Ember.Controller.extend({
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() {
this.setProperties({ model: null, lastValidatedAt: null });
},

View File

@ -65,6 +65,7 @@ const bindings = {
"shift+s": { click: "#topic-footer-buttons button.share", anonymous: true }, // share topic
"shift+u": { handler: "goToUnreadPost" },
"shift+z shift+z": { handler: "logout" },
"shift+f11": { handler: "fullscreenComposer" },
t: { postAction: "replyAsNewTopic" },
u: { handler: "goBack", anonymous: true },
"x r": {
@ -212,6 +213,13 @@ export default {
}
},
fullscreenComposer() {
const composer = this.container.lookup("controller:composer");
if (composer.get("model")) {
composer.toggleFullscreen();
}
},
pinUnpinTopic() {
this.container.lookup("controller:topic").togglePinnedState();
},

View File

@ -26,6 +26,7 @@ const CLOSED = "closed",
SAVING = "saving",
OPEN = "open",
DRAFT = "draft",
FULLSCREEN = "fullscreen",
// When creating, these fields are moved into the post model from the composer model
_create_serializer = {
raw: "reply",
@ -144,15 +145,24 @@ const Composer = RestModel.extend({
viewOpen: Em.computed.equal("composeState", OPEN),
viewDraft: Em.computed.equal("composeState", DRAFT),
viewFullscreen: Em.computed.equal("composeState", FULLSCREEN),
viewOpenOrFullscreen: Em.computed.or("viewOpen", "viewFullscreen"),
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) {
this.set("composerOpened", oldOpen || new Date());
} else {
if (oldOpen) {
var oldTotal = this.get("composerTotalOpened") || 0;
let oldTotal = this.get("composerTotalOpened") || 0;
this.set("composerTotalOpened", oldTotal + (new Date() - oldOpen));
}
this.set("composerOpened", null);
@ -160,9 +170,8 @@ const Composer = RestModel.extend({
}.observes("composeState"),
composerTime: function() {
var total = this.get("composerTotalOpened") || 0;
var oldOpen = this.get("composerOpened");
let total = this.get("composerTotalOpened") || 0,
oldOpen = this.get("composerOpened");
if (oldOpen) {
total += new Date() - oldOpen;
}
@ -183,7 +192,7 @@ const Composer = RestModel.extend({
// view detected user is typing
typing: _.throttle(
function() {
var typingTime = this.get("typingTime") || 0;
let typingTime = this.get("typingTime") || 0;
this.set("typingTime", typingTime + 100);
},
100,
@ -1041,6 +1050,7 @@ Composer.reopenClass({
SAVING,
OPEN,
DRAFT,
FULLSCREEN,
// The actions the composer can take
CREATE_TOPIC,

View File

@ -10,5 +10,13 @@
class="toggler"
icon=toggleIcon
action=toggleComposer
title=title}}
title=toggleTitle}}
{{#unless site.mobileView}}
{{flat-button
class="toggle-fullscreen"
icon=fullscreenIcon
action=toggleFullscreen
title=fullscreenTitle}}
{{/unless}}
</div>

View File

@ -9,73 +9,77 @@
{{composer-messages composer=model
messageCount=messageCount
addLinkLookup="addLinkLookup"}}
{{#if model.viewOpen}}
{{#if model.viewOpenOrFullscreen}}
<div class="reply-area {{if canEditTags 'with-tags'}}">
<div class='composer-fields'>
{{plugin-outlet name="composer-open" args=(hash model=model)}}
<div class='reply-to'>
<div class="reply-details">
{{composer-action-title model=model canWhisper=canWhisper tabindex=8}}
{{#unless model.viewFullscreen}}
<div class="reply-details">
{{composer-action-title model=model canWhisper=canWhisper tabindex=8}}
{{#unless site.mobileView}}
{{#if whisperOrUnlistTopicText}}
<span class='whisper'>({{whisperOrUnlistTopicText}})</span>
{{/if}}
{{#if model.noBump}}
<span class="no-bump">{{d-icon "anchor"}}</span>
{{/if}}
{{/unless}}
{{#unless site.mobileView}}
{{#if whisperOrUnlistTopicText}}
<span class='whisper'>({{whisperOrUnlistTopicText}})</span>
{{/if}}
{{#if model.noBump}}
<span class="no-bump">{{d-icon "anchor"}}</span>
{{/if}}
{{/unless}}
{{#if canEdit}}
{{#link-to-input onClick=(action "displayEditReason") showInput=showEditReason key="composer.show_edit_reason" class="display-edit-reason"}}
{{text-field value=editReason tabindex="7" id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}}
{{/link-to-input}}
{{/if}}
</div>
{{#if canEdit}}
{{#link-to-input onClick=(action "displayEditReason") showInput=showEditReason key="composer.show_edit_reason" class="display-edit-reason"}}
{{text-field value=editReason tabindex="7" id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}}
{{/link-to-input}}
{{/if}}
</div>
{{/unless}}
{{composer-toggles composeState=model.composeState
toggleComposer=(action "toggle")
toggleToolbar=(action "toggleToolbar")}}
toggleToolbar=(action "toggleToolbar")
toggleFullscreen=(action "fullscreenComposer")}}
</div>
{{#unless model.viewFullscreen}}
{{#if model.canEditTitle}}
{{#if model.creatingPrivateMessage}}
<div class='user-selector'>
{{composer-user-selector topicId=topicModel.id
usernames=model.targetUsernames
hasGroups=model.hasTargetGroups
focusTarget=focusTarget
class="users-input"}}
{{#if showWarning}}
<label class='add-warning'>
{{input type="checkbox" checked=model.isWarning tabindex="3"}}
{{i18n "composer.add_warning"}}
</label>
{{/if}}
</div>
{{/if}}
{{#if model.canEditTitle}}
{{#if model.creatingPrivateMessage}}
<div class='user-selector'>
{{composer-user-selector topicId=topicModel.id
usernames=model.targetUsernames
hasGroups=model.hasTargetGroups
focusTarget=focusTarget
class="users-input"}}
{{#if showWarning}}
<label class='add-warning'>
{{input type="checkbox" checked=model.isWarning tabindex="3"}}
{{i18n "composer.add_warning"}}
</label>
<div class="title-and-category {{if showPreview 'with-preview'}}">
{{composer-title composer=model lastValidatedAt=lastValidatedAt focusTarget=focusTarget}}
{{#if model.showCategoryChooser}}
<div class="category-input">
{{category-chooser
fullWidthOnMobile=true
value=model.categoryId
scopedCategoryId=scopedCategoryId
tabindex="3"}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{/if}}
{{#if canEditTags}}
{{mini-tag-chooser tags=model.tags tabindex="4" categoryId=model.categoryId minimum=model.minimumRequiredTags}}
{{popup-input-tip validation=tagValidation}}
{{/if}}
</div>
{{/if}}
<div class="title-and-category {{if showPreview 'with-preview'}}">
{{composer-title composer=model lastValidatedAt=lastValidatedAt focusTarget=focusTarget}}
{{#if model.showCategoryChooser}}
<div class="category-input">
{{category-chooser
fullWidthOnMobile=true
value=model.categoryId
scopedCategoryId=scopedCategoryId
tabindex="3"}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{/if}}
{{#if canEditTags}}
{{mini-tag-chooser tags=model.tags tabindex="4" categoryId=model.categoryId minimum=model.minimumRequiredTags}}
{{popup-input-tip validation=tagValidation}}
{{/if}}
</div>
{{/if}}
{{plugin-outlet name="composer-fields" args=(hash model=model)}}
{{plugin-outlet name="composer-fields" args=(hash model=model)}}
{{/unless}}
</div>
@ -104,21 +108,24 @@
{{plugin-outlet name="composer-fields-below" args=(hash model=model)}}
<div class='save-or-cancel'>
{{composer-save-button action=(action "save")
icon=model.saveIcon
label=model.saveLabel
disableSubmit=disableSubmit}}
{{#if site.mobileView}}
<a href {{action "cancel"}} class='cancel' tabindex="6" title="{{i18n 'cancel'}}">
{{#if canEdit}}
{{d-icon "times"}}
{{else}}
{{d-icon "trash-o"}}
{{/if}}
</a>
{{else}}
<a href {{action "cancel"}} class='cancel' tabindex="6" >{{i18n 'cancel'}}</a>
{{/if}}
{{#unless model.viewFullscreen}}
{{composer-save-button action=(action "save")
icon=model.saveIcon
label=model.saveLabel
disableSubmit=disableSubmit}}
{{#if site.mobileView}}
<a href {{action "cancel"}} class='cancel' tabindex="6" title="{{i18n 'cancel'}}">
{{#if canEdit}}
{{d-icon "times"}}
{{else}}
{{d-icon "trash-o"}}
{{/if}}
</a>
{{else}}
<a href {{action "cancel"}} class='cancel' tabindex="6" >{{i18n 'cancel'}}</a>
{{/if}}
{{/unless}}
{{#if site.mobileView}}
@ -165,7 +172,7 @@
</div>
</div>
{{else}}
{{else}}
<div class='saving-text'>
{{#if model.createdPost}}
{{i18n 'composer.saved'}} <a class='permalink' href="{{unbound createdPost.url}}" {{action "viewNewReply"}}>{{i18n 'composer.view_new_post'}}</a>
@ -183,6 +190,7 @@
</div>
{{composer-toggles composeState=model.composeState
toggleFullscreen=(action "fullscreenComposer")
toggleComposer=(action "toggle")
toggleToolbar=(action "toggleToolbar")}}

View File

@ -40,6 +40,7 @@
<h4>{{i18n 'keyboard_shortcuts_help.composing.title'}}</h4>
<ul>
<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.actions.reply_as_new_topic'}}}</li>
<li>{{{i18n 'keyboard_shortcuts_help.actions.reply_topic'}}}</li>

View File

@ -147,7 +147,7 @@
margin-left: auto;
margin-right: -5px;
button {
padding: 0 8px;
padding: 0 2px;
}
}
}

View File

@ -203,3 +203,53 @@
flex-grow: 1;
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;
}
}
}
}

View File

@ -1427,6 +1427,8 @@ en:
help: "Markdown Editing Help"
collapse: "minimize the composer panel"
abandon: "close composer and discard draft"
enter_fullscreen: "enter fullscreen composer"
exit_fullscreen: "exit fullscreen composer"
modal_ok: "OK"
modal_cancel: "Cancel"
cant_send_pm: "Sorry, you can't send a message to %{username}."
@ -2613,6 +2615,7 @@ en:
composing:
title: 'Composing'
return: '<b>shift</b>+<b>c</b> Return to composer'
fullscreen: '<b>shift</b>+<b>F11</b> Fullscreen composer'
actions:
title: 'Actions'
bookmark_topic: '<b>f</b> Toggle bookmark topic'