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,
|
||||
similarTopics: null,
|
||||
|
||||
hidden: Ember.computed.not("composer.viewOpen"),
|
||||
hidden: Ember.computed.not("composer.viewOpenOrFullscreen"),
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 });
|
||||
},
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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")}}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -147,7 +147,7 @@
|
|||
margin-left: auto;
|
||||
margin-right: -5px;
|
||||
button {
|
||||
padding: 0 8px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue