From 408d882598ef37cebddf7206c857cf1190955459 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Tue, 19 Sep 2017 20:16:30 +0930 Subject: [PATCH] Update to sub in "display names" from database when rendering This also improves the behaviour of mentions in the JS preview (no more broken links, mention is only picked up if corresponding user/post is found). See flarum/core#1246 Closes flarum/core#315 --- .../mentions/js/forum/dist/extension.js | 69 ++++++++++++++++--- .../js/forum/src/addComposerAutocomplete.js | 21 ++++-- extensions/mentions/js/forum/src/main.js | 1 + .../js/forum/src/utils/textFormatter.js | 25 +++++++ .../AddPostMentionedByRelationship.php | 42 ++++++----- .../src/Listener/FormatPostMentions.php | 21 ++++-- .../src/Listener/FormatUserMentions.php | 47 ++++++------- 7 files changed, 164 insertions(+), 62 deletions(-) create mode 100644 extensions/mentions/js/forum/src/utils/textFormatter.js diff --git a/extensions/mentions/js/forum/dist/extension.js b/extensions/mentions/js/forum/dist/extension.js index 5a089eaa7..8e6668a7a 100644 --- a/extensions/mentions/js/forum/dist/extension.js +++ b/extensions/mentions/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/extensions/mentions/js/forum/src/addComposerAutocomplete.js b/extensions/mentions/js/forum/src/addComposerAutocomplete.js index d5d7afd05..98c589d76 100644 --- a/extensions/mentions/js/forum/src/addComposerAutocomplete.js +++ b/extensions/mentions/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/extensions/mentions/js/forum/src/main.js b/extensions/mentions/js/forum/src/main.js index de8cc4fa0..181375bf1 100644 --- a/extensions/mentions/js/forum/src/main.js +++ b/extensions/mentions/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/extensions/mentions/js/forum/src/utils/textFormatter.js b/extensions/mentions/js/forum/src/utils/textFormatter.js new file mode 100644 index 000000000..747a490a9 --- /dev/null +++ b/extensions/mentions/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/extensions/mentions/src/Listener/AddPostMentionedByRelationship.php b/extensions/mentions/src/Listener/AddPostMentionedByRelationship.php index d0d359639..4c7a7356f 100755 --- a/extensions/mentions/src/Listener/AddPostMentionedByRelationship.php +++ b/extensions/mentions/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\GetApiRelationship; use Flarum\Event\GetModelRelationship; use Flarum\Event\PrepareApiData; use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Database\Eloquent\Collection; class AddPostMentionedByRelationship { @@ -94,7 +93,7 @@ class AddPostMentionedByRelationship */ 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 @@ class AddPostMentionedByRelationship ]); } - 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 @@ class AddPostMentionedByRelationship ]); } - if ($event->isController(CreatePostController::class)) { + if ($event->isController(Controller\CreatePostController::class)) { $event->addInclude([ 'mentionsPosts', 'mentionsPosts.mentionedBy' @@ -133,22 +132,33 @@ class AddPostMentionedByRelationship { // 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/extensions/mentions/src/Listener/FormatPostMentions.php b/extensions/mentions/src/Listener/FormatPostMentions.php index 888da12ae..9731303be 100755 --- a/extensions/mentions/src/Listener/FormatPostMentions.php +++ b/extensions/mentions/src/Listener/FormatPostMentions.php @@ -16,6 +16,7 @@ use Flarum\Event\ConfigureFormatter; use Flarum\Event\ConfigureFormatterRenderer; use Flarum\Forum\UrlGenerator; use Illuminate\Contracts\Events\Dispatcher; +use s9e\TextFormatter\Utils; class FormatPostMentions { @@ -48,22 +49,23 @@ class FormatPostMentions { $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 @@ class FormatPostMentions */ 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 @@ class FormatPostMentions 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/extensions/mentions/src/Listener/FormatUserMentions.php b/extensions/mentions/src/Listener/FormatUserMentions.php index 28b279a95..83863f0be 100755 --- a/extensions/mentions/src/Listener/FormatUserMentions.php +++ b/extensions/mentions/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 @@ class FormatUserMentions 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 @@ class FormatUserMentions { $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 @@ class FormatUserMentions * @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; }