mirror of
https://github.com/flarum/framework.git
synced 2024-12-02 23:23:52 +08:00
Refactor Composer rendering for smoother animations
Also fixes a couple of miscellaneous bugs: - Minimise the Composer when clicking the preview button in full-screen mode on desktop. - Minimise the Composer when clicking the link to the discussion/post in the header on mobile/full-screen mode.
This commit is contained in:
parent
1cbab36fda
commit
3148f175cf
312
framework/core/js/forum/dist/app.js
vendored
312
framework/core/js/forum/dist/app.js
vendored
|
@ -20104,13 +20104,6 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
*/
|
||||
this.position = Composer.PositionEnum.HIDDEN;
|
||||
|
||||
/**
|
||||
* The composer's previous position.
|
||||
*
|
||||
* @type {Composer.PositionEnum}
|
||||
*/
|
||||
this.oldPosition = null;
|
||||
|
||||
/**
|
||||
* The composer's intended height, which can be modified by the user
|
||||
* (by dragging the composer handle).
|
||||
|
@ -20151,25 +20144,20 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
}, {
|
||||
key: 'view',
|
||||
value: function view() {
|
||||
var _this = this;
|
||||
|
||||
var classes = {
|
||||
'normal': this.position === Composer.PositionEnum.NORMAL,
|
||||
'minimized': this.position === Composer.PositionEnum.MINIMIZED,
|
||||
'fullScreen': this.position === Composer.PositionEnum.FULLSCREEN,
|
||||
'active': this.active
|
||||
};
|
||||
classes.visible = this.position === Composer.PositionEnum.NORMAL || classes.minimized || classes.fullScreen;
|
||||
classes.visible = classes.normal || classes.minimized || classes.fullScreen;
|
||||
|
||||
// If the composer is minimized, tell the composer's content component that
|
||||
// it shouldn't let the user interact with it. Set up a handler so that if
|
||||
// the content IS clicked, the composer will be shown.
|
||||
if (this.component) this.component.props.disabled = classes.minimized;
|
||||
|
||||
var showIfMinimized = function showIfMinimized() {
|
||||
if (_this.position === Composer.PositionEnum.MINIMIZED) _this.show();
|
||||
m.redraw.strategy('none');
|
||||
};
|
||||
var showIfMinimized = this.position === Composer.PositionEnum.MINIMIZED ? this.show.bind(this) : undefined;
|
||||
|
||||
return m(
|
||||
'div',
|
||||
|
@ -20190,7 +20178,7 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
}, {
|
||||
key: 'config',
|
||||
value: function config(isInitialized, context) {
|
||||
var _this2 = this;
|
||||
var _this = this;
|
||||
|
||||
var defaultHeight = undefined;
|
||||
|
||||
|
@ -20198,8 +20186,6 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
defaultHeight = this.$().height();
|
||||
}
|
||||
|
||||
this.updateHeight();
|
||||
|
||||
if (isInitialized) return;
|
||||
|
||||
// Since this component is a part of the global UI that persists between
|
||||
|
@ -20215,20 +20201,20 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
// Whenever any of the inputs inside the composer are have focus, we want to
|
||||
// add a class to the composer to draw attention to it.
|
||||
this.$().on('focus blur', ':input', function (e) {
|
||||
_this2.active = e.type === 'focusin';
|
||||
_this.active = e.type === 'focusin';
|
||||
m.redraw();
|
||||
});
|
||||
|
||||
// When the escape key is pressed on any inputs, close the composer.
|
||||
this.$().on('keydown', ':input', 'esc', function () {
|
||||
return _this2.close();
|
||||
return _this.close();
|
||||
});
|
||||
|
||||
// Don't let the user leave the page without first giving the composer's
|
||||
// component a chance to scream at the user to make sure they don't
|
||||
// unintentionally lose any contnet.
|
||||
window.onbeforeunload = function () {
|
||||
return _this2.component && _this2.component.preventExit() || undefined;
|
||||
return _this.component && _this.component.preventExit() || undefined;
|
||||
};
|
||||
|
||||
var handlers = {};
|
||||
|
@ -20317,17 +20303,15 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
}, {
|
||||
key: 'updateHeight',
|
||||
value: function updateHeight() {
|
||||
// TODO: update this in a way that is independent of the TextEditor being
|
||||
// present.
|
||||
var height = this.computedHeight();
|
||||
var $flexible = this.$('.TextEditor-flexible');
|
||||
var $flexible = this.$('.Composer-flexible');
|
||||
|
||||
this.$().height(height);
|
||||
|
||||
if ($flexible.length) {
|
||||
var headerHeight = $flexible.offset().top - this.$().offset().top;
|
||||
var paddingBottom = parseInt($flexible.css('padding-bottom'), 10);
|
||||
var footerHeight = this.$('.TextEditor-controls').outerHeight(true);
|
||||
var footerHeight = this.$('.Composer-footer').outerHeight(true);
|
||||
|
||||
$flexible.height(this.$().outerHeight() - headerHeight - paddingBottom - footerHeight);
|
||||
}
|
||||
|
@ -20344,97 +20328,23 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
var visible = this.position !== Composer.PositionEnum.HIDDEN && this.position !== Composer.PositionEnum.MINIMIZED && this.$().css('position') !== 'absolute';
|
||||
|
||||
var paddingBottom = visible ? this.computedHeight() - parseInt($('#app').css('padding-bottom'), 10) : 0;
|
||||
|
||||
$('#content').css({ paddingBottom: paddingBottom });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update (and animate) the DOM to reflect the composer's current state.
|
||||
* Determine whether or not the Composer is covering the screen.
|
||||
*
|
||||
* This will be true if the Composer is in full-screen mode on desktop, or
|
||||
* if the Composer is positioned absolutely as on mobile devices.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @public
|
||||
*/
|
||||
}, {
|
||||
key: 'update',
|
||||
value: function update() {
|
||||
var _this3 = this;
|
||||
|
||||
// Before we redraw the composer to its new state, we need to save the
|
||||
// current height of the composer, as well as the page's scroll position, so
|
||||
// that we can smoothly transition from the old to the new state.
|
||||
var $composer = this.$().stop(true);
|
||||
var oldHeight = $composer.is(':visible') ? $composer.outerHeight() : 0;
|
||||
var scrollTop = $(window).scrollTop();
|
||||
|
||||
m.redraw(true);
|
||||
|
||||
// Now that we've redrawn and the composer's DOM has been updated, we want
|
||||
// to update the composer's height. Once we've done that, we'll capture the
|
||||
// real value to use as the end point for our animation later on.
|
||||
$composer.show();
|
||||
this.updateHeight();
|
||||
|
||||
var newHeight = $composer.outerHeight();
|
||||
|
||||
switch (this.position) {
|
||||
case Composer.PositionEnum.NORMAL:
|
||||
// If the composer is being opened, we will make it visible and animate
|
||||
// it growing/sliding up from the bottom of the viewport. Or if the user
|
||||
// has just exited fullscreen mode, we will simply tell the content to
|
||||
// take focus.
|
||||
if (this.oldPosition !== Composer.PositionEnum.FULLSCREEN) {
|
||||
$composer.show().css({ height: oldHeight }).animate({ bottom: 0, height: newHeight }, 'fast', this.component.focus.bind(this.component));
|
||||
|
||||
if ($composer.css('position') === 'absolute') {
|
||||
$composer.css('top', $(window).scrollTop());
|
||||
|
||||
this.$backdrop = $('<div/>').addClass('composer-backdrop').appendTo('body');
|
||||
}
|
||||
} else {
|
||||
this.component.focus();
|
||||
}
|
||||
break;
|
||||
|
||||
case Composer.PositionEnum.MINIMIZED:
|
||||
// If the composer has been minimized, we will animate it shrinking down
|
||||
// to its new smaller size.
|
||||
$composer.css({ top: 'auto', height: oldHeight }).animate({ height: newHeight }, 'fast');
|
||||
|
||||
if (this.$backdrop) this.$backdrop.remove();
|
||||
break;
|
||||
|
||||
case Composer.PositionEnum.HIDDEN:
|
||||
// If the composer has been hidden, then we will animate it sliding down
|
||||
// beyond the edge of the viewport. Once the animation is complete, we
|
||||
// un-draw the composer's component.
|
||||
$composer.css({ top: 'auto', height: oldHeight }).animate({ bottom: -newHeight }, 'fast', function () {
|
||||
$composer.hide();
|
||||
_this3.clear();
|
||||
m.redraw();
|
||||
});
|
||||
|
||||
if (this.$backdrop) this.$backdrop.remove();
|
||||
break;
|
||||
|
||||
case Composer.PositionEnum.FULLSCREEN:
|
||||
this.component.focus();
|
||||
break;
|
||||
|
||||
default:
|
||||
// no default
|
||||
}
|
||||
|
||||
// Provided the composer isn't in fullscreen mode, we'll want to update the
|
||||
// body's padding to make sure all of the page's content can still be seen.
|
||||
// Plus, we'll scroll back to where we were before the composer was opened,
|
||||
// as its opening may have changed the content of the page.
|
||||
if (this.position !== Composer.PositionEnum.FULLSCREEN) {
|
||||
this.updateBodyPadding();
|
||||
$('html, body').scrollTop(scrollTop);
|
||||
}
|
||||
|
||||
this.oldPosition = this.position;
|
||||
}
|
||||
}, {
|
||||
key: 'isMobile',
|
||||
value: function isMobile() {
|
||||
return this.$backdrop && this.$backdrop.length;
|
||||
key: 'isFullScreen',
|
||||
value: function isFullScreen() {
|
||||
return this.position === Composer.PositionEnum.FULLSCREEN || this.$().css('position') === 'absolute';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20490,6 +20400,68 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
this.component = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the Composer into the given position.
|
||||
*
|
||||
* @param {Composer.PositionEnum} position
|
||||
*/
|
||||
}, {
|
||||
key: 'animateToPosition',
|
||||
value: function animateToPosition(position) {
|
||||
var _this2 = this;
|
||||
|
||||
// Before we redraw the composer to its new state, we need to save the
|
||||
// current height of the composer, as well as the page's scroll position, so
|
||||
// that we can smoothly transition from the old to the new state.
|
||||
var oldPosition = this.position;
|
||||
var $composer = this.$().stop(true);
|
||||
var oldHeight = $composer.outerHeight();
|
||||
var scrollTop = $(window).scrollTop();
|
||||
|
||||
this.position = position;
|
||||
|
||||
m.redraw(true);
|
||||
|
||||
// Now that we've redrawn and the composer's DOM has been updated, we want
|
||||
// to update the composer's height. Once we've done that, we'll capture the
|
||||
// real value to use as the end point for our animation later on.
|
||||
$composer.show();
|
||||
this.updateHeight();
|
||||
|
||||
var newHeight = $composer.outerHeight();
|
||||
|
||||
if (oldPosition === Composer.PositionEnum.HIDDEN) {
|
||||
$composer.css({ bottom: -newHeight, height: newHeight });
|
||||
} else {
|
||||
$composer.css({ height: oldHeight });
|
||||
}
|
||||
|
||||
$composer.animate({ bottom: 0, height: newHeight }, 'fast', function () {
|
||||
return _this2.component.focus();
|
||||
});
|
||||
|
||||
this.updateBodyPadding();
|
||||
$(window).scrollTop(scrollTop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Composer backdrop.
|
||||
*/
|
||||
}, {
|
||||
key: 'showBackdrop',
|
||||
value: function showBackdrop() {
|
||||
this.$backdrop = $('<div/>').addClass('composer-backdrop').appendTo('body');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the Composer backdrop.
|
||||
*/
|
||||
}, {
|
||||
key: 'hideBackdrop',
|
||||
value: function hideBackdrop() {
|
||||
if (this.$backdrop) this.$backdrop.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the composer.
|
||||
*
|
||||
|
@ -20498,14 +20470,16 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
}, {
|
||||
key: 'show',
|
||||
value: function show() {
|
||||
// If the composer is hidden or minimized, we'll need to update its
|
||||
// position. Otherwise, if the composer is already showing (whether it's
|
||||
// fullscreen or not), we can leave it as is.
|
||||
if ([Composer.PositionEnum.MINIMIZED, Composer.PositionEnum.HIDDEN].indexOf(this.position) !== -1) {
|
||||
this.position = Composer.PositionEnum.NORMAL;
|
||||
if (this.position === Composer.PositionEnum.NORMAL || this.position === Composer.PositionEnum.FULLSCREEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.update();
|
||||
this.animateToPosition(Composer.PositionEnum.NORMAL);
|
||||
|
||||
if (this.isFullScreen()) {
|
||||
this.$().css('top', $(window).scrollTop());
|
||||
this.showBackdrop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20516,8 +20490,22 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
}, {
|
||||
key: 'hide',
|
||||
value: function hide() {
|
||||
this.position = Composer.PositionEnum.HIDDEN;
|
||||
this.update();
|
||||
var _this3 = this;
|
||||
|
||||
var $composer = this.$();
|
||||
|
||||
// Animate the composer sliding down off the bottom edge of the viewport.
|
||||
// Only when the animation is completed, update the Composer state flag and
|
||||
// other elements on the page.
|
||||
$composer.stop(true).animate({ bottom: -$composer.height() }, 'fast', function () {
|
||||
_this3.position = Composer.PositionEnum.HIDDEN;
|
||||
_this3.clear();
|
||||
m.redraw();
|
||||
|
||||
$composer.hide();
|
||||
_this3.hideBackdrop();
|
||||
_this3.updateBodyPadding();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20542,10 +20530,12 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
}, {
|
||||
key: 'minimize',
|
||||
value: function minimize() {
|
||||
if (this.position !== Composer.PositionEnum.HIDDEN) {
|
||||
this.position = Composer.PositionEnum.MINIMIZED;
|
||||
this.update();
|
||||
}
|
||||
if (this.position === Composer.PositionEnum.HIDDEN) return;
|
||||
|
||||
this.animateToPosition(Composer.PositionEnum.MINIMIZED);
|
||||
|
||||
this.$().css('top', 'auto');
|
||||
this.hideBackdrop();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20559,7 +20549,9 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
value: function fullScreen() {
|
||||
if (this.position !== Composer.PositionEnum.HIDDEN) {
|
||||
this.position = Composer.PositionEnum.FULLSCREEN;
|
||||
this.update();
|
||||
m.redraw();
|
||||
this.updateHeight();
|
||||
this.component.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20573,7 +20565,9 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
|
|||
value: function exitFullScreen() {
|
||||
if (this.position === Composer.PositionEnum.FULLSCREEN) {
|
||||
this.position = Composer.PositionEnum.NORMAL;
|
||||
this.update();
|
||||
m.redraw();
|
||||
this.updateHeight();
|
||||
this.component.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22442,6 +22436,16 @@ System.register('flarum/components/Dropdown', ['flarum/Component', 'flarum/helpe
|
|||
};
|
||||
});;
|
||||
System.register('flarum/components/EditPostComposer', ['flarum/components/ComposerBody', 'flarum/helpers/icon'], function (_export) {
|
||||
'use strict';
|
||||
|
||||
var ComposerBody, icon, EditPostComposer;
|
||||
|
||||
function minimizeComposerIfFullScreen(e) {
|
||||
if (app.composer.isFullScreen()) {
|
||||
app.composer.minimize();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `EditPostComposer` component displays the composer content for editing a
|
||||
|
@ -22453,9 +22457,6 @@ System.register('flarum/components/EditPostComposer', ['flarum/components/Compos
|
|||
* - All of the props for ComposerBody
|
||||
* - `post`
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ComposerBody, icon, EditPostComposer;
|
||||
return {
|
||||
setters: [function (_flarumComponentsComposerBody) {
|
||||
ComposerBody = _flarumComponentsComposerBody['default'];
|
||||
|
@ -22478,16 +22479,8 @@ System.register('flarum/components/EditPostComposer', ['flarum/components/Compos
|
|||
|
||||
babelHelpers.get(Object.getPrototypeOf(EditPostComposer.prototype), 'init', this).call(this);
|
||||
|
||||
this.editor.props.preview = function () {
|
||||
// If the composer backdrop is visible, assume we're on mobile and need to
|
||||
// minimize the composer in order to see the preview. We do this as a
|
||||
// timeout so that it occurs after the click handler on the composer
|
||||
// itself that shows the composer if minimized.
|
||||
if (app.composer.isMobile()) {
|
||||
setTimeout(function () {
|
||||
return app.composer.minimize();
|
||||
}, 0);
|
||||
}
|
||||
this.editor.props.preview = function (e) {
|
||||
minimizeComposerIfFullScreen(e);
|
||||
|
||||
m.route(app.route.post(_this.props.post));
|
||||
};
|
||||
|
@ -22498,6 +22491,12 @@ System.register('flarum/components/EditPostComposer', ['flarum/components/Compos
|
|||
var items = babelHelpers.get(Object.getPrototypeOf(EditPostComposer.prototype), 'headerItems', this).call(this);
|
||||
var post = this.props.post;
|
||||
|
||||
var routeAndMinimize = function routeAndMinimize(element, isInitialized) {
|
||||
if (isInitialized) return;
|
||||
$(element).on('click', minimizeComposerIfFullScreen);
|
||||
m.route.apply(this, arguments);
|
||||
};
|
||||
|
||||
items.add('title', m(
|
||||
'h3',
|
||||
null,
|
||||
|
@ -22506,7 +22505,7 @@ System.register('flarum/components/EditPostComposer', ['flarum/components/Compos
|
|||
' ',
|
||||
m(
|
||||
'a',
|
||||
{ href: app.route.discussion(post.discussion(), post.number()), config: m.route },
|
||||
{ href: app.route.discussion(post.discussion(), post.number()), config: routeAndMinimize },
|
||||
app.translator.trans('core.forum.composer_edit.post_link', { number: post.number(), discussion: post.discussion().title() })
|
||||
)
|
||||
));
|
||||
|
@ -27561,6 +27560,16 @@ System.register('flarum/components/PostUser', ['flarum/Component', 'flarum/compo
|
|||
};
|
||||
});;
|
||||
System.register('flarum/components/ReplyComposer', ['flarum/components/ComposerBody', 'flarum/components/Alert', 'flarum/components/Button', 'flarum/helpers/icon', 'flarum/utils/extractText'], function (_export) {
|
||||
'use strict';
|
||||
|
||||
var ComposerBody, Alert, Button, icon, extractText, ReplyComposer;
|
||||
|
||||
function minimizeComposerIfFullScreen(e) {
|
||||
if (app.composer.isFullScreen()) {
|
||||
app.composer.minimize();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `ReplyComposer` component displays the composer content for replying to a
|
||||
|
@ -27571,9 +27580,6 @@ System.register('flarum/components/ReplyComposer', ['flarum/components/ComposerB
|
|||
* - All of the props of ComposerBody
|
||||
* - `discussion`
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ComposerBody, Alert, Button, icon, extractText, ReplyComposer;
|
||||
return {
|
||||
setters: [function (_flarumComponentsComposerBody) {
|
||||
ComposerBody = _flarumComponentsComposerBody['default'];
|
||||
|
@ -27602,16 +27608,8 @@ System.register('flarum/components/ReplyComposer', ['flarum/components/ComposerB
|
|||
|
||||
babelHelpers.get(Object.getPrototypeOf(ReplyComposer.prototype), 'init', this).call(this);
|
||||
|
||||
this.editor.props.preview = function () {
|
||||
// If the composer backdrop is visible, assume we're on mobile and need to
|
||||
// minimize the composer in order to see the preview. We do this as a
|
||||
// timeout so that it occurs after the click handler on the composer
|
||||
// itself that shows the composer if minimized.
|
||||
if (app.composer.isMobile()) {
|
||||
setTimeout(function () {
|
||||
return app.composer.minimize();
|
||||
}, 0);
|
||||
}
|
||||
this.editor.props.preview = function (e) {
|
||||
minimizeComposerIfFullScreen(e);
|
||||
|
||||
m.route(app.route.discussion(_this.props.discussion, 'reply'));
|
||||
};
|
||||
|
@ -27622,6 +27620,12 @@ System.register('flarum/components/ReplyComposer', ['flarum/components/ComposerB
|
|||
var items = babelHelpers.get(Object.getPrototypeOf(ReplyComposer.prototype), 'headerItems', this).call(this);
|
||||
var discussion = this.props.discussion;
|
||||
|
||||
var routeAndMinimize = function routeAndMinimize(element, isInitialized) {
|
||||
if (isInitialized) return;
|
||||
$(element).on('click', minimizeComposerIfFullScreen);
|
||||
m.route.apply(this, arguments);
|
||||
};
|
||||
|
||||
items.add('title', m(
|
||||
'h3',
|
||||
null,
|
||||
|
@ -27630,7 +27634,7 @@ System.register('flarum/components/ReplyComposer', ['flarum/components/ComposerB
|
|||
' ',
|
||||
m(
|
||||
'a',
|
||||
{ href: app.route.discussion(discussion), config: m.route },
|
||||
{ href: app.route.discussion(discussion), config: routeAndMinimize },
|
||||
discussion.title()
|
||||
)
|
||||
));
|
||||
|
@ -29201,7 +29205,7 @@ System.register('flarum/components/TextEditor', ['flarum/Component', 'flarum/uti
|
|||
/**
|
||||
* The value of the textarea.
|
||||
*
|
||||
* @type {[type]}
|
||||
* @type {String}
|
||||
*/
|
||||
this.value = m.prop(this.props.value || '');
|
||||
}
|
||||
|
@ -29211,7 +29215,7 @@ System.register('flarum/components/TextEditor', ['flarum/Component', 'flarum/uti
|
|||
return m(
|
||||
'div',
|
||||
{ className: 'TextEditor' },
|
||||
m('textarea', { className: 'FormControl TextEditor-flexible',
|
||||
m('textarea', { className: 'FormControl Composer-flexible',
|
||||
config: this.configTextarea.bind(this),
|
||||
oninput: m.withAttr('value', this.oninput.bind(this)),
|
||||
placeholder: this.props.placeholder || '',
|
||||
|
@ -29219,7 +29223,7 @@ System.register('flarum/components/TextEditor', ['flarum/Component', 'flarum/uti
|
|||
value: this.value() }),
|
||||
m(
|
||||
'ul',
|
||||
{ className: 'TextEditor-controls' },
|
||||
{ className: 'TextEditor-controls Composer-footer' },
|
||||
listItems(this.controlItems().toArray())
|
||||
)
|
||||
);
|
||||
|
|
|
@ -19,13 +19,6 @@ class Composer extends Component {
|
|||
*/
|
||||
this.position = Composer.PositionEnum.HIDDEN;
|
||||
|
||||
/**
|
||||
* The composer's previous position.
|
||||
*
|
||||
* @type {Composer.PositionEnum}
|
||||
*/
|
||||
this.oldPosition = null;
|
||||
|
||||
/**
|
||||
* The composer's intended height, which can be modified by the user
|
||||
* (by dragging the composer handle).
|
||||
|
@ -71,17 +64,14 @@ class Composer extends Component {
|
|||
'fullScreen': this.position === Composer.PositionEnum.FULLSCREEN,
|
||||
'active': this.active
|
||||
};
|
||||
classes.visible = this.position === Composer.PositionEnum.NORMAL || classes.minimized || classes.fullScreen;
|
||||
classes.visible = classes.normal || classes.minimized || classes.fullScreen;
|
||||
|
||||
// If the composer is minimized, tell the composer's content component that
|
||||
// it shouldn't let the user interact with it. Set up a handler so that if
|
||||
// the content IS clicked, the composer will be shown.
|
||||
if (this.component) this.component.props.disabled = classes.minimized;
|
||||
|
||||
const showIfMinimized = () => {
|
||||
if (this.position === Composer.PositionEnum.MINIMIZED) this.show();
|
||||
m.redraw.strategy('none');
|
||||
};
|
||||
const showIfMinimized = this.position === Composer.PositionEnum.MINIMIZED ? this.show.bind(this) : undefined;
|
||||
|
||||
return (
|
||||
<div className={'Composer ' + classList(classes)}>
|
||||
|
@ -101,8 +91,6 @@ class Composer extends Component {
|
|||
defaultHeight = this.$().height();
|
||||
}
|
||||
|
||||
this.updateHeight();
|
||||
|
||||
if (isInitialized) return;
|
||||
|
||||
// Since this component is a part of the global UI that persists between
|
||||
|
@ -214,17 +202,15 @@ class Composer extends Component {
|
|||
* of any flexible elements inside the composer's body.
|
||||
*/
|
||||
updateHeight() {
|
||||
// TODO: update this in a way that is independent of the TextEditor being
|
||||
// present.
|
||||
const height = this.computedHeight();
|
||||
const $flexible = this.$('.TextEditor-flexible');
|
||||
const $flexible = this.$('.Composer-flexible');
|
||||
|
||||
this.$().height(height);
|
||||
|
||||
if ($flexible.length) {
|
||||
const headerHeight = $flexible.offset().top - this.$().offset().top;
|
||||
const paddingBottom = parseInt($flexible.css('padding-bottom'), 10);
|
||||
const footerHeight = this.$('.TextEditor-controls').outerHeight(true);
|
||||
const footerHeight = this.$('.Composer-footer').outerHeight(true);
|
||||
|
||||
$flexible.height(this.$().outerHeight() - headerHeight - paddingBottom - footerHeight);
|
||||
}
|
||||
|
@ -243,98 +229,21 @@ class Composer extends Component {
|
|||
const paddingBottom = visible
|
||||
? this.computedHeight() - parseInt($('#app').css('padding-bottom'), 10)
|
||||
: 0;
|
||||
|
||||
$('#content').css({paddingBottom});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update (and animate) the DOM to reflect the composer's current state.
|
||||
* Determine whether or not the Composer is covering the screen.
|
||||
*
|
||||
* This will be true if the Composer is in full-screen mode on desktop, or
|
||||
* if the Composer is positioned absolutely as on mobile devices.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @public
|
||||
*/
|
||||
update() {
|
||||
// Before we redraw the composer to its new state, we need to save the
|
||||
// current height of the composer, as well as the page's scroll position, so
|
||||
// that we can smoothly transition from the old to the new state.
|
||||
const $composer = this.$().stop(true);
|
||||
const oldHeight = $composer.is(':visible') ? $composer.outerHeight() : 0;
|
||||
const scrollTop = $(window).scrollTop();
|
||||
|
||||
m.redraw(true);
|
||||
|
||||
// Now that we've redrawn and the composer's DOM has been updated, we want
|
||||
// to update the composer's height. Once we've done that, we'll capture the
|
||||
// real value to use as the end point for our animation later on.
|
||||
$composer.show();
|
||||
this.updateHeight();
|
||||
|
||||
const newHeight = $composer.outerHeight();
|
||||
|
||||
switch (this.position) {
|
||||
case Composer.PositionEnum.NORMAL:
|
||||
// If the composer is being opened, we will make it visible and animate
|
||||
// it growing/sliding up from the bottom of the viewport. Or if the user
|
||||
// has just exited fullscreen mode, we will simply tell the content to
|
||||
// take focus.
|
||||
if (this.oldPosition !== Composer.PositionEnum.FULLSCREEN) {
|
||||
$composer.show()
|
||||
.css({height: oldHeight})
|
||||
.animate({bottom: 0, height: newHeight}, 'fast', this.component.focus.bind(this.component));
|
||||
|
||||
if ($composer.css('position') === 'absolute') {
|
||||
$composer.css('top', $(window).scrollTop());
|
||||
|
||||
this.$backdrop = $('<div/>')
|
||||
.addClass('composer-backdrop')
|
||||
.appendTo('body');
|
||||
}
|
||||
} else {
|
||||
this.component.focus();
|
||||
}
|
||||
break;
|
||||
|
||||
case Composer.PositionEnum.MINIMIZED:
|
||||
// If the composer has been minimized, we will animate it shrinking down
|
||||
// to its new smaller size.
|
||||
$composer.css({top: 'auto', height: oldHeight})
|
||||
.animate({height: newHeight}, 'fast');
|
||||
|
||||
if (this.$backdrop) this.$backdrop.remove();
|
||||
break;
|
||||
|
||||
case Composer.PositionEnum.HIDDEN:
|
||||
// If the composer has been hidden, then we will animate it sliding down
|
||||
// beyond the edge of the viewport. Once the animation is complete, we
|
||||
// un-draw the composer's component.
|
||||
$composer.css({top: 'auto', height: oldHeight})
|
||||
.animate({bottom: -newHeight}, 'fast', () => {
|
||||
$composer.hide();
|
||||
this.clear();
|
||||
m.redraw();
|
||||
});
|
||||
|
||||
if (this.$backdrop) this.$backdrop.remove();
|
||||
break;
|
||||
|
||||
case Composer.PositionEnum.FULLSCREEN:
|
||||
this.component.focus();
|
||||
break;
|
||||
|
||||
default:
|
||||
// no default
|
||||
}
|
||||
|
||||
// Provided the composer isn't in fullscreen mode, we'll want to update the
|
||||
// body's padding to make sure all of the page's content can still be seen.
|
||||
// Plus, we'll scroll back to where we were before the composer was opened,
|
||||
// as its opening may have changed the content of the page.
|
||||
if (this.position !== Composer.PositionEnum.FULLSCREEN) {
|
||||
this.updateBodyPadding();
|
||||
$('html, body').scrollTop(scrollTop);
|
||||
}
|
||||
|
||||
this.oldPosition = this.position;
|
||||
}
|
||||
|
||||
isMobile() {
|
||||
return this.$backdrop && this.$backdrop.length;
|
||||
isFullScreen() {
|
||||
return this.position === Composer.PositionEnum.FULLSCREEN || this.$().css('position') === 'absolute';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -384,20 +293,76 @@ class Composer extends Component {
|
|||
this.component = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the Composer into the given position.
|
||||
*
|
||||
* @param {Composer.PositionEnum} position
|
||||
*/
|
||||
animateToPosition(position) {
|
||||
// Before we redraw the composer to its new state, we need to save the
|
||||
// current height of the composer, as well as the page's scroll position, so
|
||||
// that we can smoothly transition from the old to the new state.
|
||||
const oldPosition = this.position;
|
||||
const $composer = this.$().stop(true);
|
||||
const oldHeight = $composer.outerHeight();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
|
||||
this.position = position;
|
||||
|
||||
m.redraw(true);
|
||||
|
||||
// Now that we've redrawn and the composer's DOM has been updated, we want
|
||||
// to update the composer's height. Once we've done that, we'll capture the
|
||||
// real value to use as the end point for our animation later on.
|
||||
$composer.show();
|
||||
this.updateHeight();
|
||||
|
||||
const newHeight = $composer.outerHeight();
|
||||
|
||||
if (oldPosition === Composer.PositionEnum.HIDDEN) {
|
||||
$composer.css({bottom: -newHeight, height: newHeight});
|
||||
} else {
|
||||
$composer.css({height: oldHeight});
|
||||
}
|
||||
|
||||
$composer.animate({bottom: 0, height: newHeight}, 'fast', () => this.component.focus());
|
||||
|
||||
this.updateBodyPadding();
|
||||
$(window).scrollTop(scrollTop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Composer backdrop.
|
||||
*/
|
||||
showBackdrop() {
|
||||
this.$backdrop = $('<div/>')
|
||||
.addClass('composer-backdrop')
|
||||
.appendTo('body');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the Composer backdrop.
|
||||
*/
|
||||
hideBackdrop() {
|
||||
if (this.$backdrop) this.$backdrop.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the composer.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
show() {
|
||||
// If the composer is hidden or minimized, we'll need to update its
|
||||
// position. Otherwise, if the composer is already showing (whether it's
|
||||
// fullscreen or not), we can leave it as is.
|
||||
if ([Composer.PositionEnum.MINIMIZED, Composer.PositionEnum.HIDDEN].indexOf(this.position) !== -1) {
|
||||
this.position = Composer.PositionEnum.NORMAL;
|
||||
if (this.position === Composer.PositionEnum.NORMAL || this.position === Composer.PositionEnum.FULLSCREEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.update();
|
||||
this.animateToPosition(Composer.PositionEnum.NORMAL);
|
||||
|
||||
if (this.isFullScreen()) {
|
||||
this.$().css('top', $(window).scrollTop());
|
||||
this.showBackdrop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -406,8 +371,20 @@ class Composer extends Component {
|
|||
* @public
|
||||
*/
|
||||
hide() {
|
||||
this.position = Composer.PositionEnum.HIDDEN;
|
||||
this.update();
|
||||
const $composer = this.$();
|
||||
|
||||
// Animate the composer sliding down off the bottom edge of the viewport.
|
||||
// Only when the animation is completed, update the Composer state flag and
|
||||
// other elements on the page.
|
||||
$composer.stop(true).animate({bottom: -$composer.height()}, 'fast', () => {
|
||||
this.position = Composer.PositionEnum.HIDDEN;
|
||||
this.clear();
|
||||
m.redraw();
|
||||
|
||||
$composer.hide();
|
||||
this.hideBackdrop();
|
||||
this.updateBodyPadding();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -428,10 +405,12 @@ class Composer extends Component {
|
|||
* @public
|
||||
*/
|
||||
minimize() {
|
||||
if (this.position !== Composer.PositionEnum.HIDDEN) {
|
||||
this.position = Composer.PositionEnum.MINIMIZED;
|
||||
this.update();
|
||||
}
|
||||
if (this.position === Composer.PositionEnum.HIDDEN) return;
|
||||
|
||||
this.animateToPosition(Composer.PositionEnum.MINIMIZED);
|
||||
|
||||
this.$().css('top', 'auto');
|
||||
this.hideBackdrop();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -443,7 +422,9 @@ class Composer extends Component {
|
|||
fullScreen() {
|
||||
if (this.position !== Composer.PositionEnum.HIDDEN) {
|
||||
this.position = Composer.PositionEnum.FULLSCREEN;
|
||||
this.update();
|
||||
m.redraw();
|
||||
this.updateHeight();
|
||||
this.component.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,7 +436,9 @@ class Composer extends Component {
|
|||
exitFullScreen() {
|
||||
if (this.position === Composer.PositionEnum.FULLSCREEN) {
|
||||
this.position = Composer.PositionEnum.NORMAL;
|
||||
this.update();
|
||||
m.redraw();
|
||||
this.updateHeight();
|
||||
this.component.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import ComposerBody from 'flarum/components/ComposerBody';
|
||||
import icon from 'flarum/helpers/icon';
|
||||
|
||||
function minimizeComposerIfFullScreen(e) {
|
||||
if (app.composer.isFullScreen()) {
|
||||
app.composer.minimize();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `EditPostComposer` component displays the composer content for editing a
|
||||
* post. It sets the initial content to the content of the post that is being
|
||||
|
@ -15,14 +22,8 @@ export default class EditPostComposer extends ComposerBody {
|
|||
init() {
|
||||
super.init();
|
||||
|
||||
this.editor.props.preview = () => {
|
||||
// If the composer backdrop is visible, assume we're on mobile and need to
|
||||
// minimize the composer in order to see the preview. We do this as a
|
||||
// timeout so that it occurs after the click handler on the composer
|
||||
// itself that shows the composer if minimized.
|
||||
if (app.composer.isMobile()) {
|
||||
setTimeout(() => app.composer.minimize(), 0);
|
||||
}
|
||||
this.editor.props.preview = e => {
|
||||
minimizeComposerIfFullScreen(e);
|
||||
|
||||
m.route(app.route.post(this.props.post));
|
||||
};
|
||||
|
@ -43,10 +44,16 @@ export default class EditPostComposer extends ComposerBody {
|
|||
const items = super.headerItems();
|
||||
const post = this.props.post;
|
||||
|
||||
const routeAndMinimize = function(element, isInitialized) {
|
||||
if (isInitialized) return;
|
||||
$(element).on('click', minimizeComposerIfFullScreen);
|
||||
m.route.apply(this, arguments);
|
||||
};
|
||||
|
||||
items.add('title', (
|
||||
<h3>
|
||||
{icon('pencil')} {' '}
|
||||
<a href={app.route.discussion(post.discussion(), post.number())} config={m.route}>
|
||||
<a href={app.route.discussion(post.discussion(), post.number())} config={routeAndMinimize}>
|
||||
{app.translator.trans('core.forum.composer_edit.post_link', {number: post.number(), discussion: post.discussion().title()})}
|
||||
</a>
|
||||
</h3>
|
||||
|
|
|
@ -4,6 +4,13 @@ import Button from 'flarum/components/Button';
|
|||
import icon from 'flarum/helpers/icon';
|
||||
import extractText from 'flarum/utils/extractText';
|
||||
|
||||
function minimizeComposerIfFullScreen(e) {
|
||||
if (app.composer.isFullScreen()) {
|
||||
app.composer.minimize();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `ReplyComposer` component displays the composer content for replying to a
|
||||
* discussion.
|
||||
|
@ -17,14 +24,8 @@ export default class ReplyComposer extends ComposerBody {
|
|||
init() {
|
||||
super.init();
|
||||
|
||||
this.editor.props.preview = () => {
|
||||
// If the composer backdrop is visible, assume we're on mobile and need to
|
||||
// minimize the composer in order to see the preview. We do this as a
|
||||
// timeout so that it occurs after the click handler on the composer
|
||||
// itself that shows the composer if minimized.
|
||||
if (app.composer.isMobile()) {
|
||||
setTimeout(() => app.composer.minimize(), 0);
|
||||
}
|
||||
this.editor.props.preview = e => {
|
||||
minimizeComposerIfFullScreen(e);
|
||||
|
||||
m.route(app.route.discussion(this.props.discussion, 'reply'));
|
||||
};
|
||||
|
@ -42,10 +43,16 @@ export default class ReplyComposer extends ComposerBody {
|
|||
const items = super.headerItems();
|
||||
const discussion = this.props.discussion;
|
||||
|
||||
const routeAndMinimize = function(element, isInitialized) {
|
||||
if (isInitialized) return;
|
||||
$(element).on('click', minimizeComposerIfFullScreen);
|
||||
m.route.apply(this, arguments);
|
||||
};
|
||||
|
||||
items.add('title', (
|
||||
<h3>
|
||||
{icon('reply')} {' '}
|
||||
<a href={app.route.discussion(discussion)} config={m.route}>{discussion.title()}</a>
|
||||
<a href={app.route.discussion(discussion)} config={routeAndMinimize}>{discussion.title()}</a>
|
||||
</h3>
|
||||
));
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ export default class TextEditor extends Component {
|
|||
/**
|
||||
* The value of the textarea.
|
||||
*
|
||||
* @type {[type]}
|
||||
* @type {String}
|
||||
*/
|
||||
this.value = m.prop(this.props.value || '');
|
||||
}
|
||||
|
@ -27,14 +27,14 @@ export default class TextEditor extends Component {
|
|||
view() {
|
||||
return (
|
||||
<div className="TextEditor">
|
||||
<textarea className="FormControl TextEditor-flexible"
|
||||
<textarea className="FormControl Composer-flexible"
|
||||
config={this.configTextarea.bind(this)}
|
||||
oninput={m.withAttr('value', this.oninput.bind(this))}
|
||||
placeholder={this.props.placeholder || ''}
|
||||
disabled={!!this.props.disabled}
|
||||
value={this.value()}/>
|
||||
|
||||
<ul className="TextEditor-controls">
|
||||
<ul className="TextEditor-controls Composer-footer">
|
||||
{listItems(this.controlItems().toArray())}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -39,12 +39,15 @@
|
|||
h3 {
|
||||
margin: 0;
|
||||
line-height: 1.5em;
|
||||
color: @secondary-color;
|
||||
|
||||
&, input, a {
|
||||
color: @secondary-color;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
input, a {
|
||||
color: inherit;
|
||||
}
|
||||
input {
|
||||
font-size: 16px;
|
||||
width: 500px;
|
||||
|
@ -185,6 +188,10 @@
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
h3 {
|
||||
color: @header-control-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
h3 {
|
||||
|
|
Loading…
Reference in New Issue
Block a user