Migrate composer view to component

This commit is contained in:
Robin Ward 2016-11-18 11:17:15 -05:00
parent 4e82e3ac75
commit c9af4b839e
5 changed files with 251 additions and 242 deletions

View File

@ -68,7 +68,6 @@
//= require ./discourse/lib/emoji/groups
//= require ./discourse/lib/emoji/toolbar
//= require ./discourse/components/d-editor
//= require ./discourse/views/composer
//= require ./discourse/lib/show-modal
//= require ./discourse/lib/screen-track
//= require ./discourse/routes/discourse

View File

@ -0,0 +1,113 @@
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
import Composer from 'discourse/models/composer';
import afterTransition from 'discourse/lib/after-transition';
import positioningWorkaround from 'discourse/lib/safari-hacks';
import { headerHeight } from 'discourse/components/site-header';
export default Ember.Component.extend({
elementId: 'reply-control',
classNameBindings: ['composer.creatingPrivateMessage:private-message',
'composeState',
'composer.loading',
'composer.canEditTitle:edit-title',
'composer.createdPost:created-post',
'composer.creatingTopic:topic',
'composer.whisper:composing-whisper'],
@computed('composer.composeState')
composeState(composeState) {
return composeState || Composer.CLOSED;
},
movePanels(sizePx) {
$('#main-outlet').css('padding-bottom', sizePx);
// signal the progress bar it should move!
this.appEvents.trigger("composer:resized");
},
@observes('composeState', 'composer.action')
resize() {
Ember.run.scheduleOnce('afterRender', () => {
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);
}
});
},
keyUp() {
this.sendAction('typed');
const lastKeyUp = new Date();
this._lastKeyUp = lastKeyUp;
// One second from now, check to see if the last key was hit when
// we recorded it. If it was, the user paused typing.
Ember.run.cancel(this._lastKeyTimeout);
this._lastKeyTimeout = Ember.run.later(() => {
if (lastKeyUp !== this._lastKeyUp) { return; }
this.appEvents.trigger('composer:find-similar');
}, 1000);
},
keyDown(e) {
if (e.which === 27) {
this.sendAction('cancelled');
return false;
} else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
// CTRL+ENTER or CMD+ENTER
this.sendAction('save');
return false;
}
},
didInsertElement() {
this._super();
const $replyControl = $('#reply-control');
const resize = () => Ember.run(() => this.resize());
$replyControl.DivResizer({
resize,
maxHeight: winHeight => winHeight - headerHeight(),
onDrag: sizePx => this.movePanels(sizePx)
});
const triggerOpen = () => {
if (this.get('composer.composeState') === Composer.OPEN) {
this.appEvents.trigger('composer:opened');
}
};
triggerOpen();
afterTransition($replyControl, () => {
resize();
triggerOpen();
});
positioningWorkaround(this.$());
this.appEvents.on('composer:resize', this, this.resize);
},
willDestroyElement() {
this._super();
this.appEvents.off('composer:resize', this, this.resize);
},
click() {
this.sendAction('openIfDraft');
},
});

View File

