1
0
mirror of https://github.com/flarum/core.git synced 2025-08-13 20:04:24 +02:00

New mentions format, decouple usernames from mentions (#65)

* Convert user mentions to new `@"Display Name"#ID` format

* Handle deleted user's mentions

* Convert post mentions to `@"Display Name"#pID` format

* Handle deleted user's post mentions and deleted posts mentions

* Clean display name of `"#{letters}{numbers}` (replace with underscore _)

* Adapt integration tests to new mention formats

* Use a deleted attribute for user mentions

* Introduce cleanDisplayName util

* Detect new format with autocomplete

* Slug needed on rendering only

* Invalidate user mention tag when ID is invalid
This used to be implicitly done, when there was a username attribute configured, formatter would check that all attributes are available and if not invalidate.

since we now only have `displayname` and `id` attributes which are both available from the regex matching, formatter doesn't implicitly invalidate anymore and therefore validates ANY matches. So we explicitly invalidate the tag when the ID does not match a user.

* Allow username mention format with a setting

* Add tests for turning setting on/off

* Move setting check to tag filter
Because the configurator caches, changing the setting only takes effect after the cache is cleared.

* fix: showing autocomplete at the right moment

* Add dockblocks to explain unparsing process
This commit is contained in:
Sami Mazouz
2021-04-21 10:58:54 +01:00
committed by GitHub
parent 057517688c
commit e407c66784
18 changed files with 878 additions and 35 deletions

View File

@@ -11,6 +11,7 @@ namespace Flarum\Mentions;
use Flarum\Http\UrlGenerator;
use Flarum\Post\CommentPost;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\User;
use s9e\TextFormatter\Configurator;
@@ -42,14 +43,22 @@ class ConfigureMentions
$tagName = 'USERMENTION';
$tag = $config->tags->add($tagName);
$tag->attributes->add('username');
$tag->attributes->add('displayname');
$tag->attributes->add('id')->filterChain->append('#uint');
$tag->template = '<a href="{$PROFILE_URL}{@username}" class="UserMention">@<xsl:value-of select="@displayname"/></a>';
$tag->template = '
<xsl:choose>
<xsl:when test="@deleted != 1">
<a href="{$PROFILE_URL}{@slug}" class="UserMention">@<xsl:value-of select="@displayname"/></a>
</xsl:when>
<xsl:otherwise>
<span class="UserMention UserMention--deleted">@<xsl:value-of select="@displayname"/></span>
</xsl:otherwise>
</xsl:choose>';
$tag->filterChain->prepend([static::class, 'addUserId'])
->setJS('function(tag) { return flarum.extensions["flarum-mentions"].filterUserMentions(tag); }');
$config->Preg->match('/\B@"(?<displayname>((?!"#[a-z]{0,3}[0-9]+).)+)"#(?<id>[0-9]+)\b/', $tagName);
$config->Preg->match('/\B@(?<username>[a-z0-9_-]+)(?!#)/i', $tagName);
}
@@ -60,12 +69,22 @@ class ConfigureMentions
*/
public static function addUserId($tag)
{
if ($user = User::where('username', $tag->getAttribute('username'))->first()) {
$allow_username_format = (bool) resolve(SettingsRepositoryInterface::class)->get('flarum-mentions.allow_username_format');
if ($tag->hasAttribute('username') && $allow_username_format) {
$user = User::where('username', $tag->getAttribute('username'))->first();
} elseif ($tag->hasAttribute('id')) {
$user = User::find($tag->getAttribute('id'));
}
if (isset($user)) {
$tag->setAttribute('id', $user->id);
$tag->setAttribute('displayname', $user->display_name);
return true;
}
$tag->invalidate();
}
private function configurePostMentions(Configurator $config)
@@ -76,19 +95,26 @@ class ConfigureMentions
$tag = $config->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->template = '<a href="{$DISCUSSION_URL}{@discussionid}/{@number}" class="PostMention" data-id="{@id}"><xsl:value-of select="@displayname"/></a>';
$tag->template = '
<xsl:choose>
<xsl:when test="@deleted != 1">
<a href="{$DISCUSSION_URL}{@discussionid}/{@number}" class="PostMention" data-id="{@id}"><xsl:value-of select="@displayname"/></a>
</xsl:when>
<xsl:otherwise>
<span class="PostMention PostMention--deleted" data-id="{@id}"><xsl:value-of select="@displayname"/></span>
</xsl:otherwise>
</xsl:choose>';
$tag->filterChain
->prepend([static::class, 'addPostId'])
->setJS('function(tag) { return flarum.extensions["flarum-mentions"].filterPostMentions(tag); }');
$config->Preg->match('/\B@(?<username>[a-z0-9_-]+)#(?<id>\d+)/i', $tagName);
$config->Preg->match('/\B@"(?<displayname>((?!"#[a-z]{0,3}[0-9]+).)+)"#p(?<id>[0-9]+)\b/', $tagName);
}
/**

View File

@@ -12,9 +12,20 @@ namespace Flarum\Mentions\Formatter;
use Psr\Http\Message\ServerRequestInterface as Request;
use s9e\TextFormatter\Renderer;
use s9e\TextFormatter\Utils;
use Symfony\Contracts\Translation\TranslatorInterface;
class FormatPostMentions
{
/**
* @var TranslatorInterface
*/
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* Configure rendering for post mentions.
*
@@ -34,6 +45,17 @@ class FormatPostMentions
$attributes['displayname'] = $post->user->display_name;
}
$attributes['deleted'] = false;
if (! $post) {
$attributes['displayname'] = $this->translator->trans('flarum-mentions.forum.post_mention.deleted_text');
$attributes['deleted'] = true;
}
if ($post && ! $post->user) {
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
}
return $attributes;
});
}

