diff --git a/js/forum/dist/extension.js b/js/forum/dist/extension.js index 5a089ea..8e6668a 100644 --- a/js/forum/dist/extension.js +++ b/js/forum/dist/extension.js @@ -179,14 +179,12 @@ System.register('flarum/mentions/addComposerAutocomplete', ['flarum/extend', 'fl if (this.selectionEnd - cursor > 0) return; - // Search backwards from the cursor for an '@' symbol, without any - // intervening whitespace. If we find one, we will want to show the - // autocomplete dropdown! + // Search backwards from the cursor for an '@' symbol. If we find one, + // we will want to show the autocomplete dropdown! var value = this.value; mentionStart = 0; - for (var i = cursor - 1; i >= 0; i--) { + for (var i = cursor - 1; i >= cursor - 30; i--) { var character = value.substr(i, 1); - if (/\s/.test(character)) break; if (character === '@') { mentionStart = i + 1; break; @@ -228,6 +226,14 @@ System.register('flarum/mentions/addComposerAutocomplete', ['flarum/extend', 'fl ); }; + var userMatches = function userMatches(user) { + var names = [user.username(), user.displayName()]; + + return names.some(function (value) { + return value.toLowerCase().substr(0, typed.length) === typed; + }); + }; + var buildSuggestions = function buildSuggestions() { var suggestions = []; @@ -235,7 +241,7 @@ System.register('flarum/mentions/addComposerAutocomplete', ['flarum/extend', 'fl // matching that username. if (typed) { app.store.all('users').forEach(function (user) { - if (user.username().toLowerCase().substr(0, typed.length) !== typed) return; + if (!userMatches(user)) return; suggestions.push(makeSuggestion(user, '@' + user.username(), '', 'MentionsDropdown-user')); }); @@ -254,7 +260,7 @@ System.register('flarum/mentions/addComposerAutocomplete', ['flarum/extend', 'fl return b.time() - a.time(); }).filter(function (post) { var user = post.user(); - return user && user.username().toLowerCase().substr(0, typed.length) === typed; + return user && userMatches(user); }).splice(0, 5).forEach(function (post) { var user = post.user(); suggestions.push(makeSuggestion(user, '@' + user.username() + '#' + post.id(), [app.translator.trans('flarum-mentions.forum.composer.reply_to_post_text', { number: post.number() }), ' — ', truncate(post.contentPlain(), 200)], 'MentionsDropdown-post')); @@ -279,14 +285,18 @@ System.register('flarum/mentions/addComposerAutocomplete', ['flarum/extend', 'fl left = parent.width() - width; } dropdown.show(left, top); + } else { + dropdown.active = false; + dropdown.hide(); } }; + dropdown.active = true; + buildSuggestions(); dropdown.setIndex(0); dropdown.$().scrollTop(0); - dropdown.active = true; clearTimeout(searchTimeout); if (typed) { @@ -1166,6 +1176,7 @@ System.register('flarum/mentions/main', ['flarum/extend', 'flarum/app', 'flarum/ }), 80); }); + // Remove post mentions when rendering post previews. getPlainContent.removeSelectors.push('a.PostMention'); }); } @@ -1258,4 +1269,46 @@ System.register('flarum/mentions/utils/selectedText', [], function (_export, _co setters: [], execute: function () {} }; +});; +'use strict'; + +System.register('flarum/mentions/utils/textFormatter', ['flarum/helpers/username', 'flarum/utils/extractText'], function (_export, _context) { + "use strict"; + + var username, extractText; + function filterUserMentions(tag) { + var user = app.store.getBy('users', 'username', tag.getAttribute('username')); + + if (user) { + tag.setAttribute('id', user.id()); + tag.setAttribute('displayname', extractText(username(user))); + + return true; + } + } + + _export('filterUserMentions', filterUserMentions); + + function filterPostMentions(tag) { + var post = app.store.getById('posts', tag.getAttribute('id')); + + if (post) { + tag.setAttribute('discussionid', post.discussion().id()); + tag.setAttribute('number', post.number()); + tag.setAttribute('displayname', extractText(username(post.user()))); + + return true; + } + } + + _export('filterPostMentions', filterPostMentions); + + return { + setters: [function (_flarumHelpersUsername) { + username = _flarumHelpersUsername.default; + }, function (_flarumUtilsExtractText) { + extractText = _flarumUtilsExtractText.default; + }], + execute: function () {} + }; }); \ No newline at end of file diff --git a/js/forum/src/addComposerAutocomplete.js b/js/forum/src/addComposerAutocomplete.js index d5d7afd..98c589d 100644 --- a/js/forum/src/addComposerAutocomplete.js +++ b/js/forum/src/addComposerAutocomplete.js @@ -54,14 +54,12 @@ export default function addComposerAutocomplete() { if (this.selectionEnd - cursor > 0) return; - // Search backwards from the cursor for an '@' symbol, without any - // intervening whitespace. If we find one, we will want to show the - // autocomplete dropdown! + // Search backwards from the cursor for an '@' symbol. If we find one, + // we will want to show the autocomplete dropdown! const value = this.value; mentionStart = 0; - for (let i = cursor - 1; i >= 0; i--) { + for (let i = cursor - 1; i >= cursor - 30; i--) { const character = value.substr(i, 1); - if (/\s/.test(character)) break; if (character === '@') { mentionStart = i + 1; break; @@ -95,6 +93,15 @@ export default function addComposerAutocomplete() { ); }; + const userMatches = function(user) { + const names = [ + user.username(), + user.displayName() + ]; + + return names.some(value => value.toLowerCase().substr(0, typed.length) === typed); + }; + const buildSuggestions = () => { const suggestions = []; @@ -102,7 +109,7 @@ export default function addComposerAutocomplete() { // matching that username. if (typed) { app.store.all('users').forEach(user => { - if (user.username().toLowerCase().substr(0, typed.length) !== typed) return; + if (!userMatches(user)) return; suggestions.push( makeSuggestion(user, '@' + user.username(), '', 'MentionsDropdown-user') @@ -122,7 +129,7 @@ export default function addComposerAutocomplete() { .sort((a, b) => b.time() - a.time()) .filter(post => { const user = post.user(); - return user && user.username().toLowerCase().substr(0, typed.length) === typed; + return user && userMatches(user); }) .splice(0, 5) .forEach(post => { diff --git a/js/forum/src/main.js b/js/forum/src/main.js index de8cc4f..181375b 100644 --- a/js/forum/src/main.js +++ b/js/forum/src/main.js @@ -67,5 +67,6 @@ app.initializers.add('flarum-mentions', function() { ); }); + // Remove post mentions when rendering post previews. getPlainContent.removeSelectors.push('a.PostMention'); }); diff --git a/js/forum/src/utils/textFormatter.js b/js/forum/src/utils/textFormatter.js new file mode 100644 index 0000000..747a490 --- /dev/null +++ b/js/forum/src/utils/textFormatter.js @@ -0,0 +1,25 @@ +import username from 'flarum/helpers/username'; +import extractText from 'flarum/utils/extractText'; + +export function filterUserMentions(tag) { + const user = app.store.getBy('users', 'username', tag.getAttribute('username')); + + if (user) { + tag.setAttribute('id', user.id()); + tag.setAttribute('displayname', extractText(username(user))); + + return true; + } +} + +export function filterPostMentions(tag) { + const post = app.store.getById('posts', tag.getAttribute('id')); + + if (post) { + tag.setAttribute('discussionid', post.discussion().id()); + tag.setAttribute('number', post.number()); + tag.setAttribute('displayname', extractText(username(post.user()))); + + return true; + } +} diff --git a/src/Listener/AddPostMentionedByRelationship.php b/src/Listener/AddPostMentionedByRelationship.php index d0d3596..4c7a735 100755 --- a/src/Listener/AddPostMentionedByRelationship.php +++ b/src/Listener/AddPostMentionedByRelationship.php @@ -11,12 +11,10 @@ namespace Flarum\Mentions\Listener; -use Flarum\Api\Controller\CreatePostController; -use Flarum\Api\Controller\ListPostsController; -use Flarum\Api\Controller\ShowDiscussionController; -use Flarum\Api\Controller\ShowPostController; +use Flarum\Api\Controller; use Flarum\Api\Serializer\PostBasicSerializer; use Flarum\Core\Post; +use Flarum\Core\Post\CommentPost; use Flarum\Core\Repository\PostRepository; use Flarum\Core\User; use Flarum\Event\ConfigureApiController; @@ -24,6 +22,7 @@ use Flarum\Event\GetModelRelationship; use Flarum\Event\PrepareApiData; use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Database\Eloquent\Collection; class AddPostMentionedByRelationship { @@ -94,7 +93,7 @@ public function getApiRelationship(GetApiRelationship $event) */ public function includeRelationships(ConfigureApiController $event) { - if ($event->isController(ShowDiscussionController::class)) { + if ($event->isController(Controller\ShowDiscussionController::class)) { $event->addInclude([ 'posts.mentionedBy', 'posts.mentionedBy.user', @@ -102,8 +101,8 @@ public function includeRelationships(ConfigureApiController $event) ]); } - if ($event->isController(ShowPostController::class) - || $event->isController(ListPostsController::class)) { + if ($event->isController(Controller\ShowPostController::class) + || $event->isController(Controller\ListPostsController::class)) { $event->addInclude([ 'mentionedBy', 'mentionedBy.user', @@ -111,7 +110,7 @@ public function includeRelationships(ConfigureApiController $event) ]); } - if ($event->isController(CreatePostController::class)) { + if ($event->isController(Controller\CreatePostController::class)) { $event->addInclude([ 'mentionsPosts', 'mentionsPosts.mentionedBy' @@ -133,22 +132,33 @@ public function filterVisiblePosts(PrepareApiData $event) { // Firstly we gather a list of posts contained within the API document. // This will vary according to the API endpoint that is being accessed. - if ($event->isController(ShowDiscussionController::class)) { + if ($event->isController(Controller\ShowDiscussionController::class)) { $posts = $event->data->posts; - } elseif ($event->isController(ShowPostController::class)) { + } elseif ($event->isController(Controller\ShowPostController::class) + || $event->isController(Controller\CreatePostController::class) + || $event->isController(Controller\UpdatePostController::class)) { $posts = [$event->data]; - } elseif ($event->isController(ListPostsController::class)) { + } elseif ($event->isController(Controller\ListPostsController::class)) { $posts = $event->data; } if (isset($posts)) { - $posts = array_filter((array) $posts, 'is_object'); + $posts = new Collection($posts); + + $posts = $posts->filter(function ($post) { + return $post instanceof CommentPost; + }); + + // Load all of the users that these posts mention. This way the data + // will be ready to go when we need to sub in current usernames + // during the rendering process. + $posts->load(['mentionsUsers', 'mentionsPosts.user']); + + // Construct a list of the IDs of all of the posts that these posts + // have been mentioned in. We can then filter this list of IDs to + // weed out all of the ones which the user is not meant to see. $ids = []; - // Once we have the posts, construct a list of the IDs of all of - // the posts that they have been mentioned in. We can then filter - // this list of IDs to weed out all of the ones which the user is - // not meant to see. foreach ($posts as $post) { $ids = array_merge($ids, $post->mentionedBy->pluck('id')->all()); } diff --git a/src/Listener/FormatPostMentions.php b/src/Listener/FormatPostMentions.php index 888da12..9731303 100755 --- a/src/Listener/FormatPostMentions.php +++ b/src/Listener/FormatPostMentions.php @@ -16,6 +16,7 @@ use Flarum\Event\ConfigureFormatterRenderer; use Flarum\Forum\UrlGenerator; use Illuminate\Contracts\Events\Dispatcher; +use s9e\TextFormatter\Utils; class FormatPostMentions { @@ -48,22 +49,23 @@ public function configure(ConfigureFormatter $event) { $configurator = $event->configurator; + $configurator->rendering->parameters['DISCUSSION_URL'] = $this->url->toRoute('discussion', ['id' => '']); + $tagName = 'POSTMENTION'; $tag = $configurator->tags->add($tagName); $tag->attributes->add('username'); + $tag->attributes->add('displayname'); $tag->attributes->add('number')->filterChain->append('#uint'); $tag->attributes->add('discussionid')->filterChain->append('#uint'); $tag->attributes->add('id')->filterChain->append('#uint'); - $tag->attributes['number']->required = false; - $tag->attributes['discussionid']->required = false; - $tag->template = ''; + $tag->template = ''; $tag->filterChain ->prepend([static::class, 'addId']) - ->setJS('function() { return true; }'); + ->setJS('function(tag) { return System.get("flarum/mentions/utils/textFormatter").filterPostMentions(tag); }'); $configurator->Preg->match('/\B@(?[a-z0-9_-]+)#(?\d+)/i', $tagName); } @@ -73,7 +75,15 @@ public function configure(ConfigureFormatter $event) */ public function render(ConfigureFormatterRenderer $event) { - $event->renderer->setParameter('DISCUSSION_URL', $this->url->toRoute('discussion', ['id' => ''])); + $post = $event->context; + + $event->xml = Utils::replaceAttributes($event->xml, 'POSTMENTION', function ($attributes) use ($post) { + $post = $post->mentionsPosts->find($attributes['id']); + if ($post && $post->user) { + $attributes['displayname'] = $post->user->display_name; + } + return $attributes; + }); } /** @@ -87,6 +97,7 @@ public static function addId($tag) if ($post) { $tag->setAttribute('discussionid', (int) $post->discussion_id); $tag->setAttribute('number', (int) $post->number); + $tag->setAttribute('displayname', $post->user->display_name); return true; } diff --git a/src/Listener/FormatUserMentions.php b/src/Listener/FormatUserMentions.php index 28b279a..83863f0 100755 --- a/src/Listener/FormatUserMentions.php +++ b/src/Listener/FormatUserMentions.php @@ -11,32 +11,25 @@ namespace Flarum\Mentions\Listener; -use Flarum\Core\Repository\UserRepository; +use Flarum\Core\User; use Flarum\Event\ConfigureFormatter; -use Flarum\Event\ConfigureFormatterParser; use Flarum\Event\ConfigureFormatterRenderer; use Flarum\Forum\UrlGenerator; use Illuminate\Contracts\Events\Dispatcher; +use s9e\TextFormatter\Utils; class FormatUserMentions { - /** - * @var UserRepository - */ - protected $users; - /** * @var UrlGenerator */ protected $url; /** - * @param UserRepository $users * @param UrlGenerator $url */ - public function __construct(UserRepository $users, UrlGenerator $url) + public function __construct(UrlGenerator $url) { - $this->users = $users; $this->url = $url; } @@ -46,7 +39,6 @@ public function __construct(UserRepository $users, UrlGenerator $url) public function subscribe(Dispatcher $events) { $events->listen(ConfigureFormatter::class, [$this, 'configure']); - $events->listen(ConfigureFormatterParser::class, [$this, 'parse']); $events->listen(ConfigureFormatterRenderer::class, [$this, 'render']); } @@ -57,35 +49,37 @@ public function configure(ConfigureFormatter $event) { $configurator = $event->configurator; + $configurator->rendering->parameters['PROFILE_URL'] = $this->url->toRoute('user', ['username' => '']); + $tagName = 'USERMENTION'; $tag = $configurator->tags->add($tagName); $tag->attributes->add('username'); + $tag->attributes->add('displayname'); $tag->attributes->add('id')->filterChain->append('#uint'); - $tag->attributes['id']->required = false; - $tag->template = '@'; + $tag->template = '@'; $tag->filterChain->prepend([static::class, 'addId']) ->addParameterByName('userRepository') - ->setJS('function() { return true; }'); + ->setJS('function(tag) { return System.get("flarum/mentions/utils/textFormatter").filterUserMentions(tag); }'); $configurator->Preg->match('/\B@(?[a-z0-9_-]+)(?!#)/i', $tagName); } - /** - * @param ConfigureFormatterParser $event - */ - public function parse(ConfigureFormatterParser $event) - { - $event->parser->registeredVars['userRepository'] = $this->users; - } - /** * @param ConfigureFormatterRenderer $event */ public function render(ConfigureFormatterRenderer $event) { - $event->renderer->setParameter('PROFILE_URL', $this->url->toRoute('user', ['username' => ''])); + $post = $event->context; + + $event->xml = Utils::replaceAttributes($event->xml, 'USERMENTION', function ($attributes) use ($post) { + $user = $post->mentionsUsers->find($attributes['id']); + if ($user) { + $attributes['displayname'] = $user->display_name; + } + return $attributes; + }); } /** @@ -93,10 +87,11 @@ public function render(ConfigureFormatterRenderer $event) * @param UserRepository $users * @return bool */ - public static function addId($tag, UserRepository $users) + public static function addId($tag) { - if ($id = $users->getIdForUsername($tag->getAttribute('username'))) { - $tag->setAttribute('id', $id); + if ($user = User::where('username', 'like', $tag->getAttribute('username'))->first()) { + $tag->setAttribute('id', $user->id); + $tag->setAttribute('displayname', $user->display_name); return true; }