From 8b1624f00365e8c8eeaebda98c3819a6adc692c6 Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 18 Feb 2025 12:56:42 -0800 Subject: [PATCH 01/19] wip --- .../js/components/fieldtypes/LinkFieldtype.vue | 2 ++ .../components/fieldtypes/bard/LinkToolbar.vue | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/resources/js/components/fieldtypes/LinkFieldtype.vue b/resources/js/components/fieldtypes/LinkFieldtype.vue index 16f09af95a4..4284dbd38e3 100644 --- a/resources/js/components/fieldtypes/LinkFieldtype.vue +++ b/resources/js/components/fieldtypes/LinkFieldtype.vue @@ -34,6 +34,7 @@ @input="entriesSelected" @meta-updated="meta.entry.meta = $event" /> + + +
+ +
+
Date: Wed, 26 Feb 2025 12:20:29 -0800 Subject: [PATCH 02/19] wip --- src/Fieldtypes/Bard.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Fieldtypes/Bard.php b/src/Fieldtypes/Bard.php index d92c3bec0bc..bd5ed765ead 100644 --- a/src/Fieldtypes/Bard.php +++ b/src/Fieldtypes/Bard.php @@ -38,7 +38,9 @@ class Bard extends Replicator ]; protected $categories = ['text', 'structured']; + protected $keywords = ['rich', 'richtext', 'rich text', 'editor', 'wysiwg', 'builder', 'page builder', 'gutenberg', 'content']; + protected $rules = []; protected function configFieldItems(): array @@ -713,7 +715,9 @@ private function getLinkDataForUrl($url) switch ($type) { case 'entry': - if ($entry = Entry::find($id)) { + $actualId = str($id)->before('#')->before('?')->before('&'); + + if ($entry = Entry::find($actualId->toString())) { $data = [ 'title' => $entry->get('title'), 'permalink' => $entry->absoluteUrl(), From 7f59104147151d5bf2050300999617b689af1797 Mon Sep 17 00:00:00 2001 From: Erin Dalzell Date: Thu, 6 Mar 2025 10:01:11 -0800 Subject: [PATCH 03/19] Discard changes to resources/js/components/fieldtypes/LinkFieldtype.vue --- resources/js/components/fieldtypes/LinkFieldtype.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/js/components/fieldtypes/LinkFieldtype.vue b/resources/js/components/fieldtypes/LinkFieldtype.vue index 4284dbd38e3..16f09af95a4 100644 --- a/resources/js/components/fieldtypes/LinkFieldtype.vue +++ b/resources/js/components/fieldtypes/LinkFieldtype.vue @@ -34,7 +34,6 @@ @input="entriesSelected" @meta-updated="meta.entry.meta = $event" /> - Date: Thu, 6 Mar 2025 11:41:59 -0800 Subject: [PATCH 04/19] better --- .../fieldtypes/bard/LinkToolbar.vue | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/resources/js/components/fieldtypes/bard/LinkToolbar.vue b/resources/js/components/fieldtypes/bard/LinkToolbar.vue index d47597a394b..01b7bc30340 100644 --- a/resources/js/components/fieldtypes/bard/LinkToolbar.vue +++ b/resources/js/components/fieldtypes/bard/LinkToolbar.vue @@ -225,7 +225,7 @@ export default { url: {}, urlData: {}, itemData: {}, - append: null, + appends: null, title: null, rel: null, targetBlank: null, @@ -371,10 +371,10 @@ export default { applyAttrs(attrs) { this.linkType = this.getLinkTypeForUrl(attrs.href); - this.url = { [this.linkType]: attrs.href }; + this.appends = this.getAppendsForUrl(attrs.href); + this.url = { [this.linkType]: attrs.href.replace(this.appends, '' ) }; this.urlData = { [this.linkType]: this.getUrlDataForUrl(attrs.href) }; this.itemData = { [this.linkType]: this.getItemDataForUrl(attrs.href) }; - this.title = attrs.title; this.rel = attrs.href ? attrs.rel @@ -521,14 +521,26 @@ export default { return this.bard.meta.linkData[ref]; }, + getAppendsForUrl(urlString) { + const url = URL.parse(urlString.replace('statamic://','')); + const queryParams = url.searchParams.toString(); + + if (queryParams) { + return `?${queryParams}${url.hash}`; + }; + + return url.hash; + }, + parseDataUrl(url) { if (! url) { return {} } + const appends = this.getAppendsForUrl(url); const regex = /^statamic:\/\/((.*?)::(.*))$/; - const matches = url.match(regex); + const matches = url.replace(appends, '').match(regex); if (! matches) { return {}; } From 8f4a1cf6294b1472eca2f60907010d30f1763d55 Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 6 Mar 2025 12:02:49 -0800 Subject: [PATCH 05/19] handle initial states --- resources/js/components/fieldtypes/bard/LinkToolbar.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/js/components/fieldtypes/bard/LinkToolbar.vue b/resources/js/components/fieldtypes/bard/LinkToolbar.vue index 01b7bc30340..dbbec827b9d 100644 --- a/resources/js/components/fieldtypes/bard/LinkToolbar.vue +++ b/resources/js/components/fieldtypes/bard/LinkToolbar.vue @@ -372,7 +372,7 @@ export default { this.linkType = this.getLinkTypeForUrl(attrs.href); this.appends = this.getAppendsForUrl(attrs.href); - this.url = { [this.linkType]: attrs.href.replace(this.appends, '' ) }; + this.url = { [this.linkType]: attrs.href?.replace(this.appends, '' ) }; this.urlData = { [this.linkType]: this.getUrlDataForUrl(attrs.href) }; this.itemData = { [this.linkType]: this.getItemDataForUrl(attrs.href) }; this.title = attrs.title; @@ -522,6 +522,10 @@ export default { }, getAppendsForUrl(urlString) { + if (! urlString) { + return null; + } + const url = URL.parse(urlString.replace('statamic://','')); const queryParams = url.searchParams.toString(); From 6843e9e42ac1e01dcbcbd6a54156e259947da6ba Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 6 Mar 2025 12:17:14 -0800 Subject: [PATCH 06/19] only useful on entry links --- resources/js/components/fieldtypes/bard/LinkToolbar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/fieldtypes/bard/LinkToolbar.vue b/resources/js/components/fieldtypes/bard/LinkToolbar.vue index dbbec827b9d..e7efd35371b 100644 --- a/resources/js/components/fieldtypes/bard/LinkToolbar.vue +++ b/resources/js/components/fieldtypes/bard/LinkToolbar.vue @@ -522,7 +522,7 @@ export default { }, getAppendsForUrl(urlString) { - if (! urlString) { + if (! urlString?.includes('statamic://entry::')) { return null; } From 7544f8be9918bf45181b081902e8ec7166de5680 Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 6 Mar 2025 14:08:22 -0800 Subject: [PATCH 07/19] wangjangle that pesky url --- .../js/components/fieldtypes/bard/BardFieldtype.vue | 2 ++ .../js/components/fieldtypes/bard/LinkToolbar.vue | 11 +++-------- src/Fieldtypes/Bard.php | 5 ++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/resources/js/components/fieldtypes/bard/BardFieldtype.vue b/resources/js/components/fieldtypes/bard/BardFieldtype.vue index 2e7c1b96dcc..88afacea287 100644 --- a/resources/js/components/fieldtypes/bard/BardFieldtype.vue +++ b/resources/js/components/fieldtypes/bard/BardFieldtype.vue @@ -277,6 +277,8 @@ export default { htmlWithReplacedLinks() { return this.html.replaceAll(/\"statamic:\/\/(.*?)\"/g, (match, ref) => { + // Get everything in the "ref" string before a ? or #. + ref = ref.split(/[?#]/)[0] const linkData = this.meta.linkData[ref]; if (! linkData) { this.$toast.error(`${__('No link data found for')} ${ref}`); diff --git a/resources/js/components/fieldtypes/bard/LinkToolbar.vue b/resources/js/components/fieldtypes/bard/LinkToolbar.vue index e7efd35371b..e9a0b5bbab2 100644 --- a/resources/js/components/fieldtypes/bard/LinkToolbar.vue +++ b/resources/js/components/fieldtypes/bard/LinkToolbar.vue @@ -522,18 +522,13 @@ export default { }, getAppendsForUrl(urlString) { + + // appends is only relevant to entry links if (! urlString?.includes('statamic://entry::')) { return null; } - const url = URL.parse(urlString.replace('statamic://','')); - const queryParams = url.searchParams.toString(); - - if (queryParams) { - return `?${queryParams}${url.hash}`; - }; - - return url.hash; + return urlString.replace(urlString.split(/[?#]/)[0], ''); }, parseDataUrl(url) { diff --git a/src/Fieldtypes/Bard.php b/src/Fieldtypes/Bard.php index bd5ed765ead..88e105ebfbc 100644 --- a/src/Fieldtypes/Bard.php +++ b/src/Fieldtypes/Bard.php @@ -715,9 +715,8 @@ private function getLinkDataForUrl($url) switch ($type) { case 'entry': - $actualId = str($id)->before('#')->before('?')->before('&'); - - if ($entry = Entry::find($actualId->toString())) { + $ref = str($ref)->before('?')->before('#')->toString(); + if ($entry = Entry::find(str($ref)->after('entry::')->toString())) { $data = [ 'title' => $entry->get('title'), 'permalink' => $entry->absoluteUrl(), From 4c67efa99b8667c405337f86c3abe7799e9252a9 Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 6 Mar 2025 14:10:38 -0800 Subject: [PATCH 08/19] tidy --- src/Fieldtypes/Bard.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Fieldtypes/Bard.php b/src/Fieldtypes/Bard.php index 88e105ebfbc..1b0c6f7d1a5 100644 --- a/src/Fieldtypes/Bard.php +++ b/src/Fieldtypes/Bard.php @@ -708,15 +708,15 @@ private function extractLinkDataFromNode($node) private function getLinkDataForUrl($url) { - $ref = Str::after($url, 'statamic://'); + $ref = str($url)->after('statamic://'); [$type, $id] = explode('::', $ref, 2); $data = null; switch ($type) { case 'entry': - $ref = str($ref)->before('?')->before('#')->toString(); - if ($entry = Entry::find(str($ref)->after('entry::')->toString())) { + $ref = $ref->before('?')->before('#'); + if ($entry = Entry::find($ref->after('entry::'))) { $data = [ 'title' => $entry->get('title'), 'permalink' => $entry->absoluteUrl(), @@ -733,7 +733,7 @@ private function getLinkDataForUrl($url) break; } - return [$ref => $data]; + return [$ref->toString() => $data]; } private function wrapInlineValue($value) From 07baaa7ab7f4969fc905ceaf15f6b33ed6bf15b0 Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 6 Mar 2025 14:36:36 -0800 Subject: [PATCH 09/19] deal w/ null --- resources/js/components/fieldtypes/bard/LinkToolbar.vue | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/js/components/fieldtypes/bard/LinkToolbar.vue b/resources/js/components/fieldtypes/bard/LinkToolbar.vue index e9a0b5bbab2..af8175a7501 100644 --- a/resources/js/components/fieldtypes/bard/LinkToolbar.vue +++ b/resources/js/components/fieldtypes/bard/LinkToolbar.vue @@ -327,7 +327,11 @@ export default { watch: { - linkType() { + linkType(type) { + if (type != 'entry') { + this.appends = null; + } + this.autofocus(); }, @@ -418,7 +422,7 @@ export default { } this.$emit('updated', { - href: `${this.href}${this.appends}`, + href: this.href + (this.appends ?? ''), rel: this.rel, target: (this.canHaveTarget && this.targetBlank) ? '_blank' : null, title: this.title, From 5463efc506739d4f74e894ae05d4f0d387695ae4 Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 24 Mar 2026 15:39:29 -0700 Subject: [PATCH 10/19] migrate to v6 --- .../fieldtypes/bard/LinkToolbar.vue | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/resources/js/components/fieldtypes/bard/LinkToolbar.vue b/resources/js/components/fieldtypes/bard/LinkToolbar.vue index 2753706489a..ed7effd0cfd 100644 --- a/resources/js/components/fieldtypes/bard/LinkToolbar.vue +++ b/resources/js/components/fieldtypes/bard/LinkToolbar.vue @@ -80,6 +80,16 @@
+ + + Date: Tue, 24 Mar 2026 15:41:22 -0700 Subject: [PATCH 11/19] tidy --- src/Fieldtypes/Bard.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Fieldtypes/Bard.php b/src/Fieldtypes/Bard.php index 3f8d1ef79c8..84a2b8224fc 100644 --- a/src/Fieldtypes/Bard.php +++ b/src/Fieldtypes/Bard.php @@ -45,9 +45,7 @@ class Bard extends Replicator ]; protected $categories = ['text', 'structured']; - protected $keywords = ['rich', 'richtext', 'rich text', 'editor', 'wysiwg', 'builder', 'page builder', 'gutenberg', 'content']; - protected $rules = []; protected function configFieldItems(): array From ebe93d082532c3a2f53f638a28357d167f800e85 Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 24 Mar 2026 16:34:49 -0700 Subject: [PATCH 12/19] need type --- resources/js/components/fieldtypes/bard/LinkToolbar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/fieldtypes/bard/LinkToolbar.vue b/resources/js/components/fieldtypes/bard/LinkToolbar.vue index ed7effd0cfd..4714ec9a8ca 100644 --- a/resources/js/components/fieldtypes/bard/LinkToolbar.vue +++ b/resources/js/components/fieldtypes/bard/LinkToolbar.vue @@ -318,7 +318,7 @@ export default { }, watch: { - linkType() { + linkType(type) { if (type != 'entry') { this.appends = null; } From 9a75b8ea6eda3c4f656c9777aaf06e7d273609a1 Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 24 Mar 2026 16:34:59 -0700 Subject: [PATCH 13/19] append --- src/Fieldtypes/Bard/LinkMark.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Fieldtypes/Bard/LinkMark.php b/src/Fieldtypes/Bard/LinkMark.php index 955011d86f8..14b367aa7f3 100644 --- a/src/Fieldtypes/Bard/LinkMark.php +++ b/src/Fieldtypes/Bard/LinkMark.php @@ -62,19 +62,21 @@ protected function convertHref($href) return $href; } - $ref = Str::after($href, 'statamic://'); + $ref = str($href)->after('statamic://')->before('?')->before('#'); - if (! $item = Data::find($ref)) { + if (! $item = Data::find($ref->toString())) { return ''; } $selectAcrossSites = Augmentor::$currentBardConfig['select_across_sites'] ?? false; + $extras = Str::after($href, $ref); + if (! $selectAcrossSites && ! $this->isApi() && $item instanceof Entry) { - return ($item->in(Site::current()->handle()) ?? $item)->url(); + return ($item->in(Site::current()->handle()) ?? $item)->url().$extras; } - return $selectAcrossSites ? $item->absoluteUrl() : $item->url(); + return $selectAcrossSites ? $item->absoluteUrl().$extras : $item->url().$extras; } private function isApi() From 4868a08df13a4eebe8693b20e661be97cb47e571 Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 24 Mar 2026 16:36:11 -0700 Subject: [PATCH 14/19] simpler --- src/Fieldtypes/Bard/LinkMark.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Fieldtypes/Bard/LinkMark.php b/src/Fieldtypes/Bard/LinkMark.php index 14b367aa7f3..1277d60c8f1 100644 --- a/src/Fieldtypes/Bard/LinkMark.php +++ b/src/Fieldtypes/Bard/LinkMark.php @@ -62,9 +62,9 @@ protected function convertHref($href) return $href; } - $ref = str($href)->after('statamic://')->before('?')->before('#'); + $ref = str($href)->after('statamic://')->before('?')->before('#')->toString(); - if (! $item = Data::find($ref->toString())) { + if (! $item = Data::find($ref)) { return ''; } From eb97f80433f7cc95222d5f14e27e05c4a6c5169b Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 24 Mar 2026 16:39:21 -0700 Subject: [PATCH 15/19] Simpler --- src/Fieldtypes/Bard.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Fieldtypes/Bard.php b/src/Fieldtypes/Bard.php index 84a2b8224fc..67ba03600c1 100644 --- a/src/Fieldtypes/Bard.php +++ b/src/Fieldtypes/Bard.php @@ -788,15 +788,14 @@ private function extractLinkDataFromNode($node) private function getLinkDataForUrl($url) { - $ref = str($url)->after('statamic://'); + $ref = str($url)->after('statamic://')->before('?')->before('#')->toString(); [$type, $id] = explode('::', $ref, 2); $data = null; switch ($type) { case 'entry': - $ref = $ref->before('?')->before('#'); - if ($entry = Entry::find($ref->after('entry::'))) { + if ($entry = Entry::find($ref)) { $data = [ 'title' => $entry->get('title'), 'permalink' => $entry->absoluteUrl(), From 982d4a5c303a51b91a38404b6a6f3d8825fc418b Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 24 Mar 2026 16:39:57 -0700 Subject: [PATCH 16/19] whoops --- src/Fieldtypes/Bard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fieldtypes/Bard.php b/src/Fieldtypes/Bard.php index 67ba03600c1..4808eef7545 100644 --- a/src/Fieldtypes/Bard.php +++ b/src/Fieldtypes/Bard.php @@ -812,7 +812,7 @@ private function getLinkDataForUrl($url) break; } - return [$ref->toString() => $data]; + return [$ref => $data]; } private function wrapInlineValue($value) From 2c7d64cd84be84c873ada2479801b0349ade6570 Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 24 Mar 2026 16:41:34 -0700 Subject: [PATCH 17/19] revert --- src/Fieldtypes/Bard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fieldtypes/Bard.php b/src/Fieldtypes/Bard.php index 4808eef7545..c708632ccc8 100644 --- a/src/Fieldtypes/Bard.php +++ b/src/Fieldtypes/Bard.php @@ -795,7 +795,7 @@ private function getLinkDataForUrl($url) switch ($type) { case 'entry': - if ($entry = Entry::find($ref)) { + if ($entry = Entry::find($id)) { $data = [ 'title' => $entry->get('title'), 'permalink' => $entry->absoluteUrl(), From 3b265d321add83826083d5d975bdf5189e2709d0 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 8 Apr 2026 14:47:40 -0400 Subject: [PATCH 18/19] Fix bugs in Bard entry link appends and add tests - Fix String.replace(null) bug that could mangle URLs containing "null" - Localize the "Append" label - Return null consistently from getAppendsForUrl when no appends exist - Add tests for query params, anchors, and combined appends on entry links Co-Authored-By: Claude Opus 4.6 (1M context) --- .../fieldtypes/bard/LinkToolbar.vue | 8 +-- tests/Fieldtypes/BardTest.php | 62 +++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/resources/js/components/fieldtypes/bard/LinkToolbar.vue b/resources/js/components/fieldtypes/bard/LinkToolbar.vue index 4714ec9a8ca..e6c0b74553b 100644 --- a/resources/js/components/fieldtypes/bard/LinkToolbar.vue +++ b/resources/js/components/fieldtypes/bard/LinkToolbar.vue @@ -86,7 +86,7 @@ type="text" ref="input" v-model="appends" - prepend="Append" + :prepend="__('Append')" :placeholder="__('?query=params#anchor')" /> @@ -365,7 +365,7 @@ export default { applyAttrs(attrs) { this.linkType = this.getLinkTypeForUrl(attrs.href); this.appends = this.getAppendsForUrl(attrs.href); - this.url = { [this.linkType]: attrs.href?.replace(this.appends, '' ) }; + this.url = { [this.linkType]: this.appends ? attrs.href?.replace(this.appends, '') : attrs.href }; this.urlData = { [this.linkType]: this.getUrlDataForUrl(attrs.href) }; this.itemData = { [this.linkType]: this.getItemDataForUrl(attrs.href) }; @@ -515,7 +515,7 @@ export default { return null; } - return urlString.replace(urlString.split(/[?#]/)[0], ''); + return urlString.replace(urlString.split(/[?#]/)[0], '') || null; }, parseDataUrl(url) { @@ -526,7 +526,7 @@ export default { const appends = this.getAppendsForUrl(url); const regex = /^statamic:\/\/((.*?)::(.*))$/; - const matches = url.replace(appends, '').match(regex); + const matches = (appends ? url.replace(appends, '') : url).match(regex); if (!matches) { return {}; } diff --git a/tests/Fieldtypes/BardTest.php b/tests/Fieldtypes/BardTest.php index 7eca35f1d5a..31ffd397f32 100644 --- a/tests/Fieldtypes/BardTest.php +++ b/tests/Fieldtypes/BardTest.php @@ -1351,6 +1351,68 @@ public function it_doesnt_localize_when_select_across_sites_setting_is_enabled() $this->assertEquals('The One', $augmented); } + #[Test] + public function it_preserves_query_params_on_entry_links() + { + tap(Facades\Collection::make('blog')->routes('blog/{slug}'))->save(); + EntryFactory::collection('blog')->id('123')->slug('my-post')->data(['title' => 'My Post'])->create(); + + $field = (new Bard)->setField(new Field('test', ['type' => 'bard'])); + + $augmented = $field->augment([ + ['type' => 'text', 'marks' => [['type' => 'link', 'attrs' => ['href' => 'statamic://entry::123?foo=bar']]], 'text' => 'Link'], + ]); + + $this->assertEquals('Link', $augmented); + } + + #[Test] + public function it_preserves_anchors_on_entry_links() + { + tap(Facades\Collection::make('blog')->routes('blog/{slug}'))->save(); + EntryFactory::collection('blog')->id('123')->slug('my-post')->data(['title' => 'My Post'])->create(); + + $field = (new Bard)->setField(new Field('test', ['type' => 'bard'])); + + $augmented = $field->augment([ + ['type' => 'text', 'marks' => [['type' => 'link', 'attrs' => ['href' => 'statamic://entry::123#section']]], 'text' => 'Link'], + ]); + + $this->assertEquals('Link', $augmented); + } + + #[Test] + public function it_preserves_query_params_and_anchors_on_entry_links() + { + tap(Facades\Collection::make('blog')->routes('blog/{slug}'))->save(); + EntryFactory::collection('blog')->id('123')->slug('my-post')->data(['title' => 'My Post'])->create(); + + $field = (new Bard)->setField(new Field('test', ['type' => 'bard'])); + + $augmented = $field->augment([ + ['type' => 'text', 'marks' => [['type' => 'link', 'attrs' => ['href' => 'statamic://entry::123?foo=bar#section']]], 'text' => 'Link'], + ]); + + $this->assertEquals('Link', $augmented); + } + + #[Test] + public function it_gets_link_data_with_appends() + { + tap(Facades\Collection::make('pages')->routes('/{slug}'))->save(); + EntryFactory::collection('pages')->id('1')->slug('about')->data(['title' => 'About'])->create(); + + $bard = $this->bard(['save_html' => true, 'sets' => null]); + + $html = '

Link with appends

'; + + $prosemirror = (new Augmentor($this))->renderHtmlToProsemirror($html)['content']; + + $this->assertEquals([ + 'entry::1' => ['title' => 'About', 'permalink' => 'http://localhost/about'], + ], $bard->getLinkData($prosemirror)); + } + private function bard($config = []) { return (new Bard)->setField(new Field('test', array_merge(['type' => 'bard', 'sets' => ['one' => []]], $config))); From d62d588126efd115d240b7da1e163c54495d6ff4 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 9 Apr 2026 15:25:25 -0400 Subject: [PATCH 19/19] Normalize appends input, remove unused ref, and add localization tests Auto-prepend ? or # to appends values missing a prefix, remove duplicate ref="input" from the Append field, and add tests for localized entry links and select_across_sites with appends. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../fieldtypes/bard/LinkToolbar.vue | 10 +++- tests/Fieldtypes/BardTest.php | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/resources/js/components/fieldtypes/bard/LinkToolbar.vue b/resources/js/components/fieldtypes/bard/LinkToolbar.vue index e6c0b74553b..72ceabdc006 100644 --- a/resources/js/components/fieldtypes/bard/LinkToolbar.vue +++ b/resources/js/components/fieldtypes/bard/LinkToolbar.vue @@ -84,7 +84,6 @@ assertEquals('Link', $augmented); } + #[Test] + public function it_preserves_appends_on_localized_entry_links() + { + $this->setSites([ + 'en' => ['url' => 'http://localhost/', 'locale' => 'en'], + 'fr' => ['url' => 'http://localhost/fr/', 'locale' => 'fr'], + ]); + + Facades\Site::setCurrent('fr'); + + tap(Facades\Collection::make('blog')->routes('blog/{slug}'))->sites(['en', 'fr'])->save(); + + EntryFactory::id('parent')->collection('blog')->slug('theparent')->id(123)->locale('en')->create(); + EntryFactory::id('123-fr')->origin('123')->locale('fr')->collection('blog')->slug('one-fr')->data(['title' => 'Le One'])->create(); + + $field = (new Bard)->setField(new Field('test', array_merge(['type' => 'bard'], ['select_across_sites' => false]))); + + $augmented = $field->augment([ + ['type' => 'text', 'marks' => [['type' => 'link', 'attrs' => ['href' => 'statamic://entry::123-fr?foo=bar#section']]], 'text' => 'The One'], + ]); + + $this->assertEquals('The One', $augmented); + } + + #[Test] + public function it_preserves_appends_on_entry_links_with_select_across_sites() + { + $this->setSites([ + 'en' => ['url' => 'http://localhost/', 'locale' => 'en'], + 'fr' => ['url' => 'http://localhost/fr/', 'locale' => 'fr'], + ]); + + Facades\Site::setCurrent('en'); + + tap(Facades\Collection::make('blog')->routes('blog/{slug}'))->sites(['en', 'fr'])->save(); + + EntryFactory::id('parent')->collection('blog')->slug('theparent')->id(123)->locale('en')->create(); + EntryFactory::id('123-fr')->origin('123')->locale('fr')->collection('blog')->slug('one-fr')->data(['title' => 'Le One'])->create(); + + $field = (new Bard)->setField(new Field('test', array_merge(['type' => 'bard'], ['select_across_sites' => true]))); + + $augmented = $field->augment([ + ['type' => 'text', 'marks' => [['type' => 'link', 'attrs' => ['href' => 'statamic://entry::123-fr?foo=bar#section']]], 'text' => 'The One'], + ]); + + $this->assertEquals('The One', $augmented); + } + #[Test] public function it_gets_link_data_with_appends() {