Use CommonMark plugin for @-mentions

This commit is contained in:
Jonny Barnes 2022-07-08 16:37:38 +01:00
parent 08b3691aeb
commit 3ff4149304
Signed by: jonny
SSH key fingerprint: SHA256:CTuSlns5U7qlD9jqHvtnVmfYV3Zwl2Z7WnJ4/dqOaL8
6 changed files with 89 additions and 17 deletions

View file

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\CommonMark\Generators;
use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
use League\CommonMark\Extension\Mention\Mention;
use League\CommonMark\Node\Inline\AbstractInline;
class ContactMentionGenerator implements MentionGeneratorInterface
{
public function generateMention(Mention $mention): ?AbstractInline
{
return $mention;
}
}

View file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\CommonMark\Renderers;
use App\Models\Contact;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
class ContactMentionRenderer implements NodeRendererInterface
{
public function render(Node $node, ChildNodeRendererInterface $childRenderer): string
{
$contact = Contact::where('nick', $node->getIdentifier())->first();
if ($contact === null) {
return '<a href="https://twitter.com/' . $node->getIdentifier() . '">@' . $node->getIdentifier() . '</a>';
}
return trim(view('templates.mini-hcard', ['contact' => $contact])->render());
}
}

View file

@ -4,10 +4,9 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
class Contact extends Model class Contact extends Model
{ {
@ -26,4 +25,20 @@ class Contact extends Model
* @var array * @var array
*/ */
protected $fillable = ['nick', 'name', 'homepage', 'twitter', 'facebook']; protected $fillable = ['nick', 'name', 'homepage', 'twitter', 'facebook'];
protected function photo(): Attribute
{
$photo = '/assets/profile-images/default-image';
if (array_key_exists('homepage', $this->attributes) && !empty($this->attributes['homepage'])) {
$host = parse_url($this->attributes['homepage'], PHP_URL_HOST);
if (file_exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
$photo = '/assets/profile-images/' . $host . '/image';
}
}
return Attribute::make(
get: fn () => $photo,
);
}
} }

View file

