mirror of
https://github.com/flarum/core.git
synced 2025-07-25 02:31:17 +02:00
Work on composer, early implementation of replying
This commit is contained in:
9
ember/app/components/ui/controls/text-editor.js
Normal file
9
ember/app/components/ui/controls/text-editor.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
actions: {
|
||||||
|
save: function() {
|
||||||
|
this.sendAction('save', this.get('value'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
42
ember/app/controllers/composer.js
Normal file
42
ember/app/controllers/composer.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Controller.extend({
|
||||||
|
|
||||||
|
needs: ['index', 'application'],
|
||||||
|
|
||||||
|
user: Ember.Object.create({avatarNumber: 1}),
|
||||||
|
|
||||||
|
discussion: null,
|
||||||
|
|
||||||
|
showing: true,
|
||||||
|
minimized: false,
|
||||||
|
|
||||||
|
title: 'Replying to <em>Some Discussion Title</em>',
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
close: function() {
|
||||||
|
this.set('showing', false);
|
||||||
|
},
|
||||||
|
minimize: function() {
|
||||||
|
this.set('minimized', true);
|
||||||
|
},
|
||||||
|
show: function() {
|
||||||
|
this.set('minimized', false);
|
||||||
|
},
|
||||||
|
save: function(value) {
|
||||||
|
var store = this.store;
|
||||||
|
var discussion = this.get('discussion');
|
||||||
|
var controller = this;
|
||||||
|
|
||||||
|
var post = store.createRecord('post', {
|
||||||
|
content: value,
|
||||||
|
discussion: discussion
|
||||||
|
});
|
||||||
|
post.save().then(function(post) {
|
||||||
|
discussion.set('posts', discussion.get('posts')+','+post.get('id'));
|
||||||
|
controller.get('delegate').send('replyAdded', post);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
@@ -40,8 +40,26 @@ export default Ember.ObjectController.extend(Ember.Evented, {
|
|||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
reply: function() {
|
reply: function() {
|
||||||
this.set('controllers.composer.showing', true);
|
var composer = this.get('controllers.composer');
|
||||||
this.set('controllers.composer.title', 'Replying to <em>'+this.get('model.title')+'</em>');
|
// composer.beginPropertyChanges();
|
||||||
|
composer.set('minimized', false);
|
||||||
|
composer.set('showing', true);
|
||||||
|
composer.set('title', 'Replying to <em>'+this.get('model.title')+'</em>');
|
||||||
|
composer.set('delegate', this);
|
||||||
|
composer.set('discussion', this.get('model'));
|
||||||
|
// composer.endPropertyChanges();
|
||||||
|
},
|
||||||
|
|
||||||
|
replyAdded: function(post) {
|
||||||
|
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
|
||||||
|
16
ember/app/mixins/add-css-class-to-body.js
Normal file
16
ember/app/mixins/add-css-class-to-body.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Mixin.create({
|
||||||
|
activate: function() {
|
||||||
|
var cssClass = this.toCssClass();
|
||||||
|
Ember.$('body').addClass(cssClass);
|
||||||
|
},
|
||||||
|
|
||||||
|
deactivate: function() {
|
||||||
|
Ember.$('body').removeClass(this.toCssClass());
|
||||||
|
},
|
||||||
|
|
||||||
|
toCssClass: function() {
|
||||||
|
return this.routeName.replace(/\./g, '-').dasherize();
|
||||||
|
}
|
||||||
|
});
|
@@ -1,6 +1,8 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
export default Ember.Route.extend({
|
import AddCssClassToBodyMixin from '../../mixins/add-css-class-to-body';
|
||||||
|
|
||||||
|
export default Ember.Route.extend(AddCssClassToBodyMixin, {
|
||||||
|
|
||||||
// When we enter the discussions list view, we no longer want the
|
// When we enter the discussions list view, we no longer want the
|
||||||
// discussions list to be in pane mode.
|
// discussions list to be in pane mode.
|
||||||
|
@@ -8,33 +8,67 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
z-index: @zindex-navbar-fixed;
|
z-index: @zindex-navbar-fixed;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
.transition(left 0.2s);
|
||||||
|
|
||||||
|
.with-pane & {
|
||||||
|
left: @index-pane-width;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.composer {
|
.composer {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
margin-left: 200px;
|
margin-left: -20px;
|
||||||
|
margin-right: 200px;
|
||||||
.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: 4px 4px 0 0;
|
||||||
background: rgba(255, 255, 255, 0.98);
|
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;
|
||||||
|
.transition(~"margin-left 0.2s, margin-right 0.2s, background 0.2s");
|
||||||
|
|
||||||
|
.index-index & {
|
||||||
|
margin-left: 205px;
|
||||||
|
margin-right: -20px;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background: rgba(255, 255, 255, 0.98);
|
||||||
|
}
|
||||||
|
&.minimized {
|
||||||
|
height: 50px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.composer-content {
|
.composer-content {
|
||||||
padding: 0 20px 15px;
|
padding: 20px 20px 15px;
|
||||||
|
|
||||||
|
.minimized & {
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.composer-handle {
|
.composer-handle {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
margin-bottom: -20px;
|
||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.minimized & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.composer-controls {
|
.composer-controls {
|
||||||
float: right;
|
position: absolute;
|
||||||
margin: -5px 15px 0 0;
|
right: 10px;
|
||||||
|
top: 10px;
|
||||||
}
|
}
|
||||||
.composer-avatar {
|
.composer-avatar {
|
||||||
float: left;
|
float: left;
|
||||||
width: 64px;
|
.avatar-size(64px);
|
||||||
height: 64px;
|
|
||||||
|
.minimized & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.composer-body {
|
.composer-body {
|
||||||
margin-left: 84px;
|
margin-left: 90px;
|
||||||
|
|
||||||
& h3 {
|
& h3 {
|
||||||
margin: 5px 0 10px;
|
margin: 5px 0 10px;
|
||||||
@@ -42,20 +76,30 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.composer-editor textarea {
|
|
||||||
background: none;
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
height: 200px;
|
|
||||||
border: 0;
|
|
||||||
resize: none;
|
|
||||||
color: @fl-body-color;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.6;
|
|
||||||
|
|
||||||
&:focus {
|
.minimized & {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.composer-editor {
|
||||||
|
& textarea {
|
||||||
background: none;
|
background: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
height: 200px;
|
||||||
|
border: 0;
|
||||||
|
resize: none;
|
||||||
|
color: @fl-body-color;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimized & {
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
<div {{bind-attr class=":page panePinned:with-pane"}}>
|
<div id="page" {{bind-attr class=":page panePinned:with-pane"}}>
|
||||||
|
|
||||||
<header id="header" class="global-header">
|
<header id="header" class="global-header">
|
||||||
|
|
||||||
@@ -31,11 +31,11 @@
|
|||||||
<main id="main" class="global-main">
|
<main id="main" class="global-main">
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
|
|
||||||
{{!-- <div class="composer-container">
|
<div class="composer-container">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{{render "composer"}}
|
{{render "composer"}}
|
||||||
</div>
|
</div>
|
||||||
</div> --}}
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer id="footer" class="global-footer">
|
<footer id="footer" class="global-footer">
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<textarea class="form-control" {{bind-attr placeholder=placeholder}}></textarea>
|
{{textarea value=value placeholder=placeholder class="form-control"}}
|
||||||
|
|
||||||
<div class="composer-editor-controls">
|
<div class="composer-editor-controls">
|
||||||
<button class="btn btn-primary">Submit Reply</button>
|
<button class="btn btn-primary" {{action "save"}}>Submit Reply</button>
|
||||||
<div class="btn-group">
|
<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-image"></i></button>
|
||||||
<button class="btn btn-default btn-icon"><i class="fa fa-fw fa-paperclip"></i></button>
|
<button class="btn btn-default btn-icon"><i class="fa fa-fw fa-paperclip"></i></button>
|
||||||
|
@@ -1,14 +1,16 @@
|
|||||||
<div class="composer-handle"></div>
|
<div class="composer-handle"></div>
|
||||||
|
|
||||||
<div class="composer-controls btn-group">
|
<div class="composer-controls btn-group">
|
||||||
<div class="btn-group dropdown">
|
{{#unless minimized}}
|
||||||
<a href="#" {{action "fullScreen"}} class="btn btn-icon btn-link dropdown-toggle" data-toggle="dropdown">{{fa-icon "ellipsis-v"}}</a>
|
<div class="btn-group dropdown">
|
||||||
<ul class="dropdown-menu pull-right">
|
<a href="#" {{action "fullScreen"}} class="btn btn-icon btn-link dropdown-toggle" data-toggle="dropdown">{{fa-icon "ellipsis-v"}}</a>
|
||||||
<li><a href="#">{{fa-icon "expand"}} Full Screen</a></li>
|
<ul class="dropdown-menu pull-right">
|
||||||
<li><a href="#">{{fa-icon "external-link"}} Pop-Out</a></li>
|
<li><a href="#">{{fa-icon "expand"}} Full Screen</a></li>
|
||||||
</ul>
|
<li><a href="#">{{fa-icon "external-link"}} Pop-Out</a></li>
|
||||||
</div>
|
</ul>
|
||||||
<a href="#" {{action "hide"}} class="btn btn-icon btn-link">{{fa-icon "chevron-down"}}</a>
|
</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>
|
<a href="#" {{action "close"}} class="btn btn-icon btn-link">{{fa-icon "times"}}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@
|
|||||||
<h3>{{{title}}}</h3>
|
<h3>{{{title}}}</h3>
|
||||||
|
|
||||||
<div class="composer-editor">
|
<div class="composer-editor">
|
||||||
{{ui/controls/text-editor placeholder=""}}
|
{{ui/controls/text-editor placeholder="" save="save"}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,35 +4,65 @@ export default Ember.View.extend({
|
|||||||
|
|
||||||
classNames: ['composer'],
|
classNames: ['composer'],
|
||||||
|
|
||||||
// classNameBindings: ['controller.showing:showing'],
|
classNameBindings: ['controller.showing:showing', 'controller.minimized:minimized', 'active'],
|
||||||
|
|
||||||
// showingChanged: function() {
|
showingChanged: function() {
|
||||||
// if (this.$()) {
|
if (! this.$()) {
|
||||||
// var view = this;
|
return;
|
||||||
// this.$().animate({bottom: this.get('controller.showing') ? 20 : -this.$().height()}, 'fast', function() {
|
}
|
||||||
// if (view.get('controller.showing')) {
|
|
||||||
// $(this).find('textarea').focus();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// $('#body').animate({marginBottom: this.get('controller.showing') ? this.$().height() + 20 : 0}, 'fast');
|
|
||||||
// }
|
|
||||||
// }.observes('controller.showing'),
|
|
||||||
|
|
||||||
// panePinnedChanged: function() {
|
var view = this;
|
||||||
// if (this.$()) {
|
Ember.run.scheduleOnce('afterRender', function() {
|
||||||
// var discussions = this.get('controller.controllers.discussions');
|
view.$().css('bottom', view.get('controller.showing') ? -view.$().outerHeight() : 0);
|
||||||
// var $this = this.$();
|
view.$().animate({bottom: view.get('controller.showing') ? 0 : -view.$().outerHeight()}, 'fast', function() {
|
||||||
// Ember.run.scheduleOnce('afterRender', function() {
|
if (view.get('controller.showing')) {
|
||||||
// var discussion = $('.discussion-pane');
|
Ember.$(this).find('textarea').focus();
|
||||||
// var width = discussion.length ? discussion.offset().left : $('#body').offset().left;
|
}
|
||||||
// $this.css('left', width);
|
});
|
||||||
// });
|
view.updateBottomPadding();
|
||||||
// }
|
});
|
||||||
// }.observes('controller.controllers.discussions.paned', 'controller.controllers.discussions.panePinned'),
|
}.observes('controller.showing'),
|
||||||
|
|
||||||
|
minimizedChanged: function() {
|
||||||
|
if (! this.$() || ! this.get('controller.showing')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var view = this;
|
||||||
|
var oldHeight = this.$().height();
|
||||||
|
Ember.run.scheduleOnce('afterRender', function() {
|
||||||
|
var newHeight = view.$().height();
|
||||||
|
view.updateBottomPadding();
|
||||||
|
view.$().css('height', oldHeight).animate({height: newHeight}, 'fast', function() {
|
||||||
|
view.$().css('height', '');
|
||||||
|
if (! view.get('controller.minimized')) {
|
||||||
|
view.$('textarea').focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}.observes('controller.minimized'),
|
||||||
|
|
||||||
didInsertElement: function() {
|
didInsertElement: function() {
|
||||||
// this.showingChanged();
|
this.showingChanged();
|
||||||
// this.panePinnedChanged();
|
this.minimizedChanged();
|
||||||
|
|
||||||
|
var controller = this.get('controller');
|
||||||
|
this.$('.composer-content').click(function() {
|
||||||
|
if (controller.get('minimized')) {
|
||||||
|
controller.send('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var view = this;
|
||||||
|
this.$('textarea').focus(function() {
|
||||||
|
view.set('active', true);
|
||||||
|
}).blur(function() {
|
||||||
|
view.set('active', false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateBottomPadding: function() {
|
||||||
|
Ember.$('#main').animate({paddingBottom: this.get('controller.showing') ? this.$().outerHeight() - Ember.$('#footer').outerHeight(true) : 0}, 'fast');
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -89,20 +89,14 @@ export default Ember.View.extend(Ember.Evented, {
|
|||||||
|
|
||||||
populateControlsDefault: function(controls) {
|
populateControlsDefault: function(controls) {
|
||||||
var view = this;
|
var view = this;
|
||||||
var ReplyItem = ActionButton.extend({
|
var reply = ActionButton.create({
|
||||||
label: 'Reply',
|
label: 'Reply',
|
||||||
icon: 'reply',
|
icon: 'reply',
|
||||||
classNameBindings: ['className', 'replying:disabled'],
|
|
||||||
replying: function() {
|
|
||||||
return this.get('parentController.controllers.composer.showing');
|
|
||||||
}.property('parentController.controllers.composer.showing'),
|
|
||||||
action: function() {
|
action: function() {
|
||||||
var lastPost = $('.posts .item:last');
|
view.get('streamContent').send('goToIndex', view.get('controller.stream.count') - 1);
|
||||||
$('html, body').animate({scrollTop: lastPost.offset().top + lastPost.outerHeight() - $(window).height() + $('.composer').height() + 19}, 'fast');
|
|
||||||
view.get('controller').send('reply');
|
view.get('controller').send('reply');
|
||||||
},
|
},
|
||||||
parentController: this.get('controller'),
|
|
||||||
});
|
});
|
||||||
controls.pushObjectWithTag(ReplyItem.create(), 'reply');
|
controls.pushObjectWithTag(reply, 'reply');
|
||||||
}.on('populateControls')
|
}.on('populateControls')
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user