@ -196,6 +196,17 @@ export default Ember.Controller.extend({
}.property('model.creatingPrivateMessage', 'model.targetUsernames'),
actions: {
typed() {
this.checkReplyLength();
this.get('model').typing();
},
cancelled() {
this.send('hitEsc');
this.send('hideOptions');
},
addLinkLookup(linkLookup) {
this.set('linkLookup', linkLookup);
},

View File

@ -1,136 +1,143 @@
{{#if visible}}
<div class='contents'>
{{#composer-body
composer=model
openIfDraft="openIfDraft"
typed="typed"
cancelled="cancelled"
save="save"}}
{{#if visible}}
<div class='contents'>
{{#if showPopupMenu}}
{{#popup-menu visible=optionsVisible hide="hideOptions" title="composer.options"}}
{{#each popupMenuOptions as |option|}}
{{#if option.condition}}
<li>
{{d-button action=option.action icon=option.icon label=option.label}}
</li>
{{#if showPopupMenu}}
{{#popup-menu visible=optionsVisible hide="hideOptions" title="composer.options"}}
{{#each popupMenuOptions as |option|}}
{{#if option.condition}}
<li>
{{d-button action=option.action icon=option.icon label=option.label}}
</li>
{{/if}}
{{/each}}
{{/popup-menu}}
{{/if}}
{{composer-messages composer=model messageCount=messageCount addLinkLookup="addLinkLookup"}}
<div class='control'>
{{#if site.mobileView}}
<a href class='toggle-toolbar' {{action "toggleToolbar" bubbles=false}}></a>
{{/if}}
{{/each}}
{{/popup-menu}}
{{/if}}
<a href class='toggler' {{action "toggle" bubbles=false}} title={{i18n 'composer.toggler'}}></a>
{{composer-messages composer=model messageCount=messageCount addLinkLookup="addLinkLookup"}}
{{#if model.viewOpen}}
<div class='control-row reply-area'>
<div class='composer-fields'>
{{plugin-outlet "composer-open"}}
<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}}
<div class='control-row reply-area'>
<div class='composer-fields'>
{{plugin-outlet "composer-open"}}
<div class='reply-to'>
{{{model.actionTitle}}}
{{#unless site.mobileView}}
{{#if whisperOrUnlistTopicText}}
<span class='whisper'>({{whisperOrUnlistTopicText}})</span>
{{/if}}
{{/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}}
</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>
<div class='reply-to'>
{{{model.actionTitle}}}
{{#unless site.mobileView}}
{{#if whisperOrUnlistTopicText}}
<span class='whisper'>({{whisperOrUnlistTopicText}})</span>
{{/if}}
{{/if}}
{{/unless}}
{{composer-title composer=model lastValidatedAt=lastValidatedAt focusTarget=focusTarget}}
{{#if model.showCategoryChooser}}
<div class="category-input">
{{category-chooser valueAttribute="id" value=model.categoryId scopedCategoryId=scopedCategoryId tabindex="3"}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{#if model.archetype.hasOptions}}
<button class='btn' {{action "showOptions"}}>{{i18n 'topic.options'}}</button>
{{#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}}
{{render "additional-composer-buttons" model}}
</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 valueAttribute="id" value=model.categoryId scopedCategoryId=scopedCategoryId tabindex="3"}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{#if model.archetype.hasOptions}}
<button class='btn' {{action "showOptions"}}>{{i18n 'topic.options'}}</button>
{{/if}}
{{render "additional-composer-buttons" model}}
{{/if}}
</div>
{{/if}}
{{plugin-outlet "composer-fields"}}
</div>
{{/if}}
{{plugin-outlet "composer-fields"}}
</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"}}
{{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"}}
{{#if currentUser}}
<div class='submit-panel'>
{{plugin-outlet "composer-fields-below"}}
{{#if canEditTags}}
{{tag-chooser tags=model.tags tabIndex="4" categoryId=model.categoryId}}
{{/if}}
<button {{action "save"}} tabindex="5" class="btn btn-primary create {{if disableSubmit 'disabled'}}" title="{{i18n 'composer.title'}}">{{{model.saveIcon}}}{{model.saveText}}</button>
<a href {{action "cancel"}} class='cancel' tabindex="6">{{i18n 'cancel'}}</a>
{{#if currentUser}}
<div class='submit-panel'>
{{plugin-outlet "composer-fields-below"}}
{{#if canEditTags}}
{{tag-chooser tags=model.tags tabIndex="4" categoryId=model.categoryId}}
{{/if}}
<button {{action "save"}} tabindex="5" class="btn btn-primary create {{if 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 whisperOrUnlistTopic}}
<span class='whisper'><i class='fa fa-eye-slash'></i></span>
{{/if}}
{{#if site.mobileView}}
{{#if whisperOrUnlistTopic}}
<span class='whisper'><i class='fa fa-eye-slash'></i></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'>
{{i18n 'composer.saved_draft'}}
</div>
</div>
</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'>
{{i18n 'composer.saved_draft'}}
</div>
</div>
</div>
{{/if}}
</div>
</div>
{{/if}}
</div>
</div>
{{/if}}
{{/composer-body}}

View File

@ -1,121 +0,0 @@
import afterTransition from 'discourse/lib/after-transition';
import positioningWorkaround from 'discourse/lib/safari-hacks';
import { headerHeight } from 'discourse/components/site-header';
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
import Composer from 'discourse/models/composer';
const ComposerView = Ember.View.extend({
_lastKeyTimeout: null,
elementId: 'reply-control',
classNameBindings: ['composer.creatingPrivateMessage:private-message',
'composeState',
'composer.loading',
'composer.canEditTitle:edit-title',
'composer.createdPost:created-post',
'composer.creatingTopic:topic',
'composer.whisper:composing-whisper'],
composer: Em.computed.alias('controller.model'),
@computed('composer.composeState')
composeState(composeState) {
return composeState || Composer.CLOSED;
},
movePanels(sizePx) {
$('#main-outlet').css('padding-bottom', sizePx);
// signal the progress bar it should move!
this.appEvents.trigger("composer:resized");
},
@observes('composeState', 'composer.action')
resize() {
Ember.run.scheduleOnce('afterRender', () => {
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);
}
});
},
keyUp() {
const controller = this.get('controller');
controller.checkReplyLength();
this.get('composer').typing();
const lastKeyUp = new Date();
this._lastKeyUp = lastKeyUp;
// One second from now, check to see if the last key was hit when
// we recorded it. If it was, the user paused typing.
Ember.run.cancel(this._lastKeyTimeout);
this._lastKeyTimeout = Ember.run.later(() => {
if (lastKeyUp !== this._lastKeyUp) { return; }
this.appEvents.trigger('composer:find-similar');
}, 1000);
},
keyDown(e) {
if (e.which === 27) {
this.get('controller').send('hitEsc');
this.get('controller').send('hideOptions');
return false;
} else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
// CTRL+ENTER or CMD+ENTER
this.get('controller').send('save');
return false;
}
},
@on('didInsertElement')
_enableResizing() {
const $replyControl = $('#reply-control');
const resize = () => Ember.run(() => this.resize());
$replyControl.DivResizer({
resize,
maxHeight: winHeight => winHeight - headerHeight(),
onDrag: sizePx => this.movePanels(sizePx)
});
const triggerOpen = () => {
if (this.get('composer.composeState') === Composer.OPEN) {
this.appEvents.trigger('composer:opened');
}
};
triggerOpen();
afterTransition($replyControl, () => {
resize();
triggerOpen();
});
positioningWorkaround(this.$());
this.appEvents.on('composer:resize', this, this.resize);
},
willDestroyElement() {
this._super();
this.appEvents.off('composer:resize', this, this.resize);
},
click() {
this.get('controller').send('openIfDraft');
}
});
RSVP.EventTarget.mixin(ComposerView);
export default ComposerView;