From 620bc7108e3c76abb4caecd79cccbc0e9062dbbc Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sun, 14 Apr 2024 17:32:55 -0300 Subject: [PATCH 1/7] Support custom content attachments that dont require an sgid --- src/AttachableFactory.php | 4 + src/RichTextLaravel.php | 26 ++++ tests/ContentTest.php | 25 ++++ .../app/Models/Opengraph/OpengraphEmbed.php | 104 ++++++++++++++++ .../Opengraph/OpengraphEmbed/Fetching.php | 65 ++++++++++ .../Providers/WorkbenchServiceProvider.php | 13 ++ .../views/chat/partials/trix-input.blade.php | 7 +- .../views/components/app-layout.blade.php | 116 +++++++++++++++++- .../attachables/opengraph_embed.blade.php | 7 ++ .../partials/opengraph-card.blade.php | 9 ++ .../partials/twitter-card.blade.php | 9 ++ workbench/routes/web.php | 20 +++ 12 files changed, 401 insertions(+), 4 deletions(-) create mode 100644 workbench/app/Models/Opengraph/OpengraphEmbed.php create mode 100644 workbench/app/Models/Opengraph/OpengraphEmbed/Fetching.php create mode 100644 workbench/resources/views/rich-text-laravel/attachables/opengraph_embed.blade.php create mode 100644 workbench/resources/views/rich-text-laravel/attachables/partials/opengraph-card.blade.php create mode 100644 workbench/resources/views/rich-text-laravel/attachables/partials/twitter-card.blade.php diff --git a/src/AttachableFactory.php b/src/AttachableFactory.php index e343b2f..d0fde31 100644 --- a/src/AttachableFactory.php +++ b/src/AttachableFactory.php @@ -12,6 +12,10 @@ class AttachableFactory { public static function fromNode(DOMElement $node): Attachables\AttachableContract { + if ($attachable = RichTextLaravel::attachableFromCustomResolver($node)) { + return $attachable; + } + if ($node->hasAttribute('sgid') && $attachable = static::attachableFromSgid($node->getAttribute('sgid'))) { return $attachable; } diff --git a/src/RichTextLaravel.php b/src/RichTextLaravel.php index 724f542..7960499 100644 --- a/src/RichTextLaravel.php +++ b/src/RichTextLaravel.php @@ -2,7 +2,9 @@ namespace Tonysm\RichTextLaravel; +use DOMElement; use Illuminate\Support\Facades\Crypt; +use Tonysm\RichTextLaravel\Attachables\AttachableContract; class RichTextLaravel { @@ -20,6 +22,13 @@ class RichTextLaravel */ protected static $decryptHandler; + /** + * The custom content attachment resolver (if any). + * + * @var callable + */ + protected static $customAttachablesResolver; + /** * Override the way the package handles encryption. */ @@ -55,4 +64,21 @@ public static function decrypt($value, $model, $key): ?string return $value ? call_user_func($decrypt, $value, $model, $key) : $value; } + + public static function withCustomAttachables($customAttachablesResolver): void + { + static::$customAttachablesResolver = $customAttachablesResolver; + } + + public static function clearCustomAttachables(): void + { + static::withCustomAttachables(null); + } + + public static function attachableFromCustomResolver(DOMElement $node): ?AttachableContract + { + $resolver = static::$customAttachablesResolver ?? fn () => null; + + return $resolver($node); + } } diff --git a/tests/ContentTest.php b/tests/ContentTest.php index c1f4a8c..af4182d 100644 --- a/tests/ContentTest.php +++ b/tests/ContentTest.php @@ -6,6 +6,7 @@ use Tonysm\RichTextLaravel\Attachables\RemoteImage; use Tonysm\RichTextLaravel\Attachment; use Tonysm\RichTextLaravel\Content; +use Workbench\App\Models\Opengraph\OpengraphEmbed; use Workbench\App\Models\User; use Workbench\Database\Factories\UserFactory; @@ -544,6 +545,30 @@ public function renders_horizontal_rules_for_trix() HTML, $content->toTrixHtml()); } + /** @test */ + public function supports_custom_content_attachments_without_sgid() + { + $contentType = OpengraphEmbed::CONTENT_TYPE; + + $content = $this->fromHtml(<< + Testing out with cards: https://github.com/tonysm/rich-text-laravel + + + HTML); + + $this->assertCount(1, $content->attachments()); + $this->assertCount(1, $content->attachables()); + $this->assertInstanceOf(OpengraphEmbed::class, $content->attachables()->first()); + $this->assertEquals('https://github.com/tonysm/rich-text-laravel', $content->attachables()->first()->href); + } + private function withAttachmentTagName(string $tagName, callable $callback) { try { diff --git a/workbench/app/Models/Opengraph/OpengraphEmbed.php b/workbench/app/Models/Opengraph/OpengraphEmbed.php new file mode 100644 index 0000000..c7045a3 --- /dev/null +++ b/workbench/app/Models/Opengraph/OpengraphEmbed.php @@ -0,0 +1,104 @@ +hasAttribute('content-type') && $node->getAttribute('content-type') === static::CONTENT_TYPE) { + return new OpengraphEmbed(...static::attributesFromNode($node)); + } + + return null; + } + + public static function tryFromAttributes(array $attributes) + { + if (validator($attributes, [ + 'title' => ['required'], + 'url' => ['required'], + 'description' => ['required'], + 'image' => ['sometimes', 'required', 'url'], + ])->fails()) { + return null; + } + + return new static( + $attributes['url'], + $attributes['image'] ?? null, + $attributes['title'], + $attributes['description'], + ); + } + + private static function attributesFromNode(DOMElement $node): array + { + return [ + 'href' => $node->getAttribute('href'), + 'url' => $node->getAttribute('url'), + 'filename' => $node->getAttribute('filename'), + 'description' => $node->getAttribute('caption'), + ]; + } + + public function __construct( + public $href, + public $url, + public $filename, + public $description, + ) {} + + public function toRichTextAttributes(array $attributes): array + { + return collect($attributes) + ->replace([ + 'content_type' => $this->richTextContentType(), + 'previewable' => true, + ]) + ->filter() + ->all(); + } + + public function equalsToAttachable(AttachableContract $attachable): bool + { + return $this->richTextRender() === $attachable->richTextRender(); + } + + public function richTextRender(array $options = []): string + { + return view('rich-text-laravel.attachables.opengraph_embed', [ + 'attachable' => $this + ])->render(); + } + + public function richTextAsPlainText(?string $caption = null): string + { + return ''; + } + + public function richTextContentType(): string + { + return static::CONTENT_TYPE; + } + + public function toArray(): array + { + return [ + 'href' => $this->href, + 'url' => $this->url, + 'filename' => $this->filename, + 'description' => $this->description, + 'contentType' => $this->richTextContentType(), + 'content' => $this->richTextRender(), + ]; + } +} diff --git a/workbench/app/Models/Opengraph/OpengraphEmbed/Fetching.php b/workbench/app/Models/Opengraph/OpengraphEmbed/Fetching.php new file mode 100644 index 0000000..3e00fa0 --- /dev/null +++ b/workbench/app/Models/Opengraph/OpengraphEmbed/Fetching.php @@ -0,0 +1,65 @@ +maxRedirects(10) + ->get(static::replaceTwitterDomainFromTweetUrls($url)) + ->throw() + ->body() + ); + } + + private static function replaceTwitterDomainFromTweetUrls(string $url) + { + $domains = [ + 'www.x.com', + 'www.twitter.com', + 'twitter.com', + 'x.com', + ]; + + $host = parse_url($url)['host']; + + return in_array($host, $domains, strict: true) + ? str_replace($host, 'fxtwitter.com', $url) + : $url; + } + + private static function extractAttributesFromDocument(DOMDocument $document): array + { + $xpath = new DOMXPath($document); + $openGraphTags = $xpath->query("//meta[starts-with(@property, \"og:\") or starts-with(@name, \"og:\")]"); + $attributes = []; + + foreach ($openGraphTags as $tag) { + if (! $tag->hasAttribute('content')) continue; + + $key = str_replace('og:', '', $tag->hasAttribute('property') ? $tag->getAttribute('property') : $tag->getAttribute('name')); + + if (! in_array($key, OpengraphEmbed::ATTRIBUTES, true)) continue; + + $attributes[$key] = $tag->getAttribute('content'); + } + + return $attributes; + } +} diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php index 7eefcf6..de6b0dd 100644 --- a/workbench/app/Providers/WorkbenchServiceProvider.php +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -2,10 +2,13 @@ namespace Workbench\App\Providers; +use DOMElement; use Illuminate\Contracts\Foundation\Application; use Illuminate\Support\ServiceProvider; use Livewire\LivewireManager; +use Tonysm\RichTextLaravel\RichTextLaravel; use Workbench\App\Livewire\Posts; +use Workbench\App\Models\Opengraph\OpengraphEmbed; class WorkbenchServiceProvider extends ServiceProvider { @@ -23,6 +26,7 @@ public function register(): void public function boot(): void { $this->registerComponents(); + $this->registerCustomRichTextAttachables(); } public function registerComponents(): void @@ -31,4 +35,13 @@ public function registerComponents(): void $livewire->component('posts.index', Posts::class); }); } + + public function registerCustomRichTextAttachables(): void + { + RichTextLaravel::withCustomAttachables(function (DOMElement $node) { + if ($attachable = OpengraphEmbed::fromNode($node)) { + return $attachable; + } + }); + } } diff --git a/workbench/resources/views/chat/partials/trix-input.blade.php b/workbench/resources/views/chat/partials/trix-input.blade.php index 837784d..d7e36ad 100644 --- a/workbench/resources/views/chat/partials/trix-input.blade.php +++ b/workbench/resources/views/chat/partials/trix-input.blade.php @@ -2,14 +2,15 @@ diff --git a/workbench/resources/views/components/app-layout.blade.php b/workbench/resources/views/components/app-layout.blade.php index 5db3486..512300a 100644 --- a/workbench/resources/views/components/app-layout.blade.php +++ b/workbench/resources/views/components/app-layout.blade.php @@ -29,6 +29,25 @@ } + + {{-- Tribute's Styles... --}} @@ -47,7 +66,7 @@ Controller } from 'https://cdn.skypack.dev/@hotwired/stimulus' import Tribute from 'https://ga.jspm.io/npm:tributejs@5.1.3/dist/tribute.min.js' - import Trix from 'https://ga.jspm.io/npm:trix@2.0.10/dist/trix.esm.min.js' + import Trix from 'https://ga.jspm.io/npm:trix@2.1.0/dist/trix.esm.min.js' [...document.querySelectorAll('[js-cloak]')].forEach(el => el.removeAttribute('js-cloak')) @@ -235,6 +254,101 @@ class UploadManager { return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; } }) + + Stimulus.register("oembed", class extends Controller { + static targets = ["text"] + + #abortController + + pasted(event) { + const { range } = event.paste + + if (! range) return + + const url = this.#urlFromRange(range) + + if (! url) return + + this.#convertToLink(url, range) + this.#processOembed(url) + } + + #urlFromRange(range) { + const document = this.#editor.getDocument() + + const string = document.getStringAtRange(range) + const trimmed = string.trim() + + const startOffset = range[0] + string.indexOf(trimmed) + const endOffset = startOffset + trimmed.length + + const url = document.getStringAtRange([startOffset, endOffset]) + + if (! this.#isValidUrl(url)) { + return null; + } + + return url; + } + + #isValidUrl(url) { + return /^(?:[a-z0-9]+:\/\/|www\.)[^\s]+$/.test(url) + } + + #convertToLink(url, range) { + const currentRange = this.#editor.getSelectedRange() + + this.#editor.setSelectedRange(range) + this.#editor.recordUndoEntry("Convert Pasted URL to Link") + this.#editor.activateAttribute('href', url) + this.#editor.setSelectedRange(currentRange) + } + + #processOembed(url) { + this.#fetchOpengraphMetadata(url) + .then((response) => response.json()) + .then(this.#insertOpengraphAttachment.bind(this)) + .catch(() => null) + } + + #fetchOpengraphMetadata(url) { + this.#abortController?.abort() + this.#abortController = new AbortController() + + return fetch('/opengraph-embeds', { + method: 'POST', + body: JSON.stringify({ url }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-CSRF-TOKEN': document.head.querySelector('[name=csrf-token]').content, + }, + signal: this.#abortController.signal, + }) + } + + #insertOpengraphAttachment(attributes) { + if (! this.#shouldInsertOpengraphAttachment(attributes.href)) return; + + const currentRange = this.#editor.getSelectedRange() + + this.#editor.setSelectedRange(this.#editor.getSelectedRange()) + this.#editor.recordUndoEntry("Insert Opengraph preview for Pasted URL") + this.#editor.insertAttachment(new Trix.Attachment({ + ...attributes, + caption: attributes.description, + })) + this.#editor.setSelectedRange(currentRange) + } + + #shouldInsertOpengraphAttachment(url) { + return this.#editor.getDocument().toString().includes(url) + } + + get #editor() { + return this.textTarget.editor + } + }) {{ $head ?? '' }} diff --git a/workbench/resources/views/rich-text-laravel/attachables/opengraph_embed.blade.php b/workbench/resources/views/rich-text-laravel/attachables/opengraph_embed.blade.php new file mode 100644 index 0000000..cdbd7a5 --- /dev/null +++ b/workbench/resources/views/rich-text-laravel/attachables/opengraph_embed.blade.php @@ -0,0 +1,7 @@ +
+ @if (str_contains($attachable->href, 'twitter.com') || str_contains($attachable->href, 'x.com')) + @include('rich-text-laravel.attachables.partials.twitter-card', ['attachable' => $attachable]) + @else + @include('rich-text-laravel.attachables.partials.opengraph-card', ['attachable' => $attachable]) + @endif +
diff --git a/workbench/resources/views/rich-text-laravel/attachables/partials/opengraph-card.blade.php b/workbench/resources/views/rich-text-laravel/attachables/partials/opengraph-card.blade.php new file mode 100644 index 0000000..07ae5ac --- /dev/null +++ b/workbench/resources/views/rich-text-laravel/attachables/partials/opengraph-card.blade.php @@ -0,0 +1,9 @@ +
+ @if ($attachable->url) + + @endif +
+ +
{{ $attachable->description }}
+
+
diff --git a/workbench/resources/views/rich-text-laravel/attachables/partials/twitter-card.blade.php b/workbench/resources/views/rich-text-laravel/attachables/partials/twitter-card.blade.php new file mode 100644 index 0000000..3b568b8 --- /dev/null +++ b/workbench/resources/views/rich-text-laravel/attachables/partials/twitter-card.blade.php @@ -0,0 +1,9 @@ +
+
+ +
{{ $attachable->description }}
+
+ @if ($attachable->url) + + @endif +
diff --git a/workbench/routes/web.php b/workbench/routes/web.php index add152c..0654635 100644 --- a/workbench/routes/web.php +++ b/workbench/routes/web.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Storage; use Workbench\App\Models\Message; +use Workbench\App\Models\Opengraph\OpengraphEmbed; use Workbench\App\Models\Post; use Workbench\App\Models\User; @@ -133,3 +134,22 @@ return response()->stream(fn () => fpassthru($stream), 200, $headers); })->name('attachments.show')->where('path', '.*'); + +Route::post('/opengraph-embeds', function (Request $request) { + $request->validate(['url' => [ + 'bail', 'required', 'url', function (string $attribute, mixed $value, Closure $fail) { + $ip = gethostbyname($host = parse_url($value)['host']); + + // Prevent sniffing domains resolved to private IP ranges... + if ($ip === $host || filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) { + $fail(__('URL is invalid.')); + } + }, + ]]); + + if ($opengraph = OpengraphEmbed::createFromUrl($request->url)) { + return $opengraph->toArray(); + } + + return response()->noContent(); +}); From c06ddbefdadfd8139b3dc0990ec959f329a4124c Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sun, 14 Apr 2024 17:35:31 -0300 Subject: [PATCH 2/7] Pint --- workbench/app/Models/Opengraph/OpengraphEmbed.php | 6 ++++-- .../app/Models/Opengraph/OpengraphEmbed/Fetching.php | 10 +++++++--- workbench/routes/web.php | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/workbench/app/Models/Opengraph/OpengraphEmbed.php b/workbench/app/Models/Opengraph/OpengraphEmbed.php index c7045a3..10313db 100644 --- a/workbench/app/Models/Opengraph/OpengraphEmbed.php +++ b/workbench/app/Models/Opengraph/OpengraphEmbed.php @@ -10,6 +10,7 @@ class OpengraphEmbed implements AttachableContract use OpengraphEmbed\Fetching; const ATTRIBUTES = ['title', 'url', 'image', 'description']; + const CONTENT_TYPE = 'application/vnd.rich-text-laravel.opengraph-embed'; public static function fromNode(DOMElement $node): ?OpengraphEmbed @@ -55,7 +56,8 @@ public function __construct( public $url, public $filename, public $description, - ) {} + ) { + } public function toRichTextAttributes(array $attributes): array { @@ -76,7 +78,7 @@ public function equalsToAttachable(AttachableContract $attachable): bool public function richTextRender(array $options = []): string { return view('rich-text-laravel.attachables.opengraph_embed', [ - 'attachable' => $this + 'attachable' => $this, ])->render(); } diff --git a/workbench/app/Models/Opengraph/OpengraphEmbed/Fetching.php b/workbench/app/Models/Opengraph/OpengraphEmbed/Fetching.php index 3e00fa0..c25d287 100644 --- a/workbench/app/Models/Opengraph/OpengraphEmbed/Fetching.php +++ b/workbench/app/Models/Opengraph/OpengraphEmbed/Fetching.php @@ -47,15 +47,19 @@ private static function replaceTwitterDomainFromTweetUrls(string $url) private static function extractAttributesFromDocument(DOMDocument $document): array { $xpath = new DOMXPath($document); - $openGraphTags = $xpath->query("//meta[starts-with(@property, \"og:\") or starts-with(@name, \"og:\")]"); + $openGraphTags = $xpath->query('//meta[starts-with(@property, "og:") or starts-with(@name, "og:")]'); $attributes = []; foreach ($openGraphTags as $tag) { - if (! $tag->hasAttribute('content')) continue; + if (! $tag->hasAttribute('content')) { + continue; + } $key = str_replace('og:', '', $tag->hasAttribute('property') ? $tag->getAttribute('property') : $tag->getAttribute('name')); - if (! in_array($key, OpengraphEmbed::ATTRIBUTES, true)) continue; + if (! in_array($key, OpengraphEmbed::ATTRIBUTES, true)) { + continue; + } $attributes[$key] = $tag->getAttribute('content'); } diff --git a/workbench/routes/web.php b/workbench/routes/web.php index 0654635..9a8635c 100644 --- a/workbench/routes/web.php +++ b/workbench/routes/web.php @@ -137,7 +137,7 @@ Route::post('/opengraph-embeds', function (Request $request) { $request->validate(['url' => [ - 'bail', 'required', 'url', function (string $attribute, mixed $value, Closure $fail) { + 'bail', 'required', 'url', function (string $attribute, mixed $value, Closure $fail) { $ip = gethostbyname($host = parse_url($value)['host']); // Prevent sniffing domains resolved to private IP ranges... From 83c64069a595aef8790e6611d70f83d381bee25d Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sun, 14 Apr 2024 17:50:22 -0300 Subject: [PATCH 3/7] Abort oembed request on disconnect and adds overflow-auto back --- workbench/resources/views/chat/partials/trix-input.blade.php | 2 +- workbench/resources/views/components/app-layout.blade.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/workbench/resources/views/chat/partials/trix-input.blade.php b/workbench/resources/views/chat/partials/trix-input.blade.php index d7e36ad..e9b0fb9 100644 --- a/workbench/resources/views/chat/partials/trix-input.blade.php +++ b/workbench/resources/views/chat/partials/trix-input.blade.php @@ -10,7 +10,7 @@ name="content" toolbar="create_message_toolbar" input="create_message_input" - class="trix-content rounded-0 p-0 [&_pre]:text-sm min-h-0 max-h-[90vh] border-0 sm:group-data-[composer-show-toolbar-value=true]:py-2 sm:group-data-[composer-show-toolbar-value=true]:min-h-[4em]" + class="trix-content overflow-auto rounded-0 p-0 [&_pre]:text-sm min-h-0 max-h-[90vh] border-0 sm:group-data-[composer-show-toolbar-value=true]:py-2 sm:group-data-[composer-show-toolbar-value=true]:min-h-[4em]" > diff --git a/workbench/resources/views/components/app-layout.blade.php b/workbench/resources/views/components/app-layout.blade.php index 512300a..36ec813 100644 --- a/workbench/resources/views/components/app-layout.blade.php +++ b/workbench/resources/views/components/app-layout.blade.php @@ -260,6 +260,10 @@ class UploadManager { #abortController + disconnect() { + this.#abortController?.abort() + } + pasted(event) { const { range } = event.paste From 0734799b1b0d11c43e15fba248fb770b0728ac76 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sun, 14 Apr 2024 17:53:18 -0300 Subject: [PATCH 4/7] Adds types --- src/RichTextLaravel.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/RichTextLaravel.php b/src/RichTextLaravel.php index 7960499..c2186fc 100644 --- a/src/RichTextLaravel.php +++ b/src/RichTextLaravel.php @@ -2,6 +2,7 @@ namespace Tonysm\RichTextLaravel; +use Closure; use DOMElement; use Illuminate\Support\Facades\Crypt; use Tonysm\RichTextLaravel\Attachables\AttachableContract; @@ -65,7 +66,7 @@ public static function decrypt($value, $model, $key): ?string return $value ? call_user_func($decrypt, $value, $model, $key) : $value; } - public static function withCustomAttachables($customAttachablesResolver): void + public static function withCustomAttachables(Closure|callable|null $customAttachablesResolver): void { static::$customAttachablesResolver = $customAttachablesResolver; } From fe36510c69cc30a9109da55e76b91b85d12d5d87 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sun, 14 Apr 2024 18:10:25 -0300 Subject: [PATCH 5/7] Docs --- README.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/README.md b/README.md index 5827871..d3edb87 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,56 @@ $post->body->galleryAttachments() ->map(fn (Attachment $attachment) => $attachment->attachable) ``` +#### Custom Content Attachments Without SGIDs + +You may want to attach resources that don't need to be stored in the database. One example of this is perhaps storing the OpenGraph Embed of links in a chat message. You probably don't want to store each OpenGraph Embed as its own database record. For cases like this, where the integraty of the data isn't necessarily key, you may register a custom attachment resolver: + +```php +use Illuminate\Support\ServiceProvider; +use Tonysm\RichTextLaravel\RichTextLaravel; + +class AppServiceProvider extends ServiceProvider +{ + public function boot() + { + RichTextLaravel::withCustomAttachables(function (DOMElement $node) { + if ($attachable = OpengraphEmbed::fromNode($node)) { + return $attachable; + } + }); + } +} +``` + +This resolver must either return an instance of an `AttachableContract` implementation or `null` if the node doesn't match your attachment. In this case of an `OpengraphEmbed`, this would look something like this: + +```php +hasAttribute('content-type') && $node->getAttribute('content-type') === static::CONTENT_TYPE) { + return new OpengraphEmbed(...static::attributesFromNode($node)); + } + + return null; + } + + // ... +} +``` + +You can see a full working implementation of this OpenGraph example in the Chat workbench demo (or in [this PR](https://github.com/tonysm/rich-text-laravel/pull/56)). + ### Plain Text Rendering From 09d58a4d431ac9d002cb8c46275f53349e894b1c Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sun, 14 Apr 2024 18:11:29 -0300 Subject: [PATCH 6/7] Tweaks --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index d3edb87..c13028b 100644 --- a/README.md +++ b/README.md @@ -401,10 +401,6 @@ class AppServiceProvider extends ServiceProvider This resolver must either return an instance of an `AttachableContract` implementation or `null` if the node doesn't match your attachment. In this case of an `OpengraphEmbed`, this would look something like this: ```php - From 2112f0da337b6c05a378938df3f7f100425efd8c Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sun, 14 Apr 2024 18:13:58 -0300 Subject: [PATCH 7/7] docs --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c13028b..6bc4318 100644 --- a/README.md +++ b/README.md @@ -382,6 +382,7 @@ $post->body->galleryAttachments() You may want to attach resources that don't need to be stored in the database. One example of this is perhaps storing the OpenGraph Embed of links in a chat message. You probably don't want to store each OpenGraph Embed as its own database record. For cases like this, where the integraty of the data isn't necessarily key, you may register a custom attachment resolver: ```php +use App\Models\Opengraph\OpengraphEmbed; use Illuminate\Support\ServiceProvider; use Tonysm\RichTextLaravel\RichTextLaravel; @@ -401,6 +402,8 @@ class AppServiceProvider extends ServiceProvider This resolver must either return an instance of an `AttachableContract` implementation or `null` if the node doesn't match your attachment. In this case of an `OpengraphEmbed`, this would look something like this: ```php +namespace App\Models\Opengraph; + use DOMElement; use Tonysm\RichTextLaravel\Attachables\AttachableContract;