From 3df2ee343154e78f8490a418df08656323c993b0 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 9 Feb 2016 17:10:24 +1100 Subject: [PATCH] UX: allow for an optional toolbar in composer in mobile Allows preview of text, emoji, quoting, whisper --- .../components/composer-editor.js.es6 | 28 +++++++- .../discourse/components/d-editor.js.es6 | 28 +++++++- .../discourse/controllers/composer.js.es6 | 8 ++- .../discourse/lib/safari-hacks.js.es6 | 6 +- .../templates/components/d-editor.hbs | 5 +- .../discourse/templates/composer.hbs | 13 ++++ app/assets/stylesheets/mobile/compose.scss | 71 ++++++++++++++++++- 7 files changed, 147 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6 index 9dbd7f25d..ddc86134c 100644 --- a/app/assets/javascripts/discourse/components/composer-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6 @@ -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) { diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 5ebd6ed92..e81f14c21 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -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); diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 1c7384c25..88e6c532b 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -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); diff --git a/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 b/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 index c14aa64b6..93e584f23 100644 --- a/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 +++ b/app/assets/javascripts/discourse/lib/safari-hacks.js.es6 @@ -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(); diff --git a/app/assets/javascripts/discourse/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/templates/components/d-editor.hbs index 64ea89098..e9226920d 100644 --- a/app/assets/javascripts/discourse/templates/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/templates/components/d-editor.hbs @@ -25,9 +25,12 @@ {{popup-input-tip validation=validation}} -
+
{{{preview}}}
+ {{#if site.mobileView}} + {{d-button action='hidePreview' class='hide-preview' label='composer.hide_preview'}} + {{/if}}
diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs index bc3fa3270..06ebfdfbe 100644 --- a/app/assets/javascripts/discourse/templates/composer.hbs +++ b/app/assets/javascripts/discourse/templates/composer.hbs @@ -11,6 +11,10 @@ {{render "composer-messages"}}
+ + {{#if site.mobileView}} + + {{/if}} {{#if model.viewOpen}} @@ -20,9 +24,11 @@
{{{model.actionTitle}}} + {{#unless site.mobileView}} {{#if model.whisper}} ({{i18n "composer.whisper"}}) {{/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"}} {{i18n 'cancel'}} + + {{#if site.mobileView}} + {{#if model.whisper}} + + {{/if}} + {{/if}}
{{/if}}
diff --git a/app/assets/stylesheets/mobile/compose.scss b/app/assets/stylesheets/mobile/compose.scss index aac01b2e9..d7372c53a 100644 --- a/app/assets/stylesheets/mobile/compose.scss +++ b/app/assets/stylesheets/mobile/compose.scss @@ -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