mirror of
https://github.com/discourse/discourse.git
synced 2025-01-18 18:52:45 +08:00
UX: allow for an optional toolbar in composer in mobile
Allows preview of text, emoji, quoting, whisper
This commit is contained in:
parent
1a1dcb59f9
commit
3df2ee3431
|
@ -5,7 +5,7 @@ import { linkSeenCategoryHashtags, fetchUnseenCategoryHashtags } from 'discourse
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
classNames: ['wmd-controls'],
|
classNames: ['wmd-controls'],
|
||||||
classNameBindings: [':wmd-controls', 'showPreview', 'showPreview::hide-preview'],
|
classNameBindings: ['showToolbar:toolbar-visible', ':wmd-controls', 'showPreview', 'showPreview::hide-preview'],
|
||||||
|
|
||||||
uploadProgress: 0,
|
uploadProgress: 0,
|
||||||
showPreview: true,
|
showPreview: true,
|
||||||
|
@ -343,12 +343,34 @@ export default Ember.Component.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
showOptions() {
|
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 myPos = this.$().position();
|
||||||
const buttonPos = this.$('.options').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",
|
this.sendAction('showOptions', { position: "absolute",
|
||||||
left: myPos.left + buttonPos.left,
|
left: left,
|
||||||
top: myPos.top + buttonPos.top });
|
top: top });
|
||||||
},
|
},
|
||||||
|
|
||||||
showUploadModal(toolbarEvent) {
|
showUploadModal(toolbarEvent) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ function Toolbar() {
|
||||||
this.groups = [
|
this.groups = [
|
||||||
{group: 'fontStyles', buttons: []},
|
{group: 'fontStyles', buttons: []},
|
||||||
{group: 'insertions', buttons: []},
|
{group: 'insertions', buttons: []},
|
||||||
{group: 'extras', buttons: [], lastGroup: true}
|
{group: 'extras', buttons: []}
|
||||||
];
|
];
|
||||||
|
|
||||||
this.addButton({
|
this.addButton({
|
||||||
|
@ -105,6 +105,20 @@ function Toolbar() {
|
||||||
title: 'composer.hr_title',
|
title: 'composer.hr_title',
|
||||||
perform: e => e.addText("\n\n----------\n")
|
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) {
|
Toolbar.prototype.addButton = function(button) {
|
||||||
|
@ -166,6 +180,7 @@ export function onToolbarCreate(func) {
|
||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
classNames: ['d-editor'],
|
classNames: ['d-editor'],
|
||||||
ready: false,
|
ready: false,
|
||||||
|
forcePreview: false,
|
||||||
insertLinkHidden: true,
|
insertLinkHidden: true,
|
||||||
link: '',
|
link: '',
|
||||||
lastSel: null,
|
lastSel: null,
|
||||||
|
@ -446,6 +461,10 @@ export default Ember.Component.extend({
|
||||||
Ember.run.scheduleOnce("afterRender", () => this.$("textarea.d-editor-input").focus());
|
Ember.run.scheduleOnce("afterRender", () => this.$("textarea.d-editor-input").focus());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_togglePreview() {
|
||||||
|
this.toggleProperty('forcePreview');
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toolbarButton(button) {
|
toolbarButton(button) {
|
||||||
const selected = this._getSelected();
|
const selected = this._getSelected();
|
||||||
|
@ -453,7 +472,8 @@ export default Ember.Component.extend({
|
||||||
selected,
|
selected,
|
||||||
applySurround: (head, tail, exampleKey) => this._applySurround(selected, head, tail, exampleKey),
|
applySurround: (head, tail, exampleKey) => this._applySurround(selected, head, tail, exampleKey),
|
||||||
applyList: (head, exampleKey) => this._applyList(selected, head, 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) {
|
if (button.sendAction) {
|
||||||
|
@ -463,6 +483,10 @@ export default Ember.Component.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hidePreview() {
|
||||||
|
this.set('forcePreview', false);
|
||||||
|
},
|
||||||
|
|
||||||
showLinkModal() {
|
showLinkModal() {
|
||||||
this._lastSel = this._getSelected();
|
this._lastSel = this._getSelected();
|
||||||
this.set('insertLinkHidden', false);
|
this.set('insertLinkHidden', false);
|
||||||
|
|
|
@ -54,12 +54,10 @@ export default Ember.Controller.extend({
|
||||||
similarTopicsMessage: null,
|
similarTopicsMessage: null,
|
||||||
lastSimilaritySearch: null,
|
lastSimilaritySearch: null,
|
||||||
optionsVisible: false,
|
optionsVisible: false,
|
||||||
|
|
||||||
lastValidatedAt: null,
|
lastValidatedAt: null,
|
||||||
|
|
||||||
isUploading: false,
|
isUploading: false,
|
||||||
|
|
||||||
topic: null,
|
topic: null,
|
||||||
|
showToolbar: false,
|
||||||
|
|
||||||
_initializeSimilar: function() {
|
_initializeSimilar: function() {
|
||||||
this.set('similarTopics', []);
|
this.set('similarTopics', []);
|
||||||
|
@ -90,6 +88,10 @@ export default Ember.Controller.extend({
|
||||||
this.toggleProperty('model.whisper');
|
this.toggleProperty('model.whisper');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleToolbar() {
|
||||||
|
this.toggleProperty('showToolbar');
|
||||||
|
},
|
||||||
|
|
||||||
showOptions(loc) {
|
showOptions(loc) {
|
||||||
this.appEvents.trigger('popup-menu:open', loc);
|
this.appEvents.trigger('popup-menu:open', loc);
|
||||||
this.set('optionsVisible', true);
|
this.set('optionsVisible', true);
|
||||||
|
|
|
@ -89,11 +89,15 @@ function positioningWorkaround($fixedElement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkForInputs = _.debounce(function(){
|
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) {
|
if ($(elem).parents('.autocomplete').length > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($(elem).parents('.d-editor-button-bar').length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
attachTouchStart(this, function(evt){
|
attachTouchStart(this, function(evt){
|
||||||
done = true;
|
done = true;
|
||||||
$(document.activeElement).blur();
|
$(document.activeElement).blur();
|
||||||
|
|
|
@ -25,9 +25,12 @@
|
||||||
{{popup-input-tip validation=validation}}
|
{{popup-input-tip validation=validation}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-editor-preview-wrapper">
|
<div class="d-editor-preview-wrapper {{if forcePreview 'force-preview'}}">
|
||||||
<div class="d-editor-preview">
|
<div class="d-editor-preview">
|
||||||
{{{preview}}}
|
{{{preview}}}
|
||||||
</div>
|
</div>
|
||||||
|
{{#if site.mobileView}}
|
||||||
|
{{d-button action='hidePreview' class='hide-preview' label='composer.hide_preview'}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
|
|
||||||
{{render "composer-messages"}}
|
{{render "composer-messages"}}
|
||||||
<div class='control'>
|
<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>
|
<a href class='toggler' {{action "toggle" bubbles=false}} title={{i18n 'composer.toggler'}}></a>
|
||||||
|
|
||||||
{{#if model.viewOpen}}
|
{{#if model.viewOpen}}
|
||||||
|
@ -20,9 +24,11 @@
|
||||||
|
|
||||||
<div class='reply-to'>
|
<div class='reply-to'>
|
||||||
{{{model.actionTitle}}}
|
{{{model.actionTitle}}}
|
||||||
|
{{#unless site.mobileView}}
|
||||||
{{#if model.whisper}}
|
{{#if model.whisper}}
|
||||||
<span class='whisper'>({{i18n "composer.whisper"}})</span>
|
<span class='whisper'>({{i18n "composer.whisper"}})</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
{{#if canEdit}}
|
{{#if canEdit}}
|
||||||
{{#if showEditReason}}
|
{{#if showEditReason}}
|
||||||
|
@ -85,6 +91,7 @@
|
||||||
groupsMentioned="groupsMentioned"
|
groupsMentioned="groupsMentioned"
|
||||||
importQuote="importQuote"
|
importQuote="importQuote"
|
||||||
showOptions="showOptions"
|
showOptions="showOptions"
|
||||||
|
showToolbar=showToolbar
|
||||||
showUploadSelector="showUploadSelector"}}
|
showUploadSelector="showUploadSelector"}}
|
||||||
|
|
||||||
{{#if currentUser}}
|
{{#if currentUser}}
|
||||||
|
@ -92,6 +99,12 @@
|
||||||
{{plugin-outlet "composer-fields-below"}}
|
{{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>
|
<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>
|
<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>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,7 +36,7 @@ input {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
.toggler {
|
.toggle-toolbar, .toggler {
|
||||||
width: 15px;
|
width: 15px;
|
||||||
right: 1px;
|
right: 1px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -48,6 +48,14 @@ input {
|
||||||
content: "\f078";
|
content: "\f078";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-toolbar {
|
||||||
|
right: 30px;
|
||||||
|
&:before {
|
||||||
|
content: "\f0c9";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a.cancel {
|
a.cancel {
|
||||||
padding-left: 7px;
|
padding-left: 7px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
|
@ -56,7 +64,7 @@ input {
|
||||||
margin: 0 0 0 5px;
|
margin: 0 0 0 5px;
|
||||||
.reply-to {
|
.reply-to {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-width: 92%;
|
max-width: 80%;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
i {
|
i {
|
||||||
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
|
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
|
||||||
|
@ -235,6 +243,31 @@ input {
|
||||||
.d-editor-preview-wrapper {
|
.d-editor-preview-wrapper {
|
||||||
display: none;
|
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 {
|
.d-editor-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -260,6 +293,40 @@ input {
|
||||||
.d-editor-button-bar {
|
.d-editor-button-bar {
|
||||||
display: none;
|
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
|
// make sure the category selector *NEVER* gets focus by default on mobile anywhere
|
||||||
|
|
Loading…
Reference in New Issue
Block a user