REFACTOR: composer to use flexbox

This commit is contained in:
Régis Hanol
2017-11-08 10:39:26 +01:00
parent b840971b77
commit 0bb07d395a
10 changed files with 604 additions and 1024 deletions
@@ -15,6 +15,7 @@ export default Ember.Component.extend(KeyEnterEscape, {
'composer.createdPost:created-post',
'composer.creatingTopic:topic',
'composer.whisper:composing-whisper',
'showPreview:show-preview:hide-preview',
'currentUserPrimaryGroupClass'],
@computed("currentUser.primary_group_name")
@@ -41,19 +42,6 @@ export default Ember.Component.extend(KeyEnterEscape, {
const h = $('#reply-control').height() || 0;
this.movePanels(h + "px");
// Figure out the size of the fields
const $fields = this.$('.composer-fields');
const fieldPos = $fields.position();
if (fieldPos) {
this.$('.wmd-controls').css('top', $fields.height() + fieldPos.top + 5);
}
// get the submit panel height
const submitPos = this.$('.submit-panel').position();
if (submitPos) {
this.$('.wmd-controls').css('bottom', h - submitPos.top + 7);
}
});
},
@@ -1,5 +1,5 @@
import userSearch from 'discourse/lib/user-search';
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
import { default as computed, on } from 'ember-addons/ember-computed-decorators';
import { linkSeenMentions, fetchUnseenMentions } from 'discourse/lib/link-mentions';
import { linkSeenCategoryHashtags, fetchUnseenCategoryHashtags } from 'discourse/lib/link-category-hashtags';
import { linkSeenTagHashtags, fetchUnseenTagHashtags } from 'discourse/lib/link-tag-hashtag';
@@ -18,11 +18,9 @@ import { lookupCachedUploadUrl,
cacheShortUploadUrl } from 'pretty-text/image-short-url';
export default Ember.Component.extend({
classNames: ['wmd-controls'],
classNameBindings: ['showToolbar:toolbar-visible', ':wmd-controls', 'showPreview', 'showPreview::hide-preview'],
classNameBindings: ['showToolbar:toolbar-visible', ':wmd-controls'],
uploadProgress: 0,
showPreview: true,
_xhr: null,
@computed
@@ -30,35 +28,6 @@ export default Ember.Component.extend({
return `[${I18n.t('uploading')}]() `;
},
@on('init')
_setupPreview() {
const val = (this.site.mobileView ? false : (this.keyValueStore.get('composer.showPreview') || 'true'));
this.set('showPreview', val === 'true');
this.appEvents.on('composer:show-preview', () => {
this.set('showPreview', true);
});
this.appEvents.on('composer:hide-preview', () => {
this.set('showPreview', false);
});
},
@computed('site.mobileView', 'showPreview')
forcePreview(mobileView, showPreview) {
return mobileView && showPreview;
},
@computed('showPreview')
toggleText: function(showPreview) {
return showPreview ? I18n.t('composer.hide_preview') : I18n.t('composer.show_preview');
},
@observes('showPreview')
showPreviewChanged() {
this.keyValueStore.set({ key: 'composer.showPreview', value: this.get('showPreview') });
},
@computed
markdownOptions() {
return {
@@ -363,7 +332,7 @@ export default Ember.Component.extend({
});
if (this.site.mobileView) {
this.$(".mobile-file-upload").on("click.uploader", function () {
$("#reply-control .mobile-file-upload").on("click.uploader", function () {
// redirect the click on the hidden file input
$("#mobile-uploader").click();
});
@@ -373,29 +342,28 @@ export default Ember.Component.extend({
},
_optionsLocation() {
// 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 composer = $("#reply-control");
const composerOffset = composer.offset();
const composerPosition = composer.position();
const popupHeight = $('#reply-control .popup-menu').height();
const popupWidth = $('#reply-control .popup-menu').width();
const buttonBarOffset = $('#reply-control .d-editor-button-bar').offset();
const optionsButton = $('#reply-control .d-editor-button-bar .options');
var top = myPos.top + buttonPos.top - 15;
var left = myPos.left + buttonPos.left - (popupWidth/2);
const popupMenu = $("#reply-control .popup-menu");
const popupWidth = popupMenu.outerWidth();
const popupHeight = popupMenu.outerHeight();
const composerPos = $('#reply-control').position();
const headerHeight = $(".d-header").outerHeight();
if (composerPos.top + top - popupHeight < 0) {
top = top + popupHeight + this.$('.options').height() + 50;
let left = optionsButton.offset().left - composerOffset.left;
let top = buttonBarOffset.top - composerOffset.top - popupHeight + popupMenu.innerHeight();
if (top + composerPosition.top - headerHeight - popupHeight < 0) {
top += popupHeight + optionsButton.outerHeight();
}
var replyWidth = $('#reply-control').width();
if (left + popupWidth > replyWidth) {
left = replyWidth - popupWidth - 40;
if (left + popupWidth > composer.width()) {
left -= popupWidth - optionsButton.outerWidth();
}
return { position: "absolute", left, top };
@@ -493,7 +461,7 @@ export default Ember.Component.extend({
@on('willDestroyElement')
_unbindUploadTarget() {
this._validUploads = 0;
this.$(".mobile-file-upload").off("click.uploader");
$("#reply-control .mobile-file-upload").off("click.uploader");
this.messageBus.unsubscribe("/uploads/composer");
const $uploadTarget = this.$();
try { $uploadTarget.fileupload("destroy"); }
@@ -504,8 +472,6 @@ export default Ember.Component.extend({
@on('willDestroyElement')
_composerClosed() {
this.appEvents.trigger('composer:will-close');
this.appEvents.off('composer:show-preview');
this.appEvents.off('composer:hide-preview');
Ember.run.next(() => {
$('#main-outlet').css('padding-bottom', 0);
// need to wait a bit for the "slide down" transition of the composer
@@ -541,12 +507,12 @@ export default Ember.Component.extend({
}
},
showUploadModal(toolbarEvent) {
this.sendAction('showUploadSelector', toolbarEvent);
togglePreview() {
this.sendAction('togglePreview');
},
togglePreview() {
this.toggleProperty('showPreview');
showUploadModal(toolbarEvent) {
this.sendAction('showUploadSelector', toolbarEvent);
},
extraButtons(toolbar) {
@@ -2,7 +2,7 @@ import DiscourseURL from 'discourse/lib/url';
import Quote from 'discourse/lib/quote';
import Draft from 'discourse/models/draft';
import Composer from 'discourse/models/composer';
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
import { default as computed, observes, on } from 'ember-addons/ember-computed-decorators';
import InputValidation from 'discourse/models/input-validation';
import { getOwner } from 'discourse-common/lib/get-owner';
import { escapeExpression } from 'discourse/lib/utilities';
@@ -68,9 +68,27 @@ export default Ember.Controller.extend({
isUploading: false,
topic: null,
linkLookup: null,
showPreview: true,
forcePreview: Ember.computed.and('site.mobileView', 'showPreview'),
whisperOrUnlistTopic: Ember.computed.or('model.whisper', 'model.unlistTopic'),
categories: Ember.computed.alias('site.categoriesList'),
@on('init')
_setupPreview() {
const val = (this.site.mobileView ? false : (this.keyValueStore.get('composer.showPreview') || 'true'));
this.set('showPreview', val === 'true');
},
@computed('showPreview')
toggleText: function(showPreview) {
return showPreview ? I18n.t('composer.hide_preview') : I18n.t('composer.show_preview');
},
@observes('showPreview')
showPreviewChanged() {
this.keyValueStore.set({ key: 'composer.showPreview', value: this.get('showPreview') });
},
@computed('model.replyingToTopic', 'model.creatingPrivateMessage', 'model.targetUsernames')
focusTarget(replyingToTopic, creatingPM, usernames) {
if (this.capabilities.isIOS) { return "none"; }
@@ -206,6 +224,10 @@ export default Ember.Controller.extend({
actions: {
togglePreview() {
this.toggleProperty('showPreview');
},
typed() {
this.checkReplyLength();
this.get('model').typing();
@@ -291,10 +313,6 @@ export default Ember.Controller.extend({
return false;
},
togglePreview() {
this.get('model').togglePreview();
},
// Import a quote from the post
importQuote(toolbarEvent) {
const postStream = this.get('topic.postStream');
@@ -13,28 +13,6 @@
forcePreview=forcePreview
composerEvents=true}}
<div class="composer-bottom-right">
{{#if site.mobileView}}
{{#if site.mobileView}}
<input type="file" id="mobile-uploader" multiple />
<a class="mobile-file-upload {{if isUploading 'hidden'}}">{{i18n 'upload'}}</a>
{{#if showPreview}}
{{d-button action='togglePreview' class='hide-preview' label='composer.hide_preview'}}
{{/if}}
{{else}}
<a href {{action "togglePreview"}} class='toggle-preview'>{{{toggleText}}}</a>
{{/if}}
{{#if isUploading}}
<div id="file-uploading">
{{loading-spinner size="small"}} {{i18n 'upload_selector.uploading'}}
{{uploadProgress}}%
{{#if isCancellable}}
<a href id="cancel-file-upload" {{action "cancelUpload"}}>{{d-icon "times"}}</a>
{{/if}}
</div>
{{/if}}
<div id='draft-status' class="{{if isUploading 'hidden'}}">
{{draftStatus}}
</div>
</div>
{{/if}}
@@ -1,4 +1,5 @@
<div class='d-editor-overlay hidden'></div>
<div class='d-editor-modals'>
{{#d-editor-modal class="insert-link" hidden=insertLinkHidden okAction="insertLink"}}
<h3>{{i18n "composer.link_dialog_title"}}</h3>
@@ -8,19 +9,18 @@
</div>
<div class='d-editor-container'>
<div class='d-editor-button-bar'>
{{#each toolbar.groups as |group|}}
{{#each group.buttons as |b|}}
{{d-button action=b.action actionParam=b translatedTitle=b.title label=b.label icon=b.icon class=b.className}}
{{/each}}
{{#unless group.lastGroup}}
<div class='d-editor-spacer'></div>
{{/unless}}
{{/each}}
</div>
<div class='d-editor-preview-header'></div>
<div class="d-editor-textarea-wrapper">
<div class='d-editor-button-bar'>
{{#each toolbar.groups as |group|}}
{{#each group.buttons as |b|}}
{{d-button action=b.action actionParam=b translatedTitle=b.title label=b.label icon=b.icon class=b.className}}
{{/each}}
{{#unless group.lastGroup}}
<div class='d-editor-spacer'></div>
{{/unless}}
{{/each}}
</div>
{{conditional-loading-spinner condition=loading}}
{{textarea tabindex=tabindex value=value class="d-editor-input" placeholder=placeholderTranslated}}
{{popup-input-tip validation=validation}}
@@ -30,7 +30,7 @@
<div class="d-editor-preview">
{{{preview}}}
</div>
{{plugin-outlet name="editor-preview"}}
{{plugin-outlet name="editor-preview" classNames="d-editor-plugin"}}
</div>
</div>
@@ -1,11 +1,11 @@
{{#composer-body
composer=model
openIfDraft="openIfDraft"
typed="typed"
cancelled="cancelled"
save="save"}}
{{#composer-body composer=model
showPreview=showPreview
openIfDraft="openIfDraft"
typed="typed"
cancelled="cancelled"
save="save"}}
{{#if visible}}
<div class='contents'>
{{#if showPopupMenu}}
{{#popup-menu visible=optionsVisible hide="hideOptions" title="composer.options"}}
@@ -19,134 +19,169 @@
{{/popup-menu}}
{{/if}}
{{composer-messages composer=model messageCount=messageCount addLinkLookup="addLinkLookup"}}
{{composer-messages composer=model
messageCount=messageCount
addLinkLookup="addLinkLookup"}}
<div class='control'>
{{composer-toggles
composeState=model.composeState
toggleComposer=(action "toggle")
toggleToolbar=(action "toggleToolbar")}}
{{composer-toggles composeState=model.composeState
toggleComposer=(action "toggle")
toggleToolbar=(action "toggleToolbar")}}
{{#if model.viewOpen}}
<div class='control-row reply-area'>
<div class='composer-fields'>
{{plugin-outlet name="composer-open" args=(hash model=model)}}
{{#if model.viewOpen}}
<div class='reply-to'>
{{{model.actionTitle}}}
{{#unless site.mobileView}}
<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'>
{{{model.actionTitle}}}
{{#unless site.mobileView}}
{{#if whisperOrUnlistTopicText}}
<span class='whisper'>({{whisperOrUnlistTopicText}})</span>
{{/if}}
{{/unless}}
{{/unless}}
{{#if canEdit}}
{{#if showEditReason}}
<div class="edit-reason-input">
{{text-field autofocus="true" value=editReason tabindex="7" id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}}
</div>
{{else}}
<a {{action "displayEditReason"}} class="display-edit-reason">{{i18n 'composer.show_edit_reason'}}</a>
{{/if}}
{{#if canEdit}}
{{#if showEditReason}}
{{text-field autofocus="true" value=editReason tabindex="7" id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}}
{{else}}
<a {{action "displayEditReason"}} class="display-edit-reason">{{i18n 'composer.show_edit_reason'}}</a>
{{/if}}
</div>
{{/if}}
</div>
{{#if model.canEditTitle}}
<div class='form-element clearfix'>
{{#if model.creatingPrivateMessage}}
{{composer-user-selector topicId=topicModel.id
usernames=model.targetUsernames
hasGroups=model.hasTargetGroups
focusTarget=focusTarget}}
{{#if showWarning}}
<div class='add-warning'>
<label>
{{input type="checkbox" checked=model.isWarning tabindex="3"}}
{{i18n "composer.add_warning"}}
</label>
</div>
{{/if}}
{{/if}}
{{composer-title composer=model lastValidatedAt=lastValidatedAt focusTarget=focusTarget}}
{{#if model.showCategoryChooser}}
<div class="category-input">
{{category-chooser value=model.categoryId scopedCategoryId=scopedCategoryId tabindex="3"}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{#if model.archetype.hasOptions}}
{{d-button action="showOptions" label="topic.options"}}
{{/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>
{{/if}}
</div>
{{/if}}
{{plugin-outlet name="composer-fields" args=(hash model=model)}}
</div>
{{composer-editor topic=topic
composer=model
lastValidatedAt=lastValidatedAt
canWhisper=canWhisper
showPopupMenu=showPopupMenu
draftStatus=model.draftStatus
isUploading=isUploading
groupsMentioned="groupsMentioned"
cannotSeeMention="cannotSeeMention"
importQuote="importQuote"
showOptions="showOptions"
hideOptions="hideOptions"
optionsVisible=optionsVisible
showToolbar=showToolbar
showUploadSelector="showUploadSelector"
afterRefresh="afterRefresh"}}
<div class="title-and-category">
{{#if currentUser}}
<div class='submit-panel'>
{{plugin-outlet name="composer-fields-below" args=(hash model=model)}}
{{#if canEditTags}}
{{tag-chooser tags=model.tags tabIndex="4" categoryId=model.categoryId}}
{{composer-title composer=model lastValidatedAt=lastValidatedAt focusTarget=focusTarget}}
{{#if model.showCategoryChooser}}
<div class="category-input">
{{category-chooser value=model.categoryId scopedCategoryId=scopedCategoryId tabindex="3"}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{#if model.archetype.hasOptions}}
{{d-button action="showOptions" label="topic.options"}}
{{/if}}
{{/if}}
{{composer-save-button
action=(action "save")
icon=model.saveIcon
label=model.saveLabel
disableSubmit=disableSubmit}}
<a href {{action "cancel"}} class='cancel' tabindex="6">{{i18n 'cancel'}}</a>
</div>
{{/if}}
{{#if site.mobileView}}
{{plugin-outlet name="composer-fields" args=(hash model=model)}}
</div>
{{composer-editor topic=topic
composer=model
lastValidatedAt=lastValidatedAt
canWhisper=canWhisper
showPopupMenu=showPopupMenu
draftStatus=model.draftStatus
isUploading=isUploading
isCancellable=isCancellable
uploadProgress=uploadProgress
groupsMentioned="groupsMentioned"
cannotSeeMention="cannotSeeMention"
importQuote="importQuote"
showOptions="showOptions"
hideOptions="hideOptions"
optionsVisible=optionsVisible
togglePreview="togglePreview"
showToolbar=showToolbar
showUploadSelector="showUploadSelector"
afterRefresh="afterRefresh"}}
<div class='submit-panel {{if canEditTags "with-tags"}}'>
{{plugin-outlet name="composer-fields-below" args=(hash model=model)}}
{{#if canEditTags}}
{{tag-chooser tags=model.tags tabIndex="4" categoryId=model.categoryId}}
{{/if}}
<div class='save-or-cancel'>
{{composer-save-button action=(action "save")
icon=model.saveIcon
label=model.saveLabel
disableSubmit=disableSubmit}}
<a href {{action "cancel"}} class='cancel' tabindex="6">{{i18n 'cancel'}}</a>
{{#if site.mobileView}}
{{#if whisperOrUnlistTopic}}
<span class='whisper'>
{{d-icon "eye-slash"}}
</span>
{{/if}}
{{/if}}
</div>
{{/if}}
</div>
{{else}}
<div class='row'>
<div class='span24'>
<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>
{{else}}
{{i18n 'composer.saving'}} {{loading-spinner size="small"}}
{{/if}}
</div>
<div class='draft-text'>
{{#if model.topic}}
{{d-icon "mail-forward"}} {{{draftTitle}}}
{{else}}
{{i18n "composer.saved_draft"}}
{{/if}}
{{/if}}
{{#if isUploading}}
<div id="file-uploading">
{{loading-spinner size="small"}} {{i18n 'upload_selector.uploading'}}
{{uploadProgress}}%
{{#if isCancellable}}
<a href id="cancel-file-upload" {{action "cancelUpload"}}>{{d-icon "times"}}</a>
{{/if}}
</div>
{{/if}}
<div id='draft-status' class="{{if isUploading 'hidden'}}">
{{model.draftStatus}}
</div>
</div>
</div>
{{/if}}
</div>
</div>
<div class="composer-bottom-right">
{{#if site.mobileView}}
<a class="mobile-file-upload {{if isUploading 'hidden'}}">{{i18n 'upload'}}</a>
{{#if showPreview}}
{{d-button action='togglePreview' class='hide-preview' label='composer.hide_preview'}}
{{/if}}
{{else}}
<a href {{action "togglePreview"}} class='toggle-preview'>{{{toggleText}}}</a>
{{/if}}
</div>
</div>
</div>
{{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>
{{else}}
{{i18n 'composer.saving'}} {{loading-spinner size="small"}}
{{/if}}
</div>
<div class='draft-text'>
{{#if model.topic}}
{{d-icon "mail-forward"}} {{{draftTitle}}}
{{else}}
{{i18n "composer.saved_draft"}}
{{/if}}
</div>
{{/if}}
{{/if}}
{{/composer-body}}