View File

@@ -9,29 +9,54 @@
namespace Flarum\Mentions\Formatter;
use Psr\Http\Message\ServerRequestInterface as Request;
use Flarum\Http\SlugManager;
use Flarum\User\User;
use s9e\TextFormatter\Renderer;
use s9e\TextFormatter\Utils;
use Symfony\Contracts\Translation\TranslatorInterface;
class FormatUserMentions
{
/**
* @var SlugManager
*/
private $slugManager;
/**
* @var TranslatorInterface
*/
private $translator;
public function __construct(SlugManager $slugManager, TranslatorInterface $translator)
{
$this->slugManager = $slugManager;
$this->translator = $translator;
}
/**
* Configure rendering for user mentions.
*
* @param s9e\TextFormatter\Renderer $renderer
* @param mixed $context
* @param string|null $xml
* @param Psr\Http\Message\ServerRequestInterface $request
* @return string $xml to be rendered
*/
public function __invoke(Renderer $renderer, $context, $xml, Request $request = null)
public function __invoke(Renderer $renderer, $context, string $xml)
{
$post = $context;
return Utils::replaceAttributes($xml, 'USERMENTION', function ($attributes) use ($post) {
$user = $post->mentionsUsers->find($attributes['id']);
$attributes['deleted'] = false;
if ($user) {
$attributes['username'] = $user->username;
$attributes['slug'] = $this->slugManager->forResource(User::class)->toSlug($user);
$attributes['displayname'] = $user->display_name;
} else {
$attributes['deleted'] = true;
$attributes['slug'] = '';
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
}
return $attributes;

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Mentions\Formatter;
use s9e\TextFormatter\Utils;
use Symfony\Contracts\Translation\TranslatorInterface;
class UnparsePostMentions
{
/**
* @var TranslatorInterface
*/
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* Configure rendering for user mentions.
*
* @param string $xml
* @param mixed $context
* @return string $xml to be unparsed
*/
public function __invoke($context, string $xml)
{
$xml = $this->updatePostMentionTags($context, $xml);
$xml = $this->unparsePostMentionTags($xml);
return $xml;
}
/**
* Updates XML post mention tags before unparsing so that unparsing uses new display names.
*
* @param mixed $context
* @param string $xml : Parsed text.
* @return string $xml : Updated XML tags;
*/
protected function updatePostMentionTags($context, string $xml): string
{
$post = $context;
return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($post) {
$post = $post->mentionsPosts->find($attributes['id']);
if ($post && $post->user) {
$attributes['displayname'] = $post->user->display_name;
}
if (! $post) {
$attributes['displayname'] = $this->translator->trans('flarum-mentions.forum.post_mention.deleted_text');
}
if ($post && ! $post->user) {
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
}
if (strpos($attributes['displayname'], '"#') !== false) {
$attributes['displayname'] = preg_replace('/"#[a-z]{0,3}[0-9]+/', '_', $attributes['displayname']);
}
return $attributes;
});
}
/**
* Transforms post mention tags from XML to raw unparsed content with updated format and display name.
*
* @param string $xml : Parsed text.
* @return string : Unparsed text.
*/
protected function unparsePostMentionTags(string $xml): string
{
$tagName = 'POSTMENTION';
if (strpos($xml, $tagName) === false) {
return $xml;
}
return preg_replace(
'/<'.preg_quote($tagName).'\b[^>]*(?=\bdisplayname="(.*)")[^>]*(?=\bid="([0-9]+)")[^>]*>@[^<]+<\/'.preg_quote($tagName).'>/U',
'@"$1"#p$2',
$xml
);
}
}

View File

@@ -0,0 +1,91 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Mentions\Formatter;
use Flarum\User\User;
use s9e\TextFormatter\Utils;
use Symfony\Contracts\Translation\TranslatorInterface;
class UnparseUserMentions
{
/**
* @var TranslatorInterface
*/
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* Configure rendering for user mentions.
*
* @param string $xml
* @param mixed $context
* @return string $xml to be unparsed
*/
public function __invoke($context, string $xml)
{
$xml = $this->updateUserMentionTags($context, $xml);
$xml = $this->unparseUserMentionTags($xml);
return $xml;
}
/**
* Updates XML user mention tags before unparsing so that unparsing uses new display names.
*
* @param mixed $context
* @param string $xml : Parsed text.
* @return string $xml : Updated XML tags;
*/
protected function updateUserMentionTags($context, string $xml): string
{
$post = $context;
return Utils::replaceAttributes($xml, 'USERMENTION', function ($attributes) use ($post) {
$user = $post->mentionsUsers->find($attributes['id']);
if ($user) {
$attributes['displayname'] = $user->display_name;
} else {
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
}
if (strpos($attributes['displayname'], '"#') !== false) {
$attributes['displayname'] = preg_replace('/"#[a-z]{0,3}[0-9]+/', '_', $attributes['displayname']);
}
return $attributes;
});
}
/**
* Transforms user mention tags from XML to raw unparsed content with updated format and display name.
*
* @param string $xml : Parsed text.
* @return string : Unparsed text.
*/
protected function unparseUserMentionTags(string $xml): string
{
$tagName = 'USERMENTION';
if (strpos($xml, $tagName) === false) {
return $xml;
}
return preg_replace(
'/<'.preg_quote($tagName).'\b[^>]*(?=\bdisplayname="(.*)")[^>]*(?=\bid="([0-9]+)")[^>]*>@[^<]+<\/'.preg_quote($tagName).'>/U',
'@"$1"#$2',
$xml
);
}
}