mirror of
https://github.com/flarum/framework.git
synced 2024-11-29 12:43:52 +08:00
Search for users in autocomplete popup + other tweaks
- Highlight matching parts of usernames - Fix positioning edge cases
This commit is contained in:
parent
547631ac93
commit
547b2b1304
|
@ -14,11 +14,31 @@ export default function() {
|
||||||
var composer = this;
|
var composer = this;
|
||||||
var $container = $('<div class="mentions-dropdown-container"></div>');
|
var $container = $('<div class="mentions-dropdown-container"></div>');
|
||||||
var dropdown = new AutocompleteDropdown({items: []});
|
var dropdown = new AutocompleteDropdown({items: []});
|
||||||
|
var typed;
|
||||||
|
var mentionStart;
|
||||||
|
var $textarea = this.$('textarea');
|
||||||
|
var searched = [];
|
||||||
|
var searchTimeout;
|
||||||
|
|
||||||
this.$('textarea')
|
var applySuggestion = function(replacement) {
|
||||||
|
replacement += ' ';
|
||||||
|
|
||||||
|
var content = composer.content();
|
||||||
|
composer.editor.setContent(content.substring(0, mentionStart - 1)+replacement+content.substr($textarea[0].selectionStart));
|
||||||
|
|
||||||
|
var index = mentionStart - 1 + replacement.length;
|
||||||
|
composer.editor.setSelectionRange(index, index);
|
||||||
|
|
||||||
|
dropdown.hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
$textarea
|
||||||
.after($container)
|
.after($container)
|
||||||
.on('keydown', dropdown.navigate.bind(dropdown))
|
.on('keydown', dropdown.navigate.bind(dropdown))
|
||||||
.on('input', function() {
|
.on('click keyup', function(e) {
|
||||||
|
// Up, down, enter, tab, escape, left, right.
|
||||||
|
if ([9, 13, 27, 40, 38, 37, 39].indexOf(e.which) !== -1) return;
|
||||||
|
|
||||||
var cursor = this.selectionStart;
|
var cursor = this.selectionStart;
|
||||||
|
|
||||||
if (this.selectionEnd - cursor > 0) return;
|
if (this.selectionEnd - cursor > 0) return;
|
||||||
|
@ -27,7 +47,7 @@ export default function() {
|
||||||
// intervening whitespace. If we find one, we will want to show the
|
// intervening whitespace. If we find one, we will want to show the
|
||||||
// autocomplete dropdown!
|
// autocomplete dropdown!
|
||||||
var value = this.value;
|
var value = this.value;
|
||||||
var mentionStart;
|
mentionStart = 0;
|
||||||
for (var i = cursor - 1; i >= 0; i--) {
|
for (var i = cursor - 1; i >= 0; i--) {
|
||||||
var character = value.substr(i, 1);
|
var character = value.substr(i, 1);
|
||||||
if (/\s/.test(character)) break;
|
if (/\s/.test(character)) break;
|
||||||
|
@ -40,80 +60,106 @@ export default function() {
|
||||||
dropdown.hide();
|
dropdown.hide();
|
||||||
|
|
||||||
if (mentionStart) {
|
if (mentionStart) {
|
||||||
var typed = value.substring(mentionStart, cursor).toLowerCase();
|
typed = value.substring(mentionStart, cursor).toLowerCase();
|
||||||
var suggestions = [];
|
|
||||||
|
|
||||||
var applySuggestion = function(replacement) {
|
var makeSuggestion = function(user, replacement, content, className) {
|
||||||
replacement += ' ';
|
|
||||||
|
|
||||||
var content = composer.content();
|
|
||||||
composer.editor.setContent(content.substring(0, mentionStart - 1)+replacement+content.substr(cursor));
|
|
||||||
|
|
||||||
var index = mentionStart + replacement.length;
|
|
||||||
composer.editor.setSelectionRange(index, index);
|
|
||||||
|
|
||||||
dropdown.hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
var makeSuggestion = function(user, replacement, index, content) {
|
|
||||||
return m('a[href=javascript:;].post-preview', {
|
return m('a[href=javascript:;].post-preview', {
|
||||||
|
className,
|
||||||
onclick: () => applySuggestion(replacement),
|
onclick: () => applySuggestion(replacement),
|
||||||
onmouseover: () => dropdown.setIndex(index)
|
onmouseenter: function() { dropdown.setIndex($(this).parent().index()); }
|
||||||
}, m('div.post-preview-content', [
|
}, m('div.post-preview-content', [
|
||||||
avatar(user),
|
avatar(user),
|
||||||
username(user), ' ',
|
(function() {
|
||||||
|
var vdom = username(user);
|
||||||
|
if (typed) {
|
||||||
|
var regexp = new RegExp(typed, 'gi');
|
||||||
|
vdom.children[0] = m.trust(
|
||||||
|
$('<div/>').text(vdom.children[0]).html().replace(regexp, '<mark>$&</mark>')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return vdom;
|
||||||
|
})(), ' ',
|
||||||
content
|
content
|
||||||
]));
|
]));
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the user is replying to a discussion, or if they are editing a
|
var buildSuggestions = () => {
|
||||||
// post, then we can suggest other posts in the discussion to mention.
|
var suggestions = [];
|
||||||
// We will add the 5 most recent comments in the discussion which
|
|
||||||
// match any username characters that have been typed.
|
// If the user is replying to a discussion, or if they are editing a
|
||||||
var composerPost = composer.props.post;
|
// post, then we can suggest other posts in the discussion to mention.
|
||||||
var discussion = (composerPost && composerPost.discussion()) || composer.props.discussion;
|
// We will add the 5 most recent comments in the discussion which
|
||||||
if (discussion) {
|
// match any username characters that have been typed.
|
||||||
discussion.posts()
|
var composerPost = composer.props.post;
|
||||||
.filter(post => post && post.contentType() === 'comment' && (!composerPost || post.number() < composerPost.number()))
|
var discussion = (composerPost && composerPost.discussion()) || composer.props.discussion;
|
||||||
.sort((a, b) => b.time() - a.time())
|
if (discussion) {
|
||||||
.filter(post => {
|
discussion.posts()
|
||||||
var user = post.user();
|
.filter(post => post && post.contentType() === 'comment' && (!composerPost || post.number() < composerPost.number()))
|
||||||
return user && user.username().toLowerCase().substr(0, typed.length) === typed;
|
.sort((a, b) => b.time() - a.time())
|
||||||
})
|
.filter(post => {
|
||||||
.splice(0, 5)
|
var user = post.user();
|
||||||
.forEach((post, i) => {
|
return user && user.username().toLowerCase().substr(0, typed.length) === typed;
|
||||||
var user = post.user();
|
})
|
||||||
|
.splice(0, 5)
|
||||||
|
.forEach(post => {
|
||||||
|
var user = post.user();
|
||||||
|
suggestions.push(
|
||||||
|
makeSuggestion(user, '@'+user.username()+'#'+post.number(), [
|
||||||
|
'Reply to #', post.number(), ' — ',
|
||||||
|
post.excerpt()
|
||||||
|
], 'suggestion-post')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user has started to type a username, then suggest users
|
||||||
|
// matching that username.
|
||||||
|
if (typed) {
|
||||||
|
app.store.all('users').forEach(user => {
|
||||||
|
if (user.username().toLowerCase().substr(0, typed.length) !== typed) return;
|
||||||
|
|
||||||
suggestions.push(
|
suggestions.push(
|
||||||
makeSuggestion(user, '@'+user.username()+'#'+post.number(), i, [
|
makeSuggestion(user, '@'+user.username(), '', 'suggestion-user')
|
||||||
'Reply to #', post.number(), ' — ',
|
|
||||||
post.excerpt()
|
|
||||||
])
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user has started to type a username, then suggest users
|
if (suggestions.length) {
|
||||||
// matching that username.
|
dropdown.props.items = suggestions;
|
||||||
if (typed) {
|
m.render($container[0], dropdown.view());
|
||||||
app.store.all('users').forEach((user, i) => {
|
|
||||||
if (user.username().toLowerCase().substr(0, typed.length) !== typed) return;
|
|
||||||
|
|
||||||
suggestions.push(
|
dropdown.show();
|
||||||
makeSuggestion(user, '@'+user.username(), i, '@mention')
|
var coordinates = getCaretCoordinates(this, mentionStart);
|
||||||
);
|
var left = coordinates.left;
|
||||||
});
|
var top = coordinates.top + 15;
|
||||||
}
|
var width = dropdown.$().outerWidth();
|
||||||
|
var height = dropdown.$().outerHeight();
|
||||||
|
var parent = dropdown.$().offsetParent();
|
||||||
|
if (top + height > parent.height()) {
|
||||||
|
top = coordinates.top - height - 15;
|
||||||
|
}
|
||||||
|
if (left + width > parent.width()) {
|
||||||
|
left = parent.width() - width;
|
||||||
|
}
|
||||||
|
dropdown.show(left, top);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (suggestions.length) {
|
buildSuggestions();
|
||||||
dropdown.props.items = suggestions;
|
|
||||||
m.render($container[0], dropdown.view());
|
|
||||||
|
|
||||||
var coordinates = getCaretCoordinates(this, mentionStart);
|
dropdown.setIndex(0);
|
||||||
dropdown.show(coordinates.left, coordinates.top + 15);
|
dropdown.$().scrollTop(0);
|
||||||
|
|
||||||
dropdown.setIndex(0);
|
clearTimeout(searchTimeout);
|
||||||
dropdown.$().scrollTop(0);
|
searchTimeout = setTimeout(function() {
|
||||||
}
|
var typedLower = typed.toLowerCase();
|
||||||
|
if (searched.indexOf(typedLower) === -1) {
|
||||||
|
app.store.find('users', {q: typed, page: {limit: 5}}).then(users => {
|
||||||
|
if (dropdown.active()) buildSuggestions();
|
||||||
|
});
|
||||||
|
searched.push(typedLower);
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,13 +30,25 @@
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
|
& mark {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
& > li > a:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.post-preview {
|
.post-preview {
|
||||||
color: @fl-body-muted-color !important;
|
color: @fl-body-muted-color !important;
|
||||||
|
|
||||||
& .avatar {
|
& .avatar {
|
||||||
.avatar-size(32px);
|
.avatar-size(24px);
|
||||||
margin: 3px 0 3px -45px;
|
margin: 0 0 0 -37px;
|
||||||
|
|
||||||
|
.suggestion-post& {
|
||||||
|
margin-top: 3px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
& .username {
|
& .username {
|
||||||
color: @fl-body-color;
|
color: @fl-body-color;
|
||||||
|
@ -44,7 +56,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.post-preview-content {
|
.post-preview-content {
|
||||||
padding-left: 45px;
|
padding-left: 37px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 1.7em;
|
line-height: 1.7em;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user