mirror of
https://github.com/flarum/core.git
synced 2025-08-06 08:27:42 +02:00
Merge remote-tracking branch 'upstream/master' into signup-fields-locking
This commit is contained in:
@@ -13,7 +13,6 @@ gulp({
|
||||
bowerDir + '/jquery.hotkeys/jquery.hotkeys.js',
|
||||
bowerDir + '/color-thief/src/color-thief.js',
|
||||
bowerDir + '/moment/moment.js',
|
||||
bowerDir + '/autolink/autolink-min.js',
|
||||
|
||||
bowerDir + '/bootstrap/js/affix.js',
|
||||
bowerDir + '/bootstrap/js/dropdown.js',
|
||||
@@ -23,7 +22,6 @@ gulp({
|
||||
|
||||
bowerDir + '/spin.js/spin.js',
|
||||
bowerDir + '/spin.js/jquery.spin.js',
|
||||
bowerDir + '/fastclick/lib/fastclick.js',
|
||||
bowerDir + '/punycode/index.js'
|
||||
],
|
||||
modules: {
|
||||
|
1328
js/forum/dist/app.js
vendored
1328
js/forum/dist/app.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,13 @@ export default class AvatarEditor extends Component {
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.loading = false;
|
||||
|
||||
/**
|
||||
* Whether or not an image has been dragged over the dropzone.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.isDraggedOver = false;
|
||||
}
|
||||
|
||||
static initProps(props) {
|
||||
@@ -35,12 +42,17 @@ export default class AvatarEditor extends Component {
|
||||
const user = this.props.user;
|
||||
|
||||
return (
|
||||
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '')}>
|
||||
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '') + (this.isDraggedOver ? ' dragover' : '')}>
|
||||
{avatar(user)}
|
||||
<a className={ user.avatarUrl() ? "Dropdown-toggle" : "Dropdown-toggle AvatarEditor--noAvatar" }
|
||||
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
||||
data-toggle="dropdown"
|
||||
onclick={this.quickUpload.bind(this)}>
|
||||
onclick={this.quickUpload.bind(this)}
|
||||
ondragover={this.enableDragover.bind(this)}
|
||||
ondragenter={this.enableDragover.bind(this)}
|
||||
ondragleave={this.disableDragover.bind(this)}
|
||||
ondragend={this.disableDragover.bind(this)}
|
||||
ondrop={this.dropUpload.bind(this)}>
|
||||
{this.loading ? LoadingIndicator.component() : (user.avatarUrl() ? icon('pencil') : icon('plus-circle'))}
|
||||
</a>
|
||||
<ul className="Dropdown-menu Menu">
|
||||
@@ -62,7 +74,7 @@ export default class AvatarEditor extends Component {
|
||||
Button.component({
|
||||
icon: 'upload',
|
||||
children: app.translator.trans('core.forum.user.avatar_upload_button'),
|
||||
onclick: this.upload.bind(this)
|
||||
onclick: this.openPicker.bind(this)
|
||||
})
|
||||
);
|
||||
|
||||
@@ -77,6 +89,40 @@ export default class AvatarEditor extends Component {
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable dragover style
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
enableDragover(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.isDraggedOver = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable dragover style
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
disableDragover(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.isDraggedOver = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload avatar when file is dropped into dropzone.
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
dropUpload(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.isDraggedOver = false;
|
||||
this.upload(e.dataTransfer.files[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user doesn't have an avatar, there's no point in showing the
|
||||
* controls dropdown, because only one option would be viable: uploading.
|
||||
@@ -89,14 +135,14 @@ export default class AvatarEditor extends Component {
|
||||
if (!this.props.user.avatarUrl()) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.upload();
|
||||
this.openPicker();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to upload a new avatar.
|
||||
* Upload avatar using file picker
|
||||
*/
|
||||
upload() {
|
||||
openPicker() {
|
||||
if (this.loading) return;
|
||||
|
||||
// Create a hidden HTML input element and click on it so the user can select
|
||||
@@ -105,24 +151,36 @@ export default class AvatarEditor extends Component {
|
||||
const $input = $('<input type="file">');
|
||||
|
||||
$input.appendTo('body').hide().click().on('change', e => {
|
||||
const data = new FormData();
|
||||
data.append('avatar', $(e.target)[0].files[0]);
|
||||
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
app.request({
|
||||
method: 'POST',
|
||||
url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar',
|
||||
serialize: raw => raw,
|
||||
data
|
||||
}).then(
|
||||
this.success.bind(this),
|
||||
this.failure.bind(this)
|
||||
);
|
||||
this.upload($(e.target)[0].files[0]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload avatar
|
||||
*
|
||||
* @param {File} file
|
||||
*/
|
||||
upload(file) {
|
||||
if (this.loading) return;
|
||||
|
||||
const user = this.props.user;
|
||||
const data = new FormData();
|
||||
data.append('avatar', file);
|
||||
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
app.request({
|
||||
method: 'POST',
|
||||
url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar',
|
||||
serialize: raw => raw,
|
||||
data
|
||||
}).then(
|
||||
this.success.bind(this),
|
||||
this.failure.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the user's avatar.
|
||||
*/
|
||||
|
@@ -3,7 +3,6 @@ import ItemList from 'flarum/utils/ItemList';
|
||||
import ComposerButton from 'flarum/components/ComposerButton';
|
||||
import listItems from 'flarum/helpers/listItems';
|
||||
import classList from 'flarum/utils/classList';
|
||||
import computed from 'flarum/utils/computed';
|
||||
|
||||
/**
|
||||
* The `Composer` component displays the composer. It can be loaded with a
|
||||
@@ -33,28 +32,6 @@ class Composer extends Component {
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.active = false;
|
||||
|
||||
/**
|
||||
* Computed the composer's current height, based on the intended height, and
|
||||
* the composer's current state. This will be applied to the composer's
|
||||
* content's DOM element.
|
||||
*
|
||||
* @return {Integer}
|
||||
*/
|
||||
this.computedHeight = computed('height', 'position', (height, position) => {
|
||||
// If the composer is minimized, then we don't want to set a height; we'll
|
||||
// let the CSS decide how high it is. If it's fullscreen, then we need to
|
||||
// make it as high as the window.
|
||||
if (position === Composer.PositionEnum.MINIMIZED) {
|
||||
return '';
|
||||
} else if (position === Composer.PositionEnum.FULLSCREEN) {
|
||||
return $(window).height();
|
||||
}
|
||||
|
||||
// Otherwise, if it's normal or hidden, then we use the intended height.
|
||||
// We don't let the composer get too small or too big, though.
|
||||
return Math.max(200, Math.min(height, $(window).height() - $('#header').outerHeight()));
|
||||
});
|
||||
}
|
||||
|
||||
view() {
|
||||
@@ -85,11 +62,9 @@ class Composer extends Component {
|
||||
}
|
||||
|
||||
config(isInitialized, context) {
|
||||
let defaultHeight;
|
||||
|
||||
if (!isInitialized) {
|
||||
defaultHeight = this.$().height();
|
||||
}
|
||||
// Set the height of the Composer element and its contents on each redraw,
|
||||
// so that they do not lose it if their DOM elements are recreated.
|
||||
this.updateHeight();
|
||||
|
||||
if (isInitialized) return;
|
||||
|
||||
@@ -97,11 +72,8 @@ class Composer extends Component {
|
||||
// routes, we will flag the DOM to be retained across route changes.
|
||||
context.retain = true;
|
||||
|
||||
// Initialize the composer's intended height based on what the user has set
|
||||
// it at previously, or otherwise the composer's default height. After that,
|
||||
// we'll hide the composer.
|
||||
this.height = localStorage.getItem('composerHeight') || defaultHeight;
|
||||
this.$().hide().css('bottom', -this.height);
|
||||
this.initializeHeight();
|
||||
this.$().hide().css('bottom', -this.computedHeight());
|
||||
|
||||
// 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.
|
||||
@@ -172,8 +144,7 @@ class Composer extends Component {
|
||||
// height so that it fills the height of the composer, and update the
|
||||
// body's padding.
|
||||
const deltaPixels = this.mouseStart - e.clientY;
|
||||
this.height = this.heightStart + deltaPixels;
|
||||
this.updateHeight();
|
||||
this.changeHeight(this.heightStart + deltaPixels);
|
||||
|
||||
// Update the body's padding-bottom so that no content on the page will ever
|
||||
// get permanently hidden behind the composer. If the user is already
|
||||
@@ -182,8 +153,6 @@ class Composer extends Component {
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const anchorToBottom = scrollTop > 0 && scrollTop + $(window).height() >= $(document).height();
|
||||
this.updateBodyPadding(anchorToBottom);
|
||||
|
||||
localStorage.setItem('composerHeight', this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -482,6 +451,73 @@ class Composer extends Component {
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize default Composer height.
|
||||
*/
|
||||
initializeHeight() {
|
||||
this.height = localStorage.getItem('composerHeight');
|
||||
|
||||
if (!this.height) {
|
||||
this.height = this.defaultHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default height of the Composer in case none is saved.
|
||||
* @returns {Integer}
|
||||
*/
|
||||
defaultHeight() {
|
||||
return this.$().height();
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum height of the Composer.
|
||||
* @returns {Integer}
|
||||
*/
|
||||
minimumHeight() {
|
||||
return 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maxmimum height of the Composer.
|
||||
* @returns {Integer}
|
||||
*/
|
||||
maximumHeight() {
|
||||
return $(window).height() - $('#header').outerHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed the composer's current height, based on the intended height, and
|
||||
* the composer's current state. This will be applied to the composer's
|
||||
* content's DOM element.
|
||||
* @returns {Integer|String}
|
||||
*/
|
||||
computedHeight() {
|
||||
// If the composer is minimized, then we don't want to set a height; we'll
|
||||
// let the CSS decide how high it is. If it's fullscreen, then we need to
|
||||
// make it as high as the window.
|
||||
if (this.position === Composer.PositionEnum.MINIMIZED) {
|
||||
return '';
|
||||
} else if (this.position === Composer.PositionEnum.FULLSCREEN) {
|
||||
return $(window).height();
|
||||
}
|
||||
|
||||
// Otherwise, if it's normal or hidden, then we use the intended height.
|
||||
// We don't let the composer get too small or too big, though.
|
||||
return Math.max(this.minimumHeight(), Math.min(this.height, this.maximumHeight()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new Composer height and update the DOM.
|
||||
* @param {Integer} height
|
||||
*/
|
||||
changeHeight(height) {
|
||||
this.height = height;
|
||||
this.updateHeight();
|
||||
|
||||
localStorage.setItem('composerHeight', this.height);
|
||||
}
|
||||
}
|
||||
|
||||
Composer.PositionEnum = {
|
||||
|
@@ -16,39 +16,17 @@ export default class NotificationList extends Component {
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.loading = false;
|
||||
|
||||
/**
|
||||
* Whether or not there are more results that can be loaded.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.moreResults = false;
|
||||
}
|
||||
|
||||
view() {
|
||||
const groups = [];
|
||||
|
||||
if (app.cache.notifications) {
|
||||
const discussions = {};
|
||||
|
||||
// Build an array of discussions which the notifications are related to,
|
||||
// and add the notifications as children.
|
||||
app.cache.notifications.forEach(notification => {
|
||||
const subject = notification.subject();
|
||||
|
||||
if (typeof subject === 'undefined') return;
|
||||
|
||||
// Get the discussion that this notification is related to. If it's not
|
||||
// directly related to a discussion, it may be related to a post or
|
||||
// other entity which is related to a discussion.
|
||||
let discussion = false;
|
||||
if (subject instanceof Discussion) discussion = subject;
|
||||
else if (subject && subject.discussion) discussion = subject.discussion();
|
||||
|
||||
// If the notification is not related to a discussion directly or
|
||||
// indirectly, then we will assign it to a neutral group.
|
||||
const key = discussion ? discussion.id() : 0;
|
||||
discussions[key] = discussions[key] || {discussion: discussion, notifications: []};
|
||||
discussions[key].notifications.push(notification);
|
||||
|
||||
if (groups.indexOf(discussions[key]) === -1) {
|
||||
groups.push(discussions[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
const pages = app.cache.notifications || [];
|
||||
|
||||
return (
|
||||
<div className="NotificationList">
|
||||
@@ -66,8 +44,34 @@ export default class NotificationList extends Component {
|
||||
</div>
|
||||
|
||||
<div className="NotificationList-content">
|
||||
{groups.length
|
||||
? groups.map(group => {
|
||||
{pages.length ? pages.map(notifications => {
|
||||
const groups = [];
|
||||
const discussions = {};
|
||||
|
||||
notifications.forEach(notification => {
|
||||
const subject = notification.subject();
|
||||
|
||||
if (typeof subject === 'undefined') return;
|
||||
|
||||
// Get the discussion that this notification is related to. If it's not
|
||||
// directly related to a discussion, it may be related to a post or
|
||||
// other entity which is related to a discussion.
|
||||
let discussion = false;
|
||||
if (subject instanceof Discussion) discussion = subject;
|
||||
else if (subject && subject.discussion) discussion = subject.discussion();
|
||||
|
||||
// If the notification is not related to a discussion directly or
|
||||
// indirectly, then we will assign it to a neutral group.
|
||||
const key = discussion ? discussion.id() : 0;
|
||||
discussions[key] = discussions[key] || {discussion: discussion, notifications: []};
|
||||
discussions[key].notifications.push(notification);
|
||||
|
||||
if (groups.indexOf(discussions[key]) === -1) {
|
||||
groups.push(discussions[key]);
|
||||
}
|
||||
});
|
||||
|
||||
return groups.map(group => {
|
||||
const badges = group.discussion && group.discussion.badges().toArray();
|
||||
|
||||
return (
|
||||
@@ -94,32 +98,71 @@ export default class NotificationList extends Component {
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: !this.loading
|
||||
? <div className="NotificationList-empty">{app.translator.trans('core.forum.notifications.empty_text')}</div>
|
||||
: LoadingIndicator.component({className: 'LoadingIndicator--block'})}
|
||||
});
|
||||
}) : ''}
|
||||
{this.loading
|
||||
? <LoadingIndicator className="LoadingIndicator--block" />
|
||||
: (pages.length ? '' : <div className="NotificationList-empty">{app.translator.trans('core.forum.notifications.empty_text')}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
config(isInitialized, context) {
|
||||
if (isInitialized) return;
|
||||
|
||||
const $notifications = this.$('.NotificationList-content');
|
||||
const $scrollParent = $notifications.css('overflow') === 'auto' ? $notifications : $(window);
|
||||
|
||||
const scrollHandler = () => {
|
||||
const scrollTop = $scrollParent.scrollTop();
|
||||
const viewportHeight = $scrollParent.height();
|
||||
const contentTop = $scrollParent === $notifications ? 0 : $notifications.offset().top;
|
||||
const contentHeight = $notifications[0].scrollHeight;
|
||||
|
||||
if (this.moreResults && !this.loading && scrollTop + viewportHeight >= contentTop + contentHeight) {
|
||||
this.loadMore();
|
||||
}
|
||||
};
|
||||
|
||||
$scrollParent.on('scroll', scrollHandler);
|
||||
|
||||
context.onunload = () => {
|
||||
$scrollParent.off('scroll', scrollHandler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load notifications into the application's cache if they haven't already
|
||||
* been loaded.
|
||||
*/
|
||||
load() {
|
||||
if (app.cache.notifications && !app.session.user.newNotificationsCount()) {
|
||||
if (app.session.user.newNotificationsCount()) {
|
||||
delete app.cache.notifications;
|
||||
}
|
||||
|
||||
if (app.cache.notifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
app.session.user.pushAttributes({newNotificationsCount: 0});
|
||||
|
||||
this.loadMore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the next page of notification results.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
loadMore() {
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
app.store.find('notifications')
|
||||
.then(notifications => {
|
||||
app.session.user.pushAttributes({newNotificationsCount: 0});
|
||||
app.cache.notifications = notifications.sort((a, b) => b.time() - a.time());
|
||||
})
|
||||
const params = app.cache.notifications ? {page: {offset: app.cache.notifications.length * 10}} : null;
|
||||
|
||||
return app.store.find('notifications', params)
|
||||
.then(this.parseResults.bind(this))
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
@@ -127,6 +170,21 @@ export default class NotificationList extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse results and append them to the notification list.
|
||||
*
|
||||
* @param {Notification[]} results
|
||||
* @return {Notification[]}
|
||||
*/
|
||||
parseResults(results) {
|
||||
app.cache.notifications = app.cache.notifications || [];
|
||||
app.cache.notifications.push(results);
|
||||
|
||||
this.moreResults = !!results.payload.links.next;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all of the notifications as read.
|
||||
*/
|
||||
@@ -135,7 +193,9 @@ export default class NotificationList extends Component {
|
||||
|
||||
app.session.user.pushAttributes({unreadNotificationsCount: 0});
|
||||
|
||||
app.cache.notifications.forEach(notification => notification.pushAttributes({isRead: true}));
|
||||
app.cache.notifications.forEach(notifications => {
|
||||
notifications.forEach(notification => notification.pushAttributes({isRead: true}))
|
||||
});
|
||||
|
||||
app.request({
|
||||
url: app.forum.attribute('apiUrl') + '/notifications/read',
|
||||
|
@@ -126,7 +126,7 @@ class PostStream extends Component {
|
||||
|
||||
this.visibleEnd = this.count();
|
||||
|
||||
this.loadRange(this.visibleStart, this.visibleEnd).then(() => m.redraw());
|
||||
return this.loadRange(this.visibleStart, this.visibleEnd).then(() => m.redraw());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -82,9 +82,10 @@ export default class ReplyComposer extends ComposerBody {
|
||||
app.store.createRecord('posts').save(data).then(
|
||||
post => {
|
||||
// If we're currently viewing the discussion which this reply was made
|
||||
// in, then we can update the post stream.
|
||||
// in, then we can update the post stream and scroll to the post.
|
||||
if (app.viewingDiscussion(discussion)) {
|
||||
app.current.stream.update();
|
||||
app.current.stream.update().then(() => app.current.stream.goToNumber(post.number()));
|
||||
|
||||
} else {
|
||||
// Otherwise, we'll create an alert message to inform the user that
|
||||
// their reply has been posted, containing a button which will
|
||||
|
@@ -82,7 +82,8 @@ export default class TextEditor extends Component {
|
||||
Button.component({
|
||||
icon: 'eye',
|
||||
className: 'Button Button--icon',
|
||||
onclick: this.props.preview
|
||||
onclick: this.props.preview,
|
||||
title: app.translator.trans('core.forum.composer.preview_tooltip')
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@@ -1,104 +0,0 @@
|
||||
import Component from 'flarum/Component';
|
||||
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||
import classList from 'flarum/utils/classList';
|
||||
import extractText from 'flarum/utils/extractText';
|
||||
|
||||
/**
|
||||
* The `UserBio` component displays a user's bio, optionally letting the user
|
||||
* edit it.
|
||||
*/
|
||||
export default class UserBio extends Component {
|
||||
init() {
|
||||
/**
|
||||
* Whether or not the bio is currently being edited.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.editing = false;
|
||||
|
||||
/**
|
||||
* Whether or not the bio is currently being saved.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
view() {
|
||||
const user = this.props.user;
|
||||
let content;
|
||||
|
||||
if (this.editing) {
|
||||
content = <textarea className="FormControl" placeholder={extractText(app.translator.trans('core.forum.user.bio_placeholder'))} rows="3" value={user.bio()}/>;
|
||||
} else {
|
||||
let subContent;
|
||||
|
||||
if (this.loading) {
|
||||
subContent = <p className="UserBio-placeholder">{LoadingIndicator.component({size: 'tiny'})}</p>;
|
||||
} else {
|
||||
const bioHtml = user.bioHtml();
|
||||
|
||||
if (bioHtml) {
|
||||
subContent = m.trust(bioHtml);
|
||||
} else if (this.props.editable) {
|
||||
subContent = <p className="UserBio-placeholder">{app.translator.trans('core.forum.user.bio_placeholder')}</p>;
|
||||
}
|
||||
}
|
||||
|
||||
content = <div className="UserBio-content" onclick={this.edit.bind(this)}>{subContent}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'UserBio ' + classList({
|
||||
editable: this.props.editable,
|
||||
editing: this.editing
|
||||
})}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the bio.
|
||||
*/
|
||||
edit() {
|
||||
if (!this.props.editable) return;
|
||||
|
||||
this.editing = true;
|
||||
m.redraw();
|
||||
|
||||
const bio = this;
|
||||
const save = function(e) {
|
||||
if (e.shiftKey) return;
|
||||
e.preventDefault();
|
||||
bio.save($(this).val());
|
||||
};
|
||||
|
||||
this.$('textarea').focus()
|
||||
.bind('blur', save)
|
||||
.bind('keydown', 'return', save);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the bio.
|
||||
*
|
||||
* @param {String} value
|
||||
*/
|
||||
save(value) {
|
||||
const user = this.props.user;
|
||||
|
||||
if (user.bio() !== value) {
|
||||
this.loading = true;
|
||||
|
||||
user.save({bio: value})
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
this.editing = false;
|
||||
m.redraw();
|
||||
}
|
||||
}
|
@@ -6,7 +6,6 @@ import avatar from 'flarum/helpers/avatar';
|
||||
import username from 'flarum/helpers/username';
|
||||
import icon from 'flarum/helpers/icon';
|
||||
import Dropdown from 'flarum/components/Dropdown';
|
||||
import UserBio from 'flarum/components/UserBio';
|
||||
import AvatarEditor from 'flarum/components/AvatarEditor';
|
||||
import listItems from 'flarum/helpers/listItems';
|
||||
|
||||
@@ -82,13 +81,6 @@ export default class UserCard extends Component {
|
||||
const user = this.props.user;
|
||||
const lastSeenTime = user.lastSeenTime();
|
||||
|
||||
items.add('bio',
|
||||
UserBio.component({
|
||||
user,
|
||||
editable: this.props.editable
|
||||
})
|
||||
);
|
||||
|
||||
if (lastSeenTime) {
|
||||
const online = user.isOnline();
|
||||
|
||||
|
@@ -9,18 +9,27 @@ import username from 'flarum/helpers/username';
|
||||
* @implements SearchSource
|
||||
*/
|
||||
export default class UsersSearchResults {
|
||||
constructor() {
|
||||
this.results = {};
|
||||
}
|
||||
|
||||
search(query) {
|
||||
return app.store.find('users', {
|
||||
filter: {q: query},
|
||||
page: {limit: 5}
|
||||
}).then(results => {
|
||||
this.results[query] = results;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
view(query) {
|
||||
query = query.toLowerCase();
|
||||
|
||||
const results = app.store.all('users')
|
||||
.filter(user => [user.username(), user.displayName()].some(value => value.toLowerCase().substr(0, query.length) === query));
|
||||
const results = (this.results[query] || [])
|
||||
.concat(app.store.all('users').filter(user => [user.username(), user.displayName()].some(value => value.toLowerCase().substr(0, query.length) === query)))
|
||||
.filter((e, i, arr) => arr.lastIndexOf(e) === i)
|
||||
.sort((a, b) => a.displayName().localeCompare(b.displayName()));
|
||||
|
||||
if (!results.length) return '';
|
||||
|
||||
|
@@ -78,11 +78,7 @@ export default function boot(app) {
|
||||
.toggleClass('scrolled', top > offset);
|
||||
}).start();
|
||||
|
||||
// Initialize FastClick, which makes links and buttons much more responsive on
|
||||
// touch devices.
|
||||
$(() => {
|
||||
FastClick.attach(document.body);
|
||||
|
||||
$('body').addClass('ontouchstart' in window ? 'touch' : 'no-touch');
|
||||
});
|
||||
|
||||
|
@@ -162,7 +162,7 @@ export default {
|
||||
}
|
||||
app.composer.show();
|
||||
|
||||
if (goToLast && app.viewingDiscussion(this)) {
|
||||
if (goToLast && app.viewingDiscussion(this) && ! app.composer.isFullScreen()) {
|
||||
app.current.stream.goToNumber('reply');
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user