@ -4,13 +4,14 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use App\CommonMark\Generators\ContactMentionGenerator;
use App\CommonMark\Renderers\ContactMentionRenderer;
use App\Exceptions\TwitterContentException; use App\Exceptions\TwitterContentException;
use Barryvdh\LaravelIdeHelper\Eloquent;
use Codebird\Codebird; use Codebird\Codebird;
use Exception; use Exception;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Illuminate\Database\Eloquent\Relations\{BelongsTo, BelongsToMany, HasMany, MorphMany};
use Illuminate\Database\Eloquent\{Builder, Factories\HasFactory, Model, SoftDeletes}; use Illuminate\Database\Eloquent\{Builder, Factories\HasFactory, Model, SoftDeletes};
use Illuminate\Database\Eloquent\Relations\{BelongsTo, BelongsToMany, HasMany, MorphMany};
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use JetBrains\PhpStorm\ArrayShape; use JetBrains\PhpStorm\ArrayShape;
use Jonnybarnes\IndieWeb\Numbers; use Jonnybarnes\IndieWeb\Numbers;
@ -19,6 +20,8 @@ use League\CommonMark\Extension\Autolink\AutolinkExtension;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode; use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode; use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode;
use League\CommonMark\Extension\Mention\Mention;
use League\CommonMark\Extension\Mention\MentionExtension;
use League\CommonMark\MarkdownConverter; use League\CommonMark\MarkdownConverter;
use Normalizer; use Normalizer;
use Spatie\CommonMarkHighlighter\{FencedCodeRenderer, IndentedCodeRenderer}; use Spatie\CommonMarkHighlighter\{FencedCodeRenderer, IndentedCodeRenderer};
@ -172,8 +175,7 @@ class Note extends Model
return null; return null;
} }
$hcards = $this->makeHCards($value); $hashtags = $this->autoLinkHashtag($value);
$hashtags = $this->autoLinkHashtag($hcards);
return $this->convertMarkdown($hashtags); return $this->convertMarkdown($hashtags);
} }
@ -377,9 +379,11 @@ class Note extends Model
*/ */
public function getTwitterContentAttribute(): string public function getTwitterContentAttribute(): string
{ {
$this->getContacts();
// check for contacts // check for contacts
if ($this->contacts === null || count($this->contacts) === 0) { if ($this->contacts === null || count($this->contacts) === 0) {
throw new TwitterContentException('There are no contacts for this note'); return '';
} }
// here we check the matched contact from the note corresponds to a contact // here we check the matched contact from the note corresponds to a contact
@ -388,10 +392,10 @@ class Note extends Model
count(array_unique(array_values($this->contacts))) === 1 count(array_unique(array_values($this->contacts))) === 1
&& array_unique(array_values($this->contacts))[0] === null && array_unique(array_values($this->contacts))[0] === null
) { ) {
throw new TwitterContentException('The matched contact is not in the database'); return '';
} }
// swap in twitter usernames // swap in Twitter usernames
$swapped = preg_replace_callback( $swapped = preg_replace_callback(
self::USERNAMES_REGEX, self::USERNAMES_REGEX,
function ($matches) { function ($matches) {
@ -406,7 +410,7 @@ class Note extends Model
return $contact->name; return $contact->name;
}, },
$this->getOriginal('note') $this->getRawOriginal('note')
); );
return $this->convertMarkdown($swapped); return $this->convertMarkdown($swapped);
@ -527,9 +531,21 @@ class Note extends Model
*/ */
private function convertMarkdown(string $note): string private function convertMarkdown(string $note): string
{ {
$environment = new Environment(); $config = [
'mentions' => [
'contacts_handle' => [
'prefix' => '@',
'pattern' => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
'generator' => new ContactMentionGenerator(),
],
],
];
$environment = new Environment($config);
$environment->addExtension(new CommonMarkCoreExtension()); $environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new AutolinkExtension()); $environment->addExtension(new AutolinkExtension());
$environment->addExtension(new MentionExtension());
$environment->addRenderer(Mention::class, new ContactMentionRenderer());
$environment->addRenderer(FencedCode::class, new FencedCodeRenderer()); $environment->addRenderer(FencedCode::class, new FencedCodeRenderer());
$environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer()); $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer());
$markdownConverter = new MarkdownConverter($environment); $markdownConverter = new MarkdownConverter($environment);

View file

@ -15,11 +15,11 @@
@if($media->type == 'download') <p><a class="u-attachment" href="{{ $media->url }}">Download the attached media</a></p>@endif @if($media->type == 'download') <p><a class="u-attachment" href="{{ $media->url }}">Download the attached media</a></p>@endif
@endforeach @endforeach
</div> </div>
@php @if ($note->twitter_content)
try { <div class="p-bridgy-twitter-content">
echo '<div class="p-bridgy-twitter-content">' . $note->twitter_content . '</div>'; {!! $note->twitter_content !!}
} catch (App\Exceptions\TwitterContentException $exception) {} </div>
@endphp @endif
<div class="note-metadata"> <div class="note-metadata">
<div> <div>
<a class="u-url" href="/notes/{{ $note->nb60id }}"><time class="dt-published" datetime="{{ $note->iso8601 }}" title="{{ $note->iso8601 }}">{{ $note->humandiff }}</time></a>@if($note->client) via <a class="client" href="{{ $note->client->client_url }}">{{ $note->client->client_name }}</a>@endif <a class="u-url" href="/notes/{{ $note->nb60id }}"><time class="dt-published" datetime="{{ $note->iso8601 }}" title="{{ $note->iso8601 }}">{{ $note->humandiff }}</time></a>@if($note->client) via <a class="client" href="{{ $note->client->client_url }}">{{ $note->client->client_name }}</a>@endif

View file

@ -25,6 +25,6 @@ class BridgyPosseTest extends TestCase
$response = $this->get($note->longurl); $response = $this->get($note->longurl);
$html = $response->content(); $html = $response->content();
$this->assertStringContainsString('p-bridgy-twitter-content', $html); $this->assertStringContainsString('Hi @joe__', $html);
} }
} }