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:
parent
e6362a222e
commit
aa2bc23039
@ -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())),
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user