mirror of
https://github.com/flarum/core.git
synced 2025-05-06 07:25:22 +02:00
Improve post composer + replying
- Make it modular so that different entry points can show different things and respond differently (reply, new discussion, edit post) - Resizable - Fullscreen - Confirm on close
This commit is contained in:
parent
6568d7d269
commit
12b0418eb7
@ -8,6 +8,7 @@ app.import('bower_components/bootstrap/dist/js/bootstrap.js');
|
|||||||
app.import('bower_components/spin.js/spin.js');
|
app.import('bower_components/spin.js/spin.js');
|
||||||
app.import('bower_components/spin.js/jquery.spin.js');
|
app.import('bower_components/spin.js/jquery.spin.js');
|
||||||
app.import('bower_components/moment/moment.js');
|
app.import('bower_components/moment/moment.js');
|
||||||
|
app.import('bower_components/jquery.hotkeys/jquery.hotkeys.js');
|
||||||
|
|
||||||
app.import('bower_components/font-awesome/fonts/fontawesome-webfont.eot');
|
app.import('bower_components/font-awesome/fonts/fontawesome-webfont.eot');
|
||||||
app.import('bower_components/font-awesome/fonts/fontawesome-webfont.svg');
|
app.import('bower_components/font-awesome/fonts/fontawesome-webfont.svg');
|
||||||
|
43
ember/app/components/discussions/composer-reply.js
Normal file
43
ember/app/components/discussions/composer-reply.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
import TaggedArray from '../../utils/tagged-array';
|
||||||
|
|
||||||
|
var precompileTemplate = Ember.Handlebars.compile;
|
||||||
|
|
||||||
|
export default Ember.Component.extend(Ember.Evented, {
|
||||||
|
layoutName: 'components/discussions/composer-body',
|
||||||
|
|
||||||
|
placeholder: 'Write your reply...',
|
||||||
|
submitLabel: 'Post Reply',
|
||||||
|
value: '',
|
||||||
|
|
||||||
|
didInsertElement: function() {
|
||||||
|
var headerItems = TaggedArray.create();
|
||||||
|
this.trigger('populateHeader', headerItems);
|
||||||
|
this.set('headerItems', headerItems);
|
||||||
|
},
|
||||||
|
|
||||||
|
populateHeader: function(header) {
|
||||||
|
var title = Ember.Component.create({
|
||||||
|
tagName: 'h3',
|
||||||
|
layout: precompileTemplate('Replying to <em>{{component.discussion.title}}</em>'),
|
||||||
|
component: this
|
||||||
|
});
|
||||||
|
header.pushObjectWithTag(title, 'title');
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
submit: function(value) {
|
||||||
|
this.get('submit').call(this, value);
|
||||||
|
},
|
||||||
|
willExit: function(abort) {
|
||||||
|
if (this.get('value') && ! confirm('You have not posted your reply. Do you wish to discard it?')) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset: function() {
|
||||||
|
this.set('loading', false);
|
||||||
|
this.set('value', '');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
@ -1,9 +1,37 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
import TaggedArray from '../../../utils/tagged-array';
|
||||||
|
import ActionButton from './action-button';
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
actions: {
|
classNames: ['text-editor'],
|
||||||
save: function() {
|
|
||||||
this.sendAction('save', this.get('value'));
|
didInsertElement: function() {
|
||||||
}
|
var controlItems = TaggedArray.create();
|
||||||
}
|
this.trigger('populateControls', controlItems);
|
||||||
|
this.set('controlItems', controlItems);
|
||||||
|
|
||||||
|
var component = this;
|
||||||
|
this.$('textarea').bind('keydown', 'meta+return', function() {
|
||||||
|
component.send('submit');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
populateControls: function(controls) {
|
||||||
|
var component = this;
|
||||||
|
var submit = ActionButton.create({
|
||||||
|
label: this.get('submitLabel'),
|
||||||
|
className: 'btn btn-primary',
|
||||||
|
action: function() {
|
||||||
|
component.send('submit');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.pushObjectWithTag(submit, 'submit');
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
submit: function() {
|
||||||
|
this.sendAction('submit', this.get('value'));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
@ -1,41 +1,72 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
export default Ember.Controller.extend(Ember.Evented, {
|
||||||
|
|
||||||
needs: ['index', 'application'],
|
needs: ['index', 'application'],
|
||||||
|
|
||||||
user: Ember.Object.create({avatarNumber: 1}),
|
content: null,
|
||||||
|
|
||||||
discussion: null,
|
showing: false,
|
||||||
|
|
||||||
showing: true,
|
|
||||||
minimized: false,
|
minimized: false,
|
||||||
|
fullScreen: false,
|
||||||
|
|
||||||
title: 'Replying to <em>Some Discussion Title</em>',
|
switchContent: function(newContent) {
|
||||||
|
var composer = this;
|
||||||
|
this.confirmExit().then(function() {
|
||||||
|
composer.set('content', null);
|
||||||
|
Ember.run.next(function() {
|
||||||
|
composer.set('content', newContent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmExit: function() {
|
||||||
|
var composer = this;
|
||||||
|
var promise = new Ember.RSVP.Promise(function(resolve, reject) {
|
||||||
|
var content = composer.get('content');
|
||||||
|
if (content) {
|
||||||
|
content.send('willExit', reject);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
close: function() {
|
close: function() {
|
||||||
|
var composer = this;
|
||||||
|
this.confirmExit().then(function() {
|
||||||
|
composer.send('hide');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hide: function() {
|
||||||
this.set('showing', false);
|
this.set('showing', false);
|
||||||
|
this.set('fullScreen', false);
|
||||||
|
var content = this.get('content');
|
||||||
|
if (content) {
|
||||||
|
content.send('reset');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
minimize: function() {
|
minimize: function() {
|
||||||
this.set('minimized', true);
|
this.set('minimized', true);
|
||||||
|
this.set('fullScreen', false);
|
||||||
},
|
},
|
||||||
show: function() {
|
show: function() {
|
||||||
this.set('minimized', false);
|
var composer = this;
|
||||||
|
Ember.run.next(function() {
|
||||||
|
composer.set('showing', true);
|
||||||
|
composer.set('minimized', false);
|
||||||
|
composer.trigger('focus');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
save: function(value) {
|
fullScreen: function() {
|
||||||
var store = this.store;
|
this.set('fullScreen', true);
|
||||||
var discussion = this.get('discussion');
|
this.set('minimized', false);
|
||||||
var controller = this;
|
this.trigger('focus');
|
||||||
|
},
|
||||||
var post = store.createRecord('post', {
|
exitFullScreen: function() {
|
||||||
content: value,
|
this.set('fullScreen', false);
|
||||||
discussion: discussion
|
this.trigger('focus');
|
||||||
});
|
|
||||||
post.save().then(function(post) {
|
|
||||||
discussion.set('posts', discussion.get('posts')+','+post.get('id'));
|
|
||||||
controller.get('delegate').send('replyAdded', post);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
import PostStream from '../models/post-stream';
|
import PostStream from '../models/post-stream';
|
||||||
|
import ComposerReply from '../components/discussions/composer-reply';
|
||||||
|
import ActionButton from '../components/ui/controls/action-button';
|
||||||
|
|
||||||
export default Ember.ObjectController.extend(Ember.Evented, {
|
export default Ember.ObjectController.extend(Ember.Evented, {
|
||||||
|
|
||||||
@ -38,28 +40,52 @@ export default Ember.ObjectController.extend(Ember.Evented, {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveReply: function(discussion, content) {
|
||||||
|
var controller = this;
|
||||||
|
var composer = this.get('controllers.composer');
|
||||||
|
var stream = this.get('stream');
|
||||||
|
|
||||||
|
composer.set('content.loading', true);
|
||||||
|
|
||||||
|
var post = this.store.createRecord('post', {
|
||||||
|
content: content,
|
||||||
|
discussion: discussion
|
||||||
|
});
|
||||||
|
|
||||||
|
var promise = post.save().then(function(post) {
|
||||||
|
if (discussion == controller.get('model')) {
|
||||||
|
discussion.set('posts', discussion.get('posts')+','+post.get('id'));
|
||||||
|
stream.set('ids', controller.get('model.postIds'));
|
||||||
|
stream.addPostToEnd(post);
|
||||||
|
}
|
||||||
|
composer.send('hide');
|
||||||
|
}, function(reason) {
|
||||||
|
var error = reason.errors[0].detail;
|
||||||
|
alert(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.finally(function() {
|
||||||
|
composer.set('content.loading', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
reply: function() {
|
reply: function() {
|
||||||
|
var controller = this;
|
||||||
|
var discussion = this.get('model');
|
||||||
var composer = this.get('controllers.composer');
|
var composer = this.get('controllers.composer');
|
||||||
// composer.beginPropertyChanges();
|
if (composer.get('content.discussion') != discussion) {
|
||||||
composer.set('minimized', false);
|
composer.switchContent(ComposerReply.create({
|
||||||
composer.set('showing', true);
|
user: controller.get('session.user'),
|
||||||
composer.set('title', 'Replying to <em>'+this.get('model.title')+'</em>');
|
discussion: discussion,
|
||||||
composer.set('delegate', this);
|
submit: function(value) {
|
||||||
composer.set('discussion', this.get('model'));
|
controller.saveReply(this.get('discussion'), value);
|
||||||
// composer.endPropertyChanges();
|
}
|
||||||
},
|
}));
|
||||||
|
}
|
||||||
replyAdded: function(post) {
|
composer.send('show');
|
||||||
var stream = this.get('stream');
|
|
||||||
stream.set('ids', this.get('model.postIds'));
|
|
||||||
var index = stream.get('count') - 1;
|
|
||||||
stream.get('content').pushObject(Ember.Object.create({
|
|
||||||
indexStart: index,
|
|
||||||
indexEnd: index,
|
|
||||||
content: post
|
|
||||||
}));
|
|
||||||
this.get('controllers.composer').set('showing', false);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// This action is called when the start position of the discussion
|
// This action is called when the start position of the discussion
|
||||||
|
@ -50,12 +50,15 @@ export default Ember.Route.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
willTransition: function() {
|
willTransition: function() {
|
||||||
// When we transition away from this discussion, we want to hide
|
// When we transition into a new discussion, we want to hide the
|
||||||
// the discussions list pane. This means that when the user
|
// discussions list pane. This means that when the user selects a
|
||||||
// selects a different discussion within the pane, the pane will
|
// different discussion within the pane, the pane will slide away.
|
||||||
// slide away.
|
|
||||||
this.controllerFor('index').set('paneShowing', false);
|
this.controllerFor('index').set('paneShowing', false);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
didTransition: function() {
|
||||||
|
this.controllerFor('composer').send('minimize');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,12 @@ export default Ember.Route.extend(AddCssClassToBodyMixin, {
|
|||||||
this.controllerFor('index').set('paned', false);
|
this.controllerFor('index').set('paned', false);
|
||||||
this.controllerFor('index').set('paneShowing', false);
|
this.controllerFor('index').set('paneShowing', false);
|
||||||
this._super(controller, model);
|
this._super(controller, model);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
didTransition: function() {
|
||||||
|
// @todo only if it's not a new discussion
|
||||||
|
this.controllerFor('composer').send('minimize');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -19,23 +19,33 @@
|
|||||||
margin-left: -20px;
|
margin-left: -20px;
|
||||||
margin-right: 180px;
|
margin-right: 180px;
|
||||||
.box-shadow(0 2px 6px rgba(0, 0, 0, 0.25));
|
.box-shadow(0 2px 6px rgba(0, 0, 0, 0.25));
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: @border-radius-base @border-radius-base 0 0;
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
transform: translateZ(0); // Fix for Chrome bug where a transparent white background is actually gray
|
transform: translateZ(0); // Fix for Chrome bug where a transparent white background is actually gray
|
||||||
position: relative;
|
position: relative;
|
||||||
.transition(~"margin-left 0.2s, margin-right 0.2s, background 0.2s");
|
height: 300px;
|
||||||
|
.transition(~"background 0.2s");
|
||||||
|
|
||||||
.index-index & {
|
.index-index & {
|
||||||
margin-left: 205px;
|
margin-left: 205px;
|
||||||
margin-right: -20px;
|
margin-right: -20px;
|
||||||
}
|
}
|
||||||
&.active {
|
&.active, &.fullscreen {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
&.minimized {
|
&.minimized {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
&.fullscreen {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.composer-content {
|
.composer-content {
|
||||||
padding: 20px 20px 15px;
|
padding: 20px 20px 15px;
|
||||||
@ -43,14 +53,18 @@
|
|||||||
.minimized & {
|
.minimized & {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
}
|
}
|
||||||
|
.fullscreen & {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.composer-handle {
|
.composer-handle {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-bottom: -20px;
|
margin-bottom: -20px;
|
||||||
cursor: row-resize;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.minimized & {
|
.minimized &, .fullscreen & {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,6 +72,16 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
& li {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn-minimize .fa {
|
||||||
|
vertical-align: -5px;
|
||||||
}
|
}
|
||||||
.composer-avatar {
|
.composer-avatar {
|
||||||
float: left;
|
float: left;
|
||||||
@ -82,12 +106,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.composer-editor {
|
.composer-editor {
|
||||||
|
.minimized & {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.text-editor {
|
||||||
& textarea {
|
& textarea {
|
||||||
background: none;
|
background: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 10px;
|
|
||||||
height: 200px;
|
|
||||||
border: 0;
|
border: 0;
|
||||||
resize: none;
|
resize: none;
|
||||||
color: @fl-body-color;
|
color: @fl-body-color;
|
||||||
@ -98,8 +125,30 @@
|
|||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.text-editor-controls {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
& li {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.minimized & {
|
.composer-loading {
|
||||||
visibility: hidden;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: @border-radius-base @border-radius-base 0 0;
|
||||||
|
.transition(opacity 0.2s);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
11
ember/app/templates/components/discussions/composer-body.hbs
Normal file
11
ember/app/templates/components/discussions/composer-body.hbs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{{user-avatar user class="composer-avatar"}}
|
||||||
|
|
||||||
|
<div class="composer-body">
|
||||||
|
{{ui/controls/item-list items=headerItems class="composer-header list-inline"}}
|
||||||
|
|
||||||
|
<div class="composer-editor">
|
||||||
|
{{ui/controls/text-editor submit="submit" value=value placeholder=placeholder submitLabel=submitLabel}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ui/controls/loading-indicator classNameBindings=":composer-loading loading:active"}}
|
@ -1,9 +1,3 @@
|
|||||||
{{textarea value=value placeholder=placeholder class="form-control"}}
|
{{textarea value=value placeholder=placeholder class="form-control"}}
|
||||||
|
|
||||||
<div class="composer-editor-controls">
|
{{ui/controls/item-list items=controlItems class="text-editor-controls"}}
|
||||||
<button class="btn btn-primary" {{action "save"}}>Submit Reply</button>
|
|
||||||
<div class="btn-group">
|
|
||||||
<button class="btn btn-default btn-icon"><i class="fa fa-fw fa-image"></i></button>
|
|
||||||
<button class="btn btn-default btn-icon"><i class="fa fa-fw fa-paperclip"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
@ -1,31 +1,9 @@
|
|||||||
<div class="composer-handle"></div>
|
<div class="composer-handle"></div>
|
||||||
|
|
||||||
<div class="composer-controls btn-group">
|
{{ui/controls/item-list items=view.controlItems class="composer-controls"}}
|
||||||
{{#unless minimized}}
|
|
||||||
<div class="btn-group dropdown">
|
|
||||||
<a href="#" {{action "fullScreen"}} class="btn btn-icon btn-link dropdown-toggle" data-toggle="dropdown">{{fa-icon "ellipsis-v"}}</a>
|
|
||||||
<ul class="dropdown-menu pull-right">
|
|
||||||
<li><a href="#">{{fa-icon "expand"}} Full Screen</a></li>
|
|
||||||
<li><a href="#">{{fa-icon "external-link"}} Pop-Out</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<a href="#" {{action "minimize"}} class="btn btn-icon btn-link">{{fa-icon "chevron-down"}}</a>
|
|
||||||
{{/unless}}
|
|
||||||
<a href="#" {{action "close"}} class="btn btn-icon btn-link">{{fa-icon "times"}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="composer-content">
|
<div class="composer-content">
|
||||||
|
{{#if content}}
|
||||||
{{user-avatar user class="composer-avatar"}}
|
{{view content}}
|
||||||
|
{{/if}}
|
||||||
<div class="composer-body">
|
|
||||||
|
|
||||||
<h3>{{{title}}}</h3>
|
|
||||||
|
|
||||||
<div class="composer-editor">
|
|
||||||
{{ui/controls/text-editor placeholder="" save="save"}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
@ -1,10 +1,39 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
export default Ember.View.extend({
|
import ActionButton from '../components/ui/controls/action-button';
|
||||||
|
import TaggedArray from '../utils/tagged-array';
|
||||||
|
|
||||||
|
export default Ember.View.extend(Ember.Evented, {
|
||||||
|
|
||||||
classNames: ['composer'],
|
classNames: ['composer'],
|
||||||
|
|
||||||
classNameBindings: ['controller.showing:showing', 'controller.minimized:minimized', 'active'],
|
classNameBindings: [
|
||||||
|
'controller.showing:showing',
|
||||||
|
'controller.minimized:minimized',
|
||||||
|
'controller.fullScreen:fullscreen',
|
||||||
|
'active'
|
||||||
|
],
|
||||||
|
|
||||||
|
computedHeight: function() {
|
||||||
|
if (this.get('controller.minimized') || this.get('controller.fullScreen')) {
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
return Math.max(200, Math.min(this.get('height'), $(window).height() - $('#header').outerHeight()));
|
||||||
|
}
|
||||||
|
}.property('height', 'controller.minimized', 'controller.fullScreen'),
|
||||||
|
|
||||||
|
updateHeight: function() {
|
||||||
|
if (! this.$()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var view = this;
|
||||||
|
Ember.run.scheduleOnce('afterRender', function() {
|
||||||
|
view.$().height(view.get('computedHeight'));
|
||||||
|
view.updateTextareaHeight();
|
||||||
|
// view.updateBottomPadding();
|
||||||
|
});
|
||||||
|
}.observes('computedHeight'),
|
||||||
|
|
||||||
showingChanged: function() {
|
showingChanged: function() {
|
||||||
if (! this.$()) {
|
if (! this.$()) {
|
||||||
@ -12,14 +41,19 @@ export default Ember.View.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
var view = this;
|
var view = this;
|
||||||
|
if (view.get('controller.showing')) {
|
||||||
|
view.$().show();
|
||||||
|
}
|
||||||
Ember.run.scheduleOnce('afterRender', function() {
|
Ember.run.scheduleOnce('afterRender', function() {
|
||||||
view.$().css('bottom', view.get('controller.showing') ? -view.$().outerHeight() : 0);
|
view.$().css('bottom', view.get('controller.showing') ? -view.$().outerHeight() : 0);
|
||||||
view.$().animate({bottom: view.get('controller.showing') ? 0 : -view.$().outerHeight()}, 'fast', function() {
|
view.$().animate({bottom: view.get('controller.showing') ? 0 : -view.$().outerHeight()}, 'fast', function() {
|
||||||
if (view.get('controller.showing')) {
|
if (view.get('controller.showing')) {
|
||||||
Ember.$(this).find('textarea').focus();
|
view.focus();
|
||||||
|
} else {
|
||||||
|
view.$().hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
view.updateBottomPadding();
|
view.updateBottomPadding(true);
|
||||||
});
|
});
|
||||||
}.observes('controller.showing'),
|
}.observes('controller.showing'),
|
||||||
|
|
||||||
@ -32,19 +66,28 @@ export default Ember.View.extend({
|
|||||||
var oldHeight = this.$().height();
|
var oldHeight = this.$().height();
|
||||||
Ember.run.scheduleOnce('afterRender', function() {
|
Ember.run.scheduleOnce('afterRender', function() {
|
||||||
var newHeight = view.$().height();
|
var newHeight = view.$().height();
|
||||||
view.updateBottomPadding();
|
view.updateBottomPadding(true);
|
||||||
view.$().css('height', oldHeight).animate({height: newHeight}, 'fast', function() {
|
view.$().css('height', oldHeight).animate({height: newHeight}, 'fast', function() {
|
||||||
view.$().css('height', '');
|
|
||||||
if (! view.get('controller.minimized')) {
|
if (! view.get('controller.minimized')) {
|
||||||
view.$('textarea').focus();
|
view.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}.observes('controller.minimized'),
|
}.observes('controller.minimized'),
|
||||||
|
|
||||||
|
fullScreenChanged: function() {
|
||||||
|
if (! this.$()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var view = this;
|
||||||
|
Ember.run.scheduleOnce('afterRender', function() {
|
||||||
|
view.updateTextareaHeight();
|
||||||
|
});
|
||||||
|
}.observes('controller.fullScreen'),
|
||||||
|
|
||||||
didInsertElement: function() {
|
didInsertElement: function() {
|
||||||
this.showingChanged();
|
this.$().hide();
|
||||||
this.minimizedChanged();
|
|
||||||
|
|
||||||
var controller = this.get('controller');
|
var controller = this.get('controller');
|
||||||
this.$('.composer-content').click(function() {
|
this.$('.composer-content').click(function() {
|
||||||
@ -54,15 +97,148 @@ export default Ember.View.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
var view = this;
|
var view = this;
|
||||||
this.$('textarea').focus(function() {
|
this.$().on('focus', ':input', function() {
|
||||||
view.set('active', true);
|
view.set('active', true);
|
||||||
}).blur(function() {
|
}).on('blur', ':input', function() {
|
||||||
view.set('active', false);
|
view.set('active', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.set('height', this.$().height());
|
||||||
|
|
||||||
|
controller.on('focus', this, this.focus);
|
||||||
|
|
||||||
|
$(window).on('resize', {view: this}, this.windowWasResized).resize();
|
||||||
|
|
||||||
|
var dragData = {view: this};
|
||||||
|
this.$('.composer-handle').css('cursor', 'row-resize')
|
||||||
|
.mousedown(function(e) {
|
||||||
|
dragData.mouseStart = e.clientY;
|
||||||
|
dragData.heightStart = view.$().height();
|
||||||
|
dragData.handle = $(this);
|
||||||
|
$('body').css('cursor', 'row-resize');
|
||||||
|
}).bind('dragstart mousedown', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document)
|
||||||
|
.on('mousemove', dragData, this.mouseWasMoved)
|
||||||
|
.on('mouseup', dragData, this.mouseWasReleased);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateBottomPadding: function() {
|
willDestroyElement: function() {
|
||||||
Ember.$('#main').animate({paddingBottom: this.get('controller.showing') ? this.$().outerHeight() - Ember.$('#footer').outerHeight(true) : 0}, 'fast');
|
$(window)
|
||||||
|
.off('resize', this.windowWasResized);
|
||||||
|
|
||||||
|
$(document)
|
||||||
|
.off('mousemove', this.mouseWasMoved)
|
||||||
|
.off('mouseup', this.mouseWasReleased);
|
||||||
|
},
|
||||||
|
|
||||||
|
windowWasResized: function(event) {
|
||||||
|
var view = event.data.view;
|
||||||
|
view.notifyPropertyChange('computedHeight');
|
||||||
|
},
|
||||||
|
|
||||||
|
mouseWasMoved: function(event) {
|
||||||
|
if (! event.data.handle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var view = event.data.view;
|
||||||
|
|
||||||
|
var deltaPixels = event.data.mouseStart - event.clientY;
|
||||||
|
view.set('height', event.data.heightStart + deltaPixels);
|
||||||
|
view.updateTextareaHeight();
|
||||||
|
view.updateBottomPadding();
|
||||||
|
},
|
||||||
|
|
||||||
|
mouseWasReleased: function(event) {
|
||||||
|
if (! event.data.handle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.data.handle = null;
|
||||||
|
$('body').css('cursor', '');
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshControls: function() {
|
||||||
|
var controlItems = TaggedArray.create();
|
||||||
|
this.trigger('populateControls', controlItems);
|
||||||
|
this.set('controlItems', controlItems);
|
||||||
|
}.observes('controller.minimized', 'controller.fullScreen'),
|
||||||
|
|
||||||
|
populateControls: function(controls) {
|
||||||
|
var controller = this.get('controller');
|
||||||
|
|
||||||
|
if (controller.get('fullScreen')) {
|
||||||
|
var exit = ActionButton.create({
|
||||||
|
icon: 'compress',
|
||||||
|
title: 'Exit Full Screen',
|
||||||
|
className: 'btn btn-icon btn-link',
|
||||||
|
action: function() {
|
||||||
|
controller.send('exitFullScreen');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.pushObjectWithTag(exit, 'exit');
|
||||||
|
} else {
|
||||||
|
if (! controller.get('minimized')) {
|
||||||
|
var minimize = ActionButton.create({
|
||||||
|
icon: 'minus',
|
||||||
|
title: 'Minimize',
|
||||||
|
className: 'btn btn-icon btn-link btn-minimize',
|
||||||
|
action: function() {
|
||||||
|
controller.send('minimize');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.pushObjectWithTag(minimize, 'minimize');
|
||||||
|
|
||||||
|
var fullScreen = ActionButton.create({
|
||||||
|
icon: 'expand',
|
||||||
|
title: 'Full Screen',
|
||||||
|
className: 'btn btn-icon btn-link',
|
||||||
|
action: function() {
|
||||||
|
controller.send('fullScreen');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.pushObjectWithTag(fullScreen, 'fullScreen');
|
||||||
|
}
|
||||||
|
|
||||||
|
var close = ActionButton.create({
|
||||||
|
icon: 'times',
|
||||||
|
title: 'Close',
|
||||||
|
className: 'btn btn-icon btn-link',
|
||||||
|
action: function() {
|
||||||
|
controller.send('close');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.pushObjectWithTag(close, 'close');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
focus: function() {
|
||||||
|
this.$().find(':input:enabled:visible:first').focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateBottomPadding: function(animate) {
|
||||||
|
var top = $(document).height() - $(window).height();
|
||||||
|
var isBottom = $(window).scrollTop() >= top;
|
||||||
|
|
||||||
|
$('#main')[animate ? 'animate' : 'css']({paddingBottom: this.get('controller.showing') ? this.$().outerHeight() - Ember.$('#footer').outerHeight(true) : 0}, 'fast');
|
||||||
|
|
||||||
|
if (isBottom) {
|
||||||
|
if (animate) {
|
||||||
|
$('html, body').animate({scrollTop: $(document).height()}, 'fast');
|
||||||
|
} else {
|
||||||
|
$('html, body').scrollTop($(document).height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTextareaHeight: function() {
|
||||||
|
var content = this.$('.composer-content');
|
||||||
|
this.$('textarea').height((this.get('computedHeight') || this.$().height())
|
||||||
|
- parseInt(content.css('padding-top'))
|
||||||
|
- parseInt(content.css('padding-bottom'))
|
||||||
|
- this.$('.composer-header').outerHeight(true)
|
||||||
|
- this.$('.text-editor-controls').outerHeight(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"spin.js": "~2.0.1",
|
"spin.js": "~2.0.1",
|
||||||
"pace": "~0.7.1",
|
"pace": "~0.7.1",
|
||||||
"moment": "~2.8.4",
|
"moment": "~2.8.4",
|
||||||
"ember-simple-auth": "0.7.2"
|
"ember-simple-auth": "0.7.2",
|
||||||
|
"jquery.hotkeys": "jeresig/jquery.hotkeys#0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user