1
0
mirror of https://github.com/flarum/core.git synced 2025-05-13 19:06:07 +02:00

Drastically improve how the composer looks and behaves

- New, cleaner, more prominent look
- Make it statically positioned down the bottom on mobile, so you can
still scroll up to look at posts
- Fix some bugs with animation, jumping between views
This commit is contained in:
Toby Zerner 2015-05-18 10:40:14 +09:30
parent e6362a222e
commit aa2bc23039
7 changed files with 83 additions and 73 deletions

View File

@ -21,8 +21,8 @@ export default class ComposerBody extends Component {
}); });
} }
view() { view(className) {
return m('div', {config: this.element}, [ return m('div', {className, config: this.element}, [
avatar(this.props.user, {className: 'composer-avatar'}), avatar(this.props.user, {className: 'composer-avatar'}),
m('div.composer-body', [ m('div.composer-body', [
m('ul.composer-header', listItems(this.headerItems().toArray())), m('ul.composer-header', listItems(this.headerItems().toArray())),

View File

@ -10,6 +10,7 @@ import ActionButton from 'flarum/components/action-button';
*/ */
export default class ComposerDiscussion extends ComposerBody { export default class ComposerDiscussion extends ComposerBody {
constructor(props) { constructor(props) {
props.placeholder = props.placeholder || 'Write a post...';
props.submitLabel = props.submitLabel || 'Post Discussion'; props.submitLabel = props.submitLabel || 'Post Discussion';
props.confirmExit = props.confirmExit || 'You have not posted your discussion. Do you wish to discard it?'; props.confirmExit = props.confirmExit || 'You have not posted your discussion. Do you wish to discard it?';
props.titlePlaceholder = props.titlePlaceholder || 'Discussion Title'; props.titlePlaceholder = props.titlePlaceholder || 'Discussion Title';
@ -26,7 +27,7 @@ export default class ComposerDiscussion extends ComposerBody {
items.add('title', m('h3', m('input', { items.add('title', m('h3', m('input', {
className: 'form-control', className: 'form-control',
value: this.title(), value: this.title(),
onchange: m.withAttr('value', this.title), oninput: m.withAttr('value', this.title),
placeholder: this.props.titlePlaceholder, placeholder: this.props.titlePlaceholder,
disabled: !!this.props.disabled, disabled: !!this.props.disabled,
config: function(element, isInitialized) { config: function(element, isInitialized) {

View File

@ -22,7 +22,11 @@ export default class ComposerEdit extends ComposerBody {
var items = new ItemList(); var items = new ItemList();
var post = this.props.post; var post = this.props.post;
items.add('title', m('h3', ['Editing Post #'+post.number()+' in ', m('em', post.discussion().title())])); items.add('title', m('h3', [
'Editing ',
m('a', {href: app.route.discussion(post.discussion(), post.number()), config: m.route}, 'Post #'+post.number()),
' in ', post.discussion().title()
]));
return items; return items;
} }

View File

@ -2,19 +2,33 @@ import ItemList from 'flarum/utils/item-list';
import ComposerBody from 'flarum/components/composer-body'; import ComposerBody from 'flarum/components/composer-body';
import Alert from 'flarum/components/alert'; import Alert from 'flarum/components/alert';
import ActionButton from 'flarum/components/action-button'; import ActionButton from 'flarum/components/action-button';
import Composer from 'flarum/components/composer';
export default class ComposerReply extends ComposerBody { export default class ComposerReply extends ComposerBody {
constructor(props) { constructor(props) {
props.placeholder = props.placeholder || 'Write your reply...';
props.submitLabel = props.submitLabel || 'Post Reply'; props.submitLabel = props.submitLabel || 'Post Reply';
props.confirmExit = props.confirmExit || 'You have not posted your reply. Do you wish to discard it?'; props.confirmExit = props.confirmExit || 'You have not posted your reply. Do you wish to discard it?';
super(props); super(props);
} }
view() {
return super.view('composer-reply');
}
headerItems() { headerItems() {
var items = new ItemList(); var items = new ItemList();
items.add('title', m('h3', ['Replying to ', m('em', this.props.discussion.title())])); if (app.composer.position() === Composer.PositionEnum.MINIMIZED ||
// https://github.com/babel/babel/issues/1150
!app.current.discussion ||
app.current.discussion() !== this.props.discussion) {
items.add('title', m('h3', [
'Replying to ',
m('a', {href: app.route.discussion(this.props.discussion), config: m.route}, this.props.discussion.title())
]));
}
return items; return items;
} }

View File

@ -133,19 +133,16 @@ class Composer extends Component {
} }
render(anchorToBottom) { render(anchorToBottom) {
// @todo this function's logic could probably use some reworking. The
// following line is bad because it prevents focusing on the composer
// input when the composer is shown when it's already being shown
if (this.position() === this.oldPosition) { return; } if (this.position() === this.oldPosition) { return; }
var $composer = this.$(); var $composer = this.$().stop(true);
var oldHeight = $composer.is(':visible') ? $composer.outerHeight() : 0; var oldHeight = $composer.is(':visible') ? $composer.outerHeight() : 0;
if (this.position() !== Composer.PositionEnum.HIDDEN) { if (this.position() !== Composer.PositionEnum.HIDDEN) {
m.redraw(true); m.redraw(true);
} }
this.updateHeight(); this.$().height(this.computedHeight());
var newHeight = $composer.outerHeight(); var newHeight = $composer.outerHeight();
switch (this.position()) { switch (this.position()) {
@ -178,7 +175,10 @@ class Composer extends Component {
} }
$('body').toggleClass('composer-open', this.position() !== Composer.PositionEnum.HIDDEN); $('body').toggleClass('composer-open', this.position() !== Composer.PositionEnum.HIDDEN);
this.oldPosition = this.position(); this.oldPosition = this.position();
this.setContentHeight(this.computedHeight());
if (this.position() !== Composer.PositionEnum.HIDDEN) {
this.setContentHeight(this.computedHeight());
}
} }
// Update the amount of padding-bottom on the body so that the page's // Update the amount of padding-bottom on the body so that the page's
@ -203,12 +203,12 @@ class Composer extends Component {
// to fill up the height of the composer, minus the space taken up by the // to fill up the height of the composer, minus the space taken up by the
// composer's header/footer/etc. // composer's header/footer/etc.
setContentHeight(height) { setContentHeight(height) {
var content = this.$('.composer-content'); var flexible = this.$('.flexible-height');
this.$('.flexible-height').height(height - if (flexible.length) {
parseInt(content.css('padding-top')) - flexible.height(height -
parseInt(content.css('padding-bottom')) - (flexible.offset().top - this.$().offset().top) -
this.$('.composer-header').outerHeight(true) - this.$('.text-editor-controls').outerHeight(true));
this.$('.text-editor-controls').outerHeight(true)); }
} }
load(component) { load(component) {
@ -225,8 +225,7 @@ class Composer extends Component {
if ([Composer.PositionEnum.MINIMIZED, Composer.PositionEnum.HIDDEN].indexOf(this.position()) !== -1) { if ([Composer.PositionEnum.MINIMIZED, Composer.PositionEnum.HIDDEN].indexOf(this.position()) !== -1) {
this.position(Composer.PositionEnum.NORMAL); this.position(Composer.PositionEnum.NORMAL);
} }
// work around https://github.com/lhorie/mithril.js/issues/603 this.render(anchorToBottom);
setTimeout(() => this.render(anchorToBottom));
} }
hide() { hide() {
@ -276,7 +275,7 @@ class Composer extends Component {
items.add('minimize', this.control({ icon: 'minus minimize', title: 'Minimize', onclick: this.minimize.bind(this) })); items.add('minimize', this.control({ icon: 'minus minimize', title: 'Minimize', onclick: this.minimize.bind(this) }));
items.add('fullScreen', this.control({ icon: 'expand', title: 'Full Screen', onclick: this.fullScreen.bind(this) })); items.add('fullScreen', this.control({ icon: 'expand', title: 'Full Screen', onclick: this.fullScreen.bind(this) }));
} }
items.add('close', this.control({ icon: 'times', title: 'Close', wrapperClass: 'back-control', onclick: this.close.bind(this) })); items.add('close', this.control({ icon: 'times', title: 'Close', onclick: this.close.bind(this) }));
} }
return items; return items;

View File

@ -25,7 +25,7 @@ export default class TextEditor extends Component {
disabled: !!this.props.disabled, disabled: !!this.props.disabled,
value: this.value() value: this.value()
}), }),
m('ul.text-editor-controls.fade', listItems(this.controlItems().toArray())) m('ul.text-editor-controls', listItems(this.controlItems().toArray()))
]); ]);
} }
@ -43,7 +43,6 @@ export default class TextEditor extends Component {
label: this.props.submitLabel, label: this.props.submitLabel,
icon: 'check', icon: 'check',
className: 'btn btn-primary', className: 'btn btn-primary',
wrapperClass: 'primary-control',
onclick: this.onsubmit.bind(this) onclick: this.onsubmit.bind(this)
}) })
); );
@ -76,7 +75,6 @@ export default class TextEditor extends Component {
oninput(value) { oninput(value) {
this.value(value); this.value(value);
this.props.onchange(this.value()); this.props.onchange(this.value());
this.$('.text-editor-controls').toggleClass('in', !!value);
m.redraw.strategy('none'); m.redraw.strategy('none');
} }

View File

@ -23,10 +23,15 @@
margin: 0 0 10px; margin: 0 0 10px;
line-height: 1.5em; line-height: 1.5em;
&, & input { &, & input, & a {
color: @fl-body-muted-color; color: @fl-body-muted-color;
font-size: 16px; font-size: 15px;
font-weight: normal; font-weight: normal;
transition: color 0.2s;
.active & {
color: @fl-body-primary-color;
}
} }
& input, & input[disabled] { & input, & input[disabled] {
background: none; background: none;
@ -36,6 +41,19 @@
} }
} }
} }
.composer-controls {
position: absolute;
right: 10px;
top: 10px;
z-index: 1;
& li {
display: inline-block;
}
.minimized & {
top: 7px;
}
}
.composer-loading { .composer-loading {
position: absolute; position: absolute;
top: 0; top: 0;
@ -58,35 +76,16 @@
// screen. The controls are hidden (except for the 'x', which is the back- // screen. The controls are hidden (except for the 'x', which is the back-
// control), and the avatar hidden. // control), and the avatar hidden.
@media @phone { @media @phone {
.composer-open {
overflow: hidden;
}
.composer { .composer {
position: fixed; position: absolute;
top: 0;
bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
z-index: @zindex-composer;
background: @fl-body-bg;
height: 100vh !important;
padding-top: @mobile-header-height;
&:before {
content: " ";
.toolbar();
opacity: 0;
.visible& {
opacity: 1;
}
}
} }
.composer-content { .composer-content {
padding: 15px; padding: 15px 15px 0;
} }
.composer-controls { .composer-controls {
& li:not(.back-control) { & li:not(:last-child) {
display: none; display: none;
} }
} }
@ -117,11 +116,14 @@
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;
height: 300px; height: 300px;
.transition(~"background 0.2s"); .transition(~"background 0.2s, box-shadow 0.2s");
&.active, &.full-screen { &.active, &.full-screen {
background: @fl-body-bg; background: @fl-body-bg;
} }
&.active:not(.full-screen) {
box-shadow: 0 2px 6px @fl-shadow-color, 0 0 0 2px @fl-body-primary-color;
}
&.minimized { &.minimized {
height: 50px; height: 50px;
cursor: pointer; cursor: pointer;
@ -142,10 +144,10 @@
} }
} }
.composer-content { .composer-content {
padding: 20px 20px 15px; padding: 20px 20px 0;
.minimized & { .minimized & {
padding: 10px 20px; padding: 12px 20px;
} }
.full-screen & { .full-screen & {
max-width: 900px; max-width: 900px;
@ -154,26 +156,14 @@
} }
} }
.composer-handle { .composer-handle {
height: 20px; height: 15px;
margin-bottom: -20px; margin-bottom: -17px;
position: relative; position: relative;
.minimized &, .full-screen & { .minimized &, .full-screen & {
display: none; display: none;
} }
} }
.composer-controls {
position: absolute;
right: 10px;
top: 10px;
& li {
display: inline-block;
}
.minimized & {
top: 7px;
}
}
.fa-minus.minimize { .fa-minus.minimize {
vertical-align: -5px; vertical-align: -5px;
} }
@ -204,14 +194,13 @@
margin-left: -20px; margin-left: -20px;
margin-right: 180px; margin-right: 180px;
.index-page & { .index-page &:not(.full-screen) {
margin-left: 205px; margin-left: 205px;
margin-right: -20px; margin-right: -20px;
} }
} }
} }
// ------------------------------------ // ------------------------------------
// Text Editor // Text Editor
@ -223,7 +212,7 @@
resize: none; resize: none;
color: @fl-body-color; color: @fl-body-color;
font-size: 14px; font-size: 14px;
line-height: 1.6; line-height: 1.7;
&, &:focus, &[disabled] { &, &:focus, &[disabled] {
background: none; background: none;
@ -231,8 +220,8 @@
} }
} }
.text-editor-controls { .text-editor-controls {
margin: 10px 0 0; margin: 0;
padding: 0; padding: 15px 0;
list-style-type: none; list-style-type: none;
& li { & li {
@ -240,10 +229,15 @@
} }
} }
// On phones, since one of the text editor controls will probably be the @media @tablet, @desktop, @desktop-hd {
// primary-control, we shouldn't hide it completely when it's "disabled".
@media @phone {
.text-editor-controls { .text-editor-controls {
opacity: 0.5; margin: 0 -20px 0 -110px;
padding: 15px 20px;
border-top: 1px solid @fl-body-secondary-color;
& .btn-primary {
padding-left: 20px;
padding-right: 20px;
}
} }
} }