UX: allow for an optional toolbar in composer in mobile

Allows preview of text, emoji, quoting, whisper
This commit is contained in:
Sam 2016-02-09 17:10:24 +11:00
parent 1a1dcb59f9
commit 3df2ee3431
7 changed files with 147 additions and 12 deletions

View file

@ -5,7 +5,7 @@ import { linkSeenCategoryHashtags, fetchUnseenCategoryHashtags } from 'discourse
export default Ember.Component.extend({
classNames: ['wmd-controls'],
classNameBindings: [':wmd-controls', 'showPreview', 'showPreview::hide-preview'],
classNameBindings: ['showToolbar:toolbar-visible', ':wmd-controls', 'showPreview', 'showPreview::hide-preview'],
uploadProgress: 0,
showPreview: true,
@ -343,12 +343,34 @@ export default Ember.Component.extend({
},
showOptions() {
// long term we want some smart positioning algorithm in popup-menu
// the problem is that positioning in a fixed panel is a nightmare
// cause offsetParent can end up returning a fixed element and then
// using offset() is not going to work, so you end up needing special logic
// especially since we allow for negative .top, provided there is room on screen
const myPos = this.$().position();
const buttonPos = this.$('.options').position();
const popupHeight = $('#reply-control .popup-menu').height();
const popupWidth = $('#reply-control .popup-menu').width();
var top = myPos.top + buttonPos.top - 15;
var left = myPos.left + buttonPos.left - (popupWidth/2);
const composerPos = $('#reply-control').position();
if (composerPos.top + top - popupHeight < 0) {
top = top + popupHeight + this.$('.options').height() + 50;
}
var replyWidth = $('#reply-control').width();
if (left + popupWidth > replyWidth) {
left = replyWidth - popupWidth - 40;
}
this.sendAction('showOptions', { position: "absolute",
left: myPos.left + buttonPos.left,
top: myPos.top + buttonPos.top });
left: left,
top: top });
},
showUploadModal(toolbarEvent) {

View file

@ -31,7 +31,7 @@ function Toolbar() {
this.groups = [
{group: 'fontStyles', buttons: []},
{group: 'insertions', buttons: []},
{group: 'extras', buttons: [], lastGroup: true}
{group: 'extras', buttons: []}
];
this.addButton({
@ -105,6 +105,20 @@ function Toolbar() {
title: 'composer.hr_title',
perform: e => e.addText("\n\n----------\n")
});
if (Discourse.Mobile.mobileView) {
this.groups.push({group: 'mobileExtras', buttons: []});
this.addButton({
id: 'preview',
group: 'mobileExtras',
icon: 'television',
title: 'composer.hr_preview',
perform: e => e.preview()
});
}
this.groups[this.groups.length-1].lastGroup = true;
};
Toolbar.prototype.addButton = function(button) {
@ -166,6 +180,7 @@ export function onToolbarCreate(func) {
export default Ember.Component.extend({
classNames: ['d-editor'],
ready: false,
forcePreview: false,
insertLinkHidden: true,
link: '',
lastSel: null,
@ -446,6 +461,10 @@ export default Ember.Component.extend({
Ember.run.scheduleOnce("afterRender", () => this.$("textarea.d-editor-input").focus());
},
_togglePreview() {
this.toggleProperty('forcePreview');
},
actions: {
toolbarButton(button) {
const selected = this._getSelected();
@ -453,7 +472,8 @@ export default Ember.Component.extend({
selected,
applySurround: (head, tail, exampleKey) => this._applySurround(selected, head, tail, exampleKey),
applyList: (head, exampleKey) => this._applyList(selected, head, exampleKey),
addText: text => this._addText(selected, text)
addText: text => this._addText(selected, text),
preview: () => this._togglePreview()
};
if (button.sendAction) {
@ -463,6 +483,10 @@ export default Ember.Component.extend({
}
},
hidePreview() {
this.set('forcePreview', false);
},
showLinkModal() {
this._lastSel = this._getSelected();
this.set('insertLinkHidden', false);

View file

@ -54,12 +54,10 @@ export default Ember.Controller.extend({
similarTopicsMessage: null,
lastSimilaritySearch: null,
optionsVisible: false,
lastValidatedAt: null,
isUploading: false,
topic: null,
showToolbar: false,
_initializeSimilar: function() {
this.set('similarTopics', []);
@ -90,6 +88,10 @@ export default Ember.Controller.extend({
this.toggleProperty('model.whisper');
},
toggleToolbar() {
this.toggleProperty('showToolbar');
},
showOptions(loc) {
this.appEvents.trigger('popup-menu:open', loc);
this.set('optionsVisible', true);

View file

@ -89,11 +89,15 @@ function positioningWorkaround($fixedElement) {
}
const checkForInputs = _.debounce(function(){
$fixedElement.find('button,a:not(.mobile-file-upload)').each(function(idx, elem){
$fixedElement.find('button:not(.hide-preview),a:not(.mobile-file-upload):not(.toggle-toolbar)').each(function(idx, elem){
if ($(elem).parents('.autocomplete').length > 0) {
return;
}
if ($(elem).parents('.d-editor-button-bar').length > 0) {
return;
}
attachTouchStart(this, function(evt){
done = true;
$(document.activeElement).blur();

View file

@ -25,9 +25,12 @@
{{popup-input-tip validation=validation}}
</div>
<div class="d-editor-preview-wrapper">
<div class="d-editor-preview-wrapper {{if forcePreview 'force-preview'}}">
<div class="d-editor-preview">
{{{preview}}}
</div>
{{#if site.mobileView}}
{{d-button action='hidePreview' class='hide-preview' label='composer.hide_preview'}}
{{/if}}
</div>
</div>

View file

@ -11,6 +11,10 @@
{{render "composer-messages"}}
<div class='control'>
{{#if site.mobileView}}
<a href class='toggle-toolbar' {{action "toggleToolbar" bubbles=false}}></a>
{{/if}}
<a href class='toggler' {{action "toggle" bubbles=false}} title={{i18n 'composer.toggler'}}></a>
{{#if model.viewOpen}}
@ -20,9 +24,11 @@
<div class='reply-to'>
{{{model.actionTitle}}}
{{#unless site.mobileView}}
{{#if model.whisper}}
<span class='whisper'>({{i18n "composer.whisper"}})</span>
{{/if}}
{{/unless}}
{{#if canEdit}}
{{#if showEditReason}}
@ -85,6 +91,7 @@
groupsMentioned="groupsMentioned"
importQuote="importQuote"
showOptions="showOptions"
showToolbar=showToolbar
showUploadSelector="showUploadSelector"}}
{{#if currentUser}}
@ -92,6 +99,12 @@
{{plugin-outlet "composer-fields-below"}}
<button {{action "save"}} tabindex="5" {{bind-attr class=":btn :btn-primary :create disableSubmit:disabled"}} title="{{i18n 'composer.title'}}">{{{model.saveIcon}}}{{model.saveText}}</button>
<a href {{action "cancel"}} class='cancel' tabindex="6">{{i18n 'cancel'}}</a>
{{#if site.mobileView}}
{{#if model.whisper}}
<span class='whisper'><i class='fa fa-eye-slash'></i></span>
{{/if}}
{{/if}}
</div>
{{/if}}
</div>

View file

@ -36,7 +36,7 @@ input {
bottom: 0;
font-size: 1em;
position: fixed;
.toggler {
.toggle-toolbar, .toggler {
width: 15px;
right: 1px;
position: absolute;
@ -48,6 +48,14 @@ input {
content: "\f078";
}
}
.toggle-toolbar {
right: 30px;
&:before {
content: "\f0c9";
}
}
a.cancel {
padding-left: 7px;
line-height: 30px;
@ -56,7 +64,7 @@ input {
margin: 0 0 0 5px;
.reply-to {
overflow: hidden;
max-width: 92%;
max-width: 80%;
white-space: nowrap;
i {
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
@ -235,6 +243,31 @@ input {
.d-editor-preview-wrapper {
display: none;
}
.d-editor-preview-wrapper.force-preview {
display: block;
position: fixed;
z-index: 1000000;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: $secondary;
.d-editor-preview {
height: 90%;
height: calc(100% - 60px);
border: 0;
overflow: auto;
}
.hide-preview {
position: fixed;
right: 5px;
bottom: 5px;
z-index: 1000001;
}
}
.d-editor-input {
width: 100%;
height: 100%;
@ -260,6 +293,40 @@ input {
.d-editor-button-bar {
display: none;
}
.wmd-controls.toolbar-visible .d-editor-input {
padding-top: 40px;
}
.wmd-controls.toolbar-visible .d-editor-button-bar {
.btn.link, .btn.upload, .btn.rule, .btn.bullet, .btn.list, .btn.heading {
display: none;
}
display: block;
margin: 1px 4px;
position: absolute;
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
background-color: $secondary;
z-index: 100;
overflow: hidden;
width: 100%;
width: calc(100% - 10px);
-moz-box-sizing: border-box;
box-sizing: border-box;
button {
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
}
button.btn.no-text {
margin: 0 2px;
padding: 2px 5px;
position: static;
}
}
}
// make sure the category selector *NEVER* gets focus by default on mobile anywhere