From c2d4f27bb5c4e446efd124b5ac9e95dd8b8cf015 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 18 Jun 2021 23:45:29 -0400 Subject: [PATCH 01/67] Test for AugmentedPage --- tests/Data/AugmentedTestCase.php | 14 ++ tests/Data/Structures/AugmentedPageTest.php | 142 ++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 tests/Data/Structures/AugmentedPageTest.php diff --git a/tests/Data/AugmentedTestCase.php b/tests/Data/AugmentedTestCase.php index 027e70b8965..66cc9ffeff5 100644 --- a/tests/Data/AugmentedTestCase.php +++ b/tests/Data/AugmentedTestCase.php @@ -13,12 +13,26 @@ class AugmentedTestCase extends TestCase use PreventSavingStacheItemsToDisk; protected function assertAugmentedCorrectly($expectations, $augmented) + { + $this->assertAllKeysAreAugmented($expectations, $augmented); + $this->assertAugmentedAsExpected($expectations, $augmented); + } + + protected function assertSubsetAugmentedCorrectly($expectations, $augmented) + { + $this->assertAugmentedAsExpected($expectations, $augmented); + } + + private function assertAllKeysAreAugmented($expectations, $augmented) { $this->assertEquals( collect($expectations)->keys()->sort()->values()->all(), $augmented->keys() ); + } + private function assertAugmentedAsExpected($expectations, $augmented) + { foreach ($expectations as $key => $expectation) { $actual = $augmented->get($key); diff --git a/tests/Data/Structures/AugmentedPageTest.php b/tests/Data/Structures/AugmentedPageTest.php new file mode 100644 index 00000000000..d83cef8b178 --- /dev/null +++ b/tests/Data/Structures/AugmentedPageTest.php @@ -0,0 +1,142 @@ +shouldReceive('reference')->andReturnFalse(); + + $augmented = new AugmentedPage($page); + + $expected = [ + 'title', + 'url', + 'uri', + 'permalink', + ]; + + $actual = $augmented->keys(); + + $this->assertEquals( + collect($expected)->sort()->values()->all(), + collect($actual)->sort()->values()->all(), + ); + } + + /** @test */ + public function it_gets_entry_keys() + { + $blueprint = Blueprint::makeFromFields([ + 'title' => ['type' => 'text'], + 'foo' => ['type' => 'text'], + 'one' => ['type' => 'text'], + ])->setHandle('test'); + + $entry = Mockery::mock(Entry::class); + $entry->shouldReceive('values')->andReturn(collect([ + 'one' => 'two', + 'three' => 'four', + ])); + $entry->shouldReceive('supplements')->andReturn(collect([ + 'alfa' => 'bravo', + 'charlie' => 'delta', + ])); + $entry->shouldReceive('blueprint')->andReturn($blueprint); + + $page = Mockery::mock(Page::class); + $page->shouldReceive('reference')->andReturn('123'); + $page->shouldReceive('referenceExists')->andReturnTrue(); + $page->shouldReceive('entry')->andReturn($entry); + + $augmented = new AugmentedPage($page); + + $expected = [ + // entry values + 'one', 'three', + // entry supplements + 'alfa', 'charlie', + // entry blueprint + 'title', 'foo', + // augmented entry keys + 'amp_url', 'api_url', 'collection', 'date', 'edit_url', 'id', 'is_entry', + 'last_modified', 'locale', 'mount', 'order', 'permalink', 'private', + 'published', 'slug', 'status', 'updated_at', 'updated_by', 'uri', 'url', + ]; + + $actual = $augmented->keys(); + + $this->assertEquals( + collect($expected)->sort()->values()->all(), + collect($actual)->sort()->values()->all(), + ); + } + + /** @test */ + public function it_gets_values_from_the_page() + { + $page = Mockery::mock(Page::class); + $page->shouldReceive('reference')->andReturnFalse(); + $page->shouldReceive('title')->andReturn('The Page Title'); + $page->shouldReceive('blueprint')->andReturnNull(); + $page->shouldReceive('url')->andReturn('/the-url'); + $page->shouldReceive('uri')->andReturn('/the-uri'); + $page->shouldReceive('absoluteUrl')->andReturn('https://site.com/the-permalink'); + + $augmented = new AugmentedPage($page); + + $expectations = [ + 'title' => ['type' => 'string', 'value' => 'The Page Title'], + 'url' => ['type' => 'string', 'value' => '/the-url'], + 'uri' => ['type' => 'string', 'value' => '/the-uri'], + 'permalink' => ['type' => 'string', 'value' => 'https://site.com/the-permalink'], + ]; + + $this->assertAugmentedCorrectly($expectations, $augmented); + } + + /** @test */ + public function it_gets_values_from_the_entry() + { + $blueprint = Blueprint::makeFromFields([ + 'title' => ['type' => 'text'], + ])->setHandle('test'); + + $entry = Mockery::mock(Entry::class); + $entry->shouldReceive('values')->andReturn(collect(['title' => 'The Entry Title'])); + $entry->shouldReceive('supplements')->andReturn(collect()); + $entry->shouldReceive('value')->with('title')->andReturn('The Entry Title'); + $entry->shouldReceive('getSupplement')->with('title')->andReturnNull(); + $entry->shouldReceive('blueprint')->andReturn($blueprint); + $entry->shouldReceive('url')->andReturn('/the-url'); + $entry->shouldReceive('uri')->andReturn('/the-uri'); + $entry->shouldReceive('absoluteUrl')->andReturn('https://site.com/the-permalink'); + + $page = Mockery::mock(Page::class); + $page->shouldReceive('reference')->andReturn('123'); + $page->shouldReceive('referenceExists')->andReturnTrue(); + $page->shouldReceive('entry')->andReturn($entry); + + $augmented = new AugmentedPage($page); + + $expectations = [ + 'title' => ['type' => Value::class, 'value' => 'The Entry Title'], + 'url' => ['type' => 'string', 'value' => '/the-url'], + 'uri' => ['type' => 'string', 'value' => '/the-uri'], + 'permalink' => ['type' => 'string', 'value' => 'https://site.com/the-permalink'], + ]; + + $this->assertSubsetAugmentedCorrectly($expectations, $augmented); + } +} From b9c48a2d8cc540c26b00ba9861cb865f6633b626 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 18 Jun 2021 23:45:54 -0400 Subject: [PATCH 02/67] Sort keys --- src/Structures/AugmentedPage.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Structures/AugmentedPage.php b/src/Structures/AugmentedPage.php index 3dc72e389ad..1c652027d71 100644 --- a/src/Structures/AugmentedPage.php +++ b/src/Structures/AugmentedPage.php @@ -25,9 +25,9 @@ public function keys() ? parent::keys() : ['title', 'url', 'uri', 'permalink']; - return Statamic::isApiRoute() - ? $this->apiKeys($keys) - : $keys; + $keys = Statamic::isApiRoute() ? $this->apiKeys($keys) : $keys; + + return collect($keys)->sort()->values()->all(); } private function apiKeys($keys) From 2746959389a483bbc7fa9380f6e540aa83b45987 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 1 Jul 2021 12:14:04 -0400 Subject: [PATCH 03/67] commas --- tests/Data/Structures/AugmentedPageTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Data/Structures/AugmentedPageTest.php b/tests/Data/Structures/AugmentedPageTest.php index d83cef8b178..6088fa3ed9c 100644 --- a/tests/Data/Structures/AugmentedPageTest.php +++ b/tests/Data/Structures/AugmentedPageTest.php @@ -31,7 +31,7 @@ public function it_gets_page_keys() $this->assertEquals( collect($expected)->sort()->values()->all(), - collect($actual)->sort()->values()->all(), + collect($actual)->sort()->values()->all() ); } @@ -79,7 +79,7 @@ public function it_gets_entry_keys() $this->assertEquals( collect($expected)->sort()->values()->all(), - collect($actual)->sort()->values()->all(), + collect($actual)->sort()->values()->all() ); } From 005507c47ddfcc79fdffe0bec022df4cd5a535a2 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 1 Jul 2021 16:25:18 -0400 Subject: [PATCH 04/67] Pages can set their own array of data, and it'll fall back to the entry --- src/Structures/Page.php | 56 ++++++++++++++++++++++ tests/Data/Structures/PageTest.php | 74 ++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/src/Structures/Page.php b/src/Structures/Page.php index 9f54ba5b7c2..cf98bf772f1 100644 --- a/src/Structures/Page.php +++ b/src/Structures/Page.php @@ -32,6 +32,7 @@ class Page implements Entry, Augmentable, Responsable, Protectable, JsonSerializ protected $url; protected $title; protected $depth; + protected $data = []; public function setUrl($url) { @@ -227,6 +228,61 @@ public function setChildren(array $children): self return $this; } + public function setData(array $data): self + { + $this->data = $data; + + return $this; + } + + public function data() + { + $data = collect($this->data); + + if ($entry = $this->entry()) { + $data = $entry->data()->merge($data); + } + + return $data; + } + + public function values() + { + $data = collect($this->data); + + if ($entry = $this->entry()) { + $data = $entry->values()->merge($data); + } + + return $data; + } + + public function get(string $key, $fallback = null) + { + if ($value = $this->data[$key] ?? null) { + return $value; + } + + if ($entry = $this->entry()) { + $value = $entry->get($key); + } + + return $value ?? $fallback; + } + + public function value(string $key) + { + if ($value = $this->data[$key] ?? null) { + return $value; + } + + if ($entry = $this->entry()) { + $value = $entry->value($key); + } + + return $value; + } + public function pages() { $pages = (new Pages) diff --git a/tests/Data/Structures/PageTest.php b/tests/Data/Structures/PageTest.php index 686ec68a3d8..74dbf48cfd0 100644 --- a/tests/Data/Structures/PageTest.php +++ b/tests/Data/Structures/PageTest.php @@ -12,6 +12,7 @@ use Statamic\Structures\Pages; use Statamic\Structures\Structure; use Statamic\Structures\Tree; +use Facades\Tests\Factories\EntryFactory; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; @@ -359,6 +360,79 @@ public function it_forwards_calls_to_the_entry() $this->assertEquals('hello', $page->testing('123')); } + /** @test */ + public function it_gets_values() + { + $page = new Page; + + $this->assertInstanceOf(Collection::class, $page->data()); + $this->assertEquals([], $page->data()->all()); + $this->assertInstanceOf(Collection::class, $page->values()); + $this->assertEquals([], $page->values()->all()); + $this->assertNull($page->value('foo')); + $this->assertNull($page->get('foo')); + $this->assertEquals('fallback', $page->get('unknown', 'fallback')); + + $page->setData(['foo' => 'bar']); + + $this->assertInstanceOf(Collection::class, $page->data()); + $this->assertEquals(['foo' => 'bar'], $page->data()->all()); + $this->assertInstanceOf(Collection::class, $page->values()); + $this->assertEquals(['foo' => 'bar'], $page->values()->all()); + $this->assertEquals('bar', $page->value('foo')); + $this->assertEquals('bar', $page->get('foo')); + $this->assertEquals('fallback', $page->get('unknown', 'fallback')); + } + + /** @test */ + public function it_gets_values_and_falls_back_to_values_from_the_entry() + { + $entry = EntryFactory::id('test-entry')->collection('test')->data([ + 'foo' => 'entry bar', + 'baz' => 'entry qux' + ])->create(); + + $tree = $this->mock(Tree::class)->shouldReceive('entry')->with('test-entry')->andReturn($entry)->getMock(); + + $page = new Page; + $page->setEntry('test-entry'); + $page->setTree($tree); + + $this->assertInstanceOf(Collection::class, $page->data()); + $this->assertEquals([ + 'foo' => 'entry bar', + 'baz' => 'entry qux', + ], $page->data()->all()); + $this->assertInstanceOf(Collection::class, $page->values()); + $this->assertEquals([ + 'foo' => 'entry bar', + 'baz' => 'entry qux', + ], $page->values()->all()); + $this->assertEquals('entry bar', $page->value('foo')); + $this->assertEquals('entry bar', $page->get('foo')); + $this->assertEquals('entry qux', $page->value('baz')); + $this->assertEquals('entry qux', $page->get('baz')); + $this->assertEquals('fallback', $page->get('unknown', 'fallback')); + + $page->setData(['foo' => 'page bar']); + + $this->assertInstanceOf(Collection::class, $page->data()); + $this->assertEquals([ + 'foo' => 'page bar', + 'baz' => 'entry qux', + ], $page->data()->all()); + $this->assertInstanceOf(Collection::class, $page->values()); + $this->assertEquals([ + 'foo' => 'page bar', + 'baz' => 'entry qux', + ], $page->values()->all()); + $this->assertEquals('page bar', $page->value('foo')); + $this->assertEquals('page bar', $page->get('foo')); + $this->assertEquals('entry qux', $page->value('baz')); + $this->assertEquals('entry qux', $page->get('baz')); + $this->assertEquals('fallback', $page->get('unknown', 'fallback')); + } + protected function newTree() { return new class extends Tree From afe37c2e660324c5a754dedb2eb00166d74d33ae Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 1 Jul 2021 16:37:19 -0400 Subject: [PATCH 05/67] augmented pages merge the entry fields --- src/Structures/AugmentedPage.php | 34 +++++++--- src/Structures/Page.php | 3 +- tests/Data/AugmentedTestCase.php | 9 +++ tests/Data/Structures/AugmentedPageTest.php | 73 ++++++++++++++++++--- 4 files changed, 101 insertions(+), 18 deletions(-) diff --git a/src/Structures/AugmentedPage.php b/src/Structures/AugmentedPage.php index 1c652027d71..53eb76dfca7 100644 --- a/src/Structures/AugmentedPage.php +++ b/src/Structures/AugmentedPage.php @@ -7,10 +7,13 @@ class AugmentedPage extends AugmentedEntry { + protected $page; protected $hasEntry = false; public function __construct($page) { + $this->page = $page; + if ($page->reference() && $page->referenceExists()) { $this->hasEntry = true; parent::__construct($page->entry()); @@ -21,13 +24,15 @@ public function __construct($page) public function keys() { - $keys = $this->hasEntry + $keys = collect($this->hasEntry ? parent::keys() - : ['title', 'url', 'uri', 'permalink']; + : ['title', 'url', 'uri', 'permalink']); + + $keys = $keys->merge($this->page->data()->keys()); $keys = Statamic::isApiRoute() ? $this->apiKeys($keys) : $keys; - return collect($keys)->sort()->values()->all(); + return $keys->unique()->sort()->values()->all(); } private function apiKeys($keys) @@ -35,14 +40,27 @@ private function apiKeys($keys) return collect($keys) ->reject(function ($key) { return in_array($key, ['parent']); - }) - ->all(); + }); } protected function getFromData($key) { - return $this->hasEntry - ? parent::getFromData($key) - : null; + if ($key === 'title') { + return $this->page->title(); + } + + return $this->page->value($key); + } + + protected function blueprintFields() + { + $fields = $this->page->blueprint()->fields()->all(); + + if ($this->page !== $this->data) { + $entryFields = $this->data->blueprint()->fields()->all(); + $fields = $entryFields->merge($fields); + } + + return $fields; } } diff --git a/src/Structures/Page.php b/src/Structures/Page.php index cf98bf772f1..7b3f26c46e7 100644 --- a/src/Structures/Page.php +++ b/src/Structures/Page.php @@ -372,7 +372,8 @@ public function private() public function blueprint() { - return optional($this->entry())->blueprint(); + // TODO: make the actual blueprint. + return new \Statamic\Fields\Blueprint; } public function collection() diff --git a/tests/Data/AugmentedTestCase.php b/tests/Data/AugmentedTestCase.php index 66cc9ffeff5..dfe60a69fb4 100644 --- a/tests/Data/AugmentedTestCase.php +++ b/tests/Data/AugmentedTestCase.php @@ -43,6 +43,15 @@ private function assertAugmentedAsExpected($expectations, $augmented) switch ($expectation['type']) { case Value::class: $this->assertSame($expectation['value'], $actual->value(), "Key '{$key}' does not match expected value."); + + if (isset($expectation['fieldtype'])) { + $this->assertEquals( + $expectation['fieldtype'], + $actual->fieldtype()->handle(), + "Key '{$key}' does not have the expected fieldtype." + ); + } + break; case Carbon::class: diff --git a/tests/Data/Structures/AugmentedPageTest.php b/tests/Data/Structures/AugmentedPageTest.php index 6088fa3ed9c..3e01ac0e0a9 100644 --- a/tests/Data/Structures/AugmentedPageTest.php +++ b/tests/Data/Structures/AugmentedPageTest.php @@ -17,6 +17,7 @@ public function it_gets_page_keys() { $page = Mockery::mock(Page::class); $page->shouldReceive('reference')->andReturnFalse(); + $page->shouldReceive('data')->andReturn(collect(['one' => 'two', 'three' => 'four'])); $augmented = new AugmentedPage($page); @@ -25,6 +26,8 @@ public function it_gets_page_keys() 'url', 'uri', 'permalink', + 'one', + 'three', ]; $actual = $augmented->keys(); @@ -38,11 +41,15 @@ public function it_gets_page_keys() /** @test */ public function it_gets_entry_keys() { - $blueprint = Blueprint::makeFromFields([ + $entryBlueprint = Blueprint::makeFromFields([ 'title' => ['type' => 'text'], 'foo' => ['type' => 'text'], 'one' => ['type' => 'text'], - ])->setHandle('test'); + ])->setNamespace('collections.articles')->setHandle('article'); + + $pageBlueprint = Blueprint::makeFromFields([ + 'jane' => ['type' => 'text'], + ])->setNamespace('navs')->setHandle('pages'); $entry = Mockery::mock(Entry::class); $entry->shouldReceive('values')->andReturn(collect([ @@ -53,12 +60,18 @@ public function it_gets_entry_keys() 'alfa' => 'bravo', 'charlie' => 'delta', ])); - $entry->shouldReceive('blueprint')->andReturn($blueprint); + $entry->shouldReceive('blueprint')->andReturn($entryBlueprint); $page = Mockery::mock(Page::class); $page->shouldReceive('reference')->andReturn('123'); $page->shouldReceive('referenceExists')->andReturnTrue(); $page->shouldReceive('entry')->andReturn($entry); + $page->shouldReceive('blueprint')->andReturn($pageBlueprint); + $page->shouldReceive('data')->andReturn(collect([ + 'john' => 'doe', + 'jane' => 'doe', + 'three' => 'four', + ])); $augmented = new AugmentedPage($page); @@ -73,6 +86,10 @@ public function it_gets_entry_keys() 'amp_url', 'api_url', 'collection', 'date', 'edit_url', 'id', 'is_entry', 'last_modified', 'locale', 'mount', 'order', 'permalink', 'private', 'published', 'slug', 'status', 'updated_at', 'updated_by', 'uri', 'url', + // page blueprint + 'jane', + // page data + 'john', ]; $actual = $augmented->keys(); @@ -86,13 +103,22 @@ public function it_gets_entry_keys() /** @test */ public function it_gets_values_from_the_page() { + $blueprint = Blueprint::makeFromFields([ + 'one' => ['type' => 'text'], + 'three' => ['type' => 'text'], + ]); + $page = Mockery::mock(Page::class); $page->shouldReceive('reference')->andReturnFalse(); $page->shouldReceive('title')->andReturn('The Page Title'); - $page->shouldReceive('blueprint')->andReturnNull(); + $page->shouldReceive('blueprint')->andReturn($blueprint); $page->shouldReceive('url')->andReturn('/the-url'); $page->shouldReceive('uri')->andReturn('/the-uri'); $page->shouldReceive('absoluteUrl')->andReturn('https://site.com/the-permalink'); + $page->shouldReceive('data')->andReturn(collect(['one' => 'two', 'three' => 'four', 'five' => 'six'])); + $page->shouldReceive('value')->with('one')->andReturn('two'); + $page->shouldReceive('value')->with('three')->andReturn('four'); + $page->shouldReceive('value')->with('five')->andReturn('six'); $augmented = new AugmentedPage($page); @@ -101,6 +127,9 @@ public function it_gets_values_from_the_page() 'url' => ['type' => 'string', 'value' => '/the-url'], 'uri' => ['type' => 'string', 'value' => '/the-uri'], 'permalink' => ['type' => 'string', 'value' => 'https://site.com/the-permalink'], + 'one' => ['type' => Value::class, 'value' => 'two'], + 'three' => ['type' => Value::class, 'value' => 'four'], + 'five' => ['type' => 'string', 'value' => 'six'], ]; $this->assertAugmentedCorrectly($expectations, $augmented); @@ -109,16 +138,33 @@ public function it_gets_values_from_the_page() /** @test */ public function it_gets_values_from_the_entry() { - $blueprint = Blueprint::makeFromFields([ + $entryBlueprint = Blueprint::makeFromFields([ 'title' => ['type' => 'text'], - ])->setHandle('test'); + 'one' => ['type' => 'text'], + ])->setNamespace('collections.articles')->setHandle('article'); + + $pageBlueprint = Blueprint::makeFromFields([ + 'one' => ['type' => 'textarea'], + 'three' => ['type' => 'textarea'], + ])->setNamespace('navs')->setHandle('pages'); $entry = Mockery::mock(Entry::class); - $entry->shouldReceive('values')->andReturn(collect(['title' => 'The Entry Title'])); + $entry->shouldReceive('values')->andReturn(collect([ + 'title' => 'The Entry Title', + 'one' => 'two', + 'three' => 'four', + 'five' => 'six', + ])); $entry->shouldReceive('supplements')->andReturn(collect()); $entry->shouldReceive('value')->with('title')->andReturn('The Entry Title'); + $entry->shouldReceive('value')->with('one')->andReturn('two'); + $entry->shouldReceive('value')->with('three')->andReturnNull('four'); + $entry->shouldReceive('value')->with('five')->andReturn('six'); $entry->shouldReceive('getSupplement')->with('title')->andReturnNull(); - $entry->shouldReceive('blueprint')->andReturn($blueprint); + $entry->shouldReceive('getSupplement')->with('one')->andReturnNull(); + $entry->shouldReceive('getSupplement')->with('three')->andReturnNull(); + $entry->shouldReceive('getSupplement')->with('five')->andReturnNull(); + $entry->shouldReceive('blueprint')->andReturn($entryBlueprint); $entry->shouldReceive('url')->andReturn('/the-url'); $entry->shouldReceive('uri')->andReturn('/the-uri'); $entry->shouldReceive('absoluteUrl')->andReturn('https://site.com/the-permalink'); @@ -127,14 +173,23 @@ public function it_gets_values_from_the_entry() $page->shouldReceive('reference')->andReturn('123'); $page->shouldReceive('referenceExists')->andReturnTrue(); $page->shouldReceive('entry')->andReturn($entry); + $page->shouldReceive('blueprint')->andReturn($pageBlueprint); + $page->shouldReceive('data')->andReturn(collect(['one' => 'dos', 'three' => 'quatro', 'five' => 'seis'])); + $page->shouldReceive('title')->andReturn('The Page Title'); + $page->shouldReceive('value')->with('one')->andReturn('dos'); + $page->shouldReceive('value')->with('three')->andReturn('quatro'); + $page->shouldReceive('value')->with('five')->andReturn('seis'); $augmented = new AugmentedPage($page); $expectations = [ - 'title' => ['type' => Value::class, 'value' => 'The Entry Title'], + 'title' => ['type' => Value::class, 'value' => 'The Page Title'], 'url' => ['type' => 'string', 'value' => '/the-url'], 'uri' => ['type' => 'string', 'value' => '/the-uri'], 'permalink' => ['type' => 'string', 'value' => 'https://site.com/the-permalink'], + 'one' => ['type' => Value::class, 'value' => 'dos', 'fieldtype' => 'textarea'], // assert fieldtype to ensure the field from the page blueprint wins + 'three' => ['type' => Value::class, 'value' => 'quatro'], + 'five' => ['type' => 'string', 'value' => 'seis'], ]; $this->assertSubsetAugmentedCorrectly($expectations, $augmented); From 6aef092ccd40d716d8d6c15d935f9fe356f3fe0c Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 1 Jul 2021 16:43:35 -0400 Subject: [PATCH 06/67] style --- tests/Data/Structures/PageTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Data/Structures/PageTest.php b/tests/Data/Structures/PageTest.php index 74dbf48cfd0..368654fe3eb 100644 --- a/tests/Data/Structures/PageTest.php +++ b/tests/Data/Structures/PageTest.php @@ -389,7 +389,7 @@ public function it_gets_values_and_falls_back_to_values_from_the_entry() { $entry = EntryFactory::id('test-entry')->collection('test')->data([ 'foo' => 'entry bar', - 'baz' => 'entry qux' + 'baz' => 'entry qux', ])->create(); $tree = $this->mock(Tree::class)->shouldReceive('entry')->with('test-entry')->andReturn($entry)->getMock(); From cad4e1f41b2469c236aff22deaaf82b7ebdd85aa Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 1 Jul 2021 16:45:57 -0400 Subject: [PATCH 07/67] style --- tests/Data/Structures/PageTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Data/Structures/PageTest.php b/tests/Data/Structures/PageTest.php index 368654fe3eb..ad20c3ab54c 100644 --- a/tests/Data/Structures/PageTest.php +++ b/tests/Data/Structures/PageTest.php @@ -2,6 +2,7 @@ namespace Tests\Data\Structures; +use Facades\Tests\Factories\EntryFactory; use Illuminate\Support\Collection; use Mockery; use Statamic\Contracts\Structures\Nav; @@ -12,7 +13,6 @@ use Statamic\Structures\Pages; use Statamic\Structures\Structure; use Statamic\Structures\Tree; -use Facades\Tests\Factories\EntryFactory; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; From 298f298824ca36fc5f3985a029b88ad52ef34212 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 1 Jul 2021 17:45:14 -0400 Subject: [PATCH 08/67] Navs can have blueprints and they can be edited in the cp --- resources/views/blueprints/index.blade.php | 20 +++++++++ .../navigation/blueprints/edit.blade.php | 22 ++++++++++ routes/cp.php | 3 ++ src/Events/NavBlueprintFound.php | 15 +++++++ .../NavigationBlueprintController.php | 44 +++++++++++++++++++ src/Structures/Nav.php | 12 +++++ 6 files changed, 116 insertions(+) create mode 100644 resources/views/navigation/blueprints/edit.blade.php create mode 100644 src/Events/NavBlueprintFound.php create mode 100644 src/Http/Controllers/CP/Structures/NavigationBlueprintController.php diff --git a/resources/views/blueprints/index.blade.php b/resources/views/blueprints/index.blade.php index 9adf1e3f83c..13b13554b52 100644 --- a/resources/views/blueprints/index.blade.php +++ b/resources/views/blueprints/index.blade.php @@ -73,6 +73,26 @@ @endif @endforeach + @foreach (Statamic\Facades\Nav::all() as $nav) + @if ($loop->first) +

{{ __('Navigation') }}

+
+ + @endif + + + + @if ($loop->last) +
+
+
@cp_svg('hierarchy-files')
+ {{ $nav->title() }} +
+
+
+ @endif + @endforeach + @foreach (Statamic\Facades\GlobalSet::all() as $set) @if ($loop->first)

{{ __('Globals') }}

diff --git a/resources/views/navigation/blueprints/edit.blade.php b/resources/views/navigation/blueprints/edit.blade.php new file mode 100644 index 00000000000..992cabe9abd --- /dev/null +++ b/resources/views/navigation/blueprints/edit.blade.php @@ -0,0 +1,22 @@ +@extends('statamic::layout') +@section('title', __('Edit Blueprint')) + +@section('content') + + @include('statamic::partials.breadcrumb', [ + 'url' => cp_route('navigation.show', $nav->handle()), + 'title' => $nav->title(), + ]) + + + + @include('statamic::partials.docs-callout', [ + 'topic' => __('Blueprints'), + 'url' => Statamic::docsUrl('blueprints') + ]) + +@endsection diff --git a/routes/cp.php b/routes/cp.php index d3991505f9a..199993e16a1 100644 --- a/routes/cp.php +++ b/routes/cp.php @@ -31,6 +31,9 @@ Route::group(['namespace' => 'Structures'], function () { Route::resource('navigation', 'NavigationController'); Route::resource('structures.pages', 'StructurePagesController', ['only' => ['index', 'store']]); + + Route::get('navigation/{navigation}/blueprint', 'NavigationBlueprintController@edit')->name('navigation.blueprint.edit'); + Route::patch('navigation/{navigation}/blueprint', 'NavigationBlueprintController@update')->name('navigation.blueprint.update'); }); Route::group(['namespace' => 'Collections'], function () { diff --git a/src/Events/NavBlueprintFound.php b/src/Events/NavBlueprintFound.php new file mode 100644 index 00000000000..02d69b55eb0 --- /dev/null +++ b/src/Events/NavBlueprintFound.php @@ -0,0 +1,15 @@ +blueprint = $blueprint; + $this->nav = $nav; + } +} diff --git a/src/Http/Controllers/CP/Structures/NavigationBlueprintController.php b/src/Http/Controllers/CP/Structures/NavigationBlueprintController.php new file mode 100644 index 00000000000..5d7d3b23cff --- /dev/null +++ b/src/Http/Controllers/CP/Structures/NavigationBlueprintController.php @@ -0,0 +1,44 @@ +middleware(\Illuminate\Auth\Middleware\Authorize::class.':configure fields'); + } + + public function edit($nav) + { + if (! $nav = Nav::find($nav)) { + return $this->pageNotFound(); + } + + $blueprint = $nav->blueprint(); + + return view('statamic::navigation.blueprints.edit', [ + 'nav' => $nav, + 'blueprint' => $blueprint, + 'blueprintVueObject' => $this->toVueObject($blueprint), + ]); + } + + public function update(Request $request, $nav) + { + if (! $nav = Nav::find($nav)) { + return $this->pageNotFound(); + } + + $request->validate(['sections' => 'array']); + + $this->updateBlueprint($request, $nav->blueprint()); + } +} diff --git a/src/Structures/Nav.php b/src/Structures/Nav.php index 7f1a71d7339..6d71eb61ffe 100644 --- a/src/Structures/Nav.php +++ b/src/Structures/Nav.php @@ -5,9 +5,11 @@ use Statamic\Contracts\Structures\Nav as Contract; use Statamic\Contracts\Structures\NavTreeRepository; use Statamic\Data\ExistsAsFile; +use Statamic\Events\NavBlueprintFound; use Statamic\Events\NavDeleted; use Statamic\Events\NavSaved; use Statamic\Facades; +use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; use Statamic\Facades\Site; use Statamic\Facades\Stache; @@ -104,4 +106,14 @@ public function existsIn($site) { return $this->trees()->has($site); } + + public function blueprint() + { + $blueprint = Blueprint::find('navigation.'.$this->handle()) + ?? Blueprint::makeFromFields([])->setHandle($this->handle())->setNamespace('navigation'); + + NavBlueprintFound::dispatch($blueprint, $this); + + return $blueprint; + } } From 3d574dec3f4391cfdd987113b1af43c1f7819726 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 2 Jul 2021 16:02:58 -0400 Subject: [PATCH 09/67] pages can check if they have a custom title --- src/Structures/Page.php | 5 +++ tests/Data/Structures/PageTest.php | 52 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/Structures/Page.php b/src/Structures/Page.php index 7b3f26c46e7..831df9523a1 100644 --- a/src/Structures/Page.php +++ b/src/Structures/Page.php @@ -84,6 +84,11 @@ public function title() return optional($this->entry())->value('title'); } + public function hasCustomTitle() + { + return $this->title !== null; + } + public function setEntry($reference): self { if ($reference === null) { diff --git a/tests/Data/Structures/PageTest.php b/tests/Data/Structures/PageTest.php index ad20c3ab54c..34b3e3c4bc3 100644 --- a/tests/Data/Structures/PageTest.php +++ b/tests/Data/Structures/PageTest.php @@ -69,6 +69,58 @@ public function it_gets_the_entry_dynamically_when_its_set_using_an_int() $this->assertEquals($page, $return); } + /** @test */ + public function it_gets_the_title() + { + $page = new Page; + + $this->assertNull($page->title()); + $this->assertFalse($page->hasCustomTitle()); + + $page->setTitle('Test'); + + $this->assertEquals('Test', $page->title()); + $this->assertTrue($page->hasCustomTitle()); + } + + /** @test */ + public function it_gets_the_title_when_referencing_an_entry() + { + $entry = $this->mock(Entry::class); + $entry->shouldReceive('id')->andReturn('test'); + $entry->shouldReceive('value')->andReturn('Entry Title'); + + $page = new Page; + + $this->assertNull($page->title()); + $this->assertFalse($page->hasCustomTitle()); + + $page->setEntry($entry); + + $this->assertEquals('Entry Title', $page->title()); + $this->assertFalse($page->hasCustomTitle()); + } + + /** @test */ + public function it_gets_the_custom_title_when_referencing_an_entry() + { + $entry = $this->mock(Entry::class); + $entry->shouldReceive('id')->andReturn('test'); + $entry->shouldReceive('value')->andReturn('Entry Title'); + + $page = new Page; + + $this->assertNull($page->title()); + $this->assertFalse($page->hasCustomTitle()); + + $page + ->setEntry($entry) + ->setTitle('Custom Title'); + + $this->assertEquals('Custom Title', $page->title()); + $this->assertTrue($page->hasCustomTitle()); + } + /** @test */ public function it_gets_and_sets_the_parent() { From 42e3a92c518af91e0c8dad641b27acf331f558da Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 2 Jul 2021 16:03:59 -0400 Subject: [PATCH 10/67] The page's own data can be set and retrieved on its own --- src/Structures/Page.php | 11 ++++++++--- tests/Data/Structures/PageTest.php | 12 ++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Structures/Page.php b/src/Structures/Page.php index 831df9523a1..b55233ac58c 100644 --- a/src/Structures/Page.php +++ b/src/Structures/Page.php @@ -233,16 +233,21 @@ public function setChildren(array $children): self return $this; } - public function setData(array $data): self + public function setPageData(array $data): self { $this->data = $data; return $this; } + public function pageData() + { + return collect($this->data); + } + public function data() { - $data = collect($this->data); + $data = $this->pageData(); if ($entry = $this->entry()) { $data = $entry->data()->merge($data); @@ -253,7 +258,7 @@ public function data() public function values() { - $data = collect($this->data); + $data = $this->pageData(); if ($entry = $this->entry()) { $data = $entry->values()->merge($data); diff --git a/tests/Data/Structures/PageTest.php b/tests/Data/Structures/PageTest.php index 34b3e3c4bc3..87051fb4eaf 100644 --- a/tests/Data/Structures/PageTest.php +++ b/tests/Data/Structures/PageTest.php @@ -419,16 +419,20 @@ public function it_gets_values() $this->assertInstanceOf(Collection::class, $page->data()); $this->assertEquals([], $page->data()->all()); + $this->assertInstanceOf(Collection::class, $page->pageData()); + $this->assertEquals([], $page->pageData()->all()); $this->assertInstanceOf(Collection::class, $page->values()); $this->assertEquals([], $page->values()->all()); $this->assertNull($page->value('foo')); $this->assertNull($page->get('foo')); $this->assertEquals('fallback', $page->get('unknown', 'fallback')); - $page->setData(['foo' => 'bar']); + $page->setPageData(['foo' => 'bar']); $this->assertInstanceOf(Collection::class, $page->data()); $this->assertEquals(['foo' => 'bar'], $page->data()->all()); + $this->assertInstanceOf(Collection::class, $page->pageData()); + $this->assertEquals(['foo' => 'bar'], $page->pageData()->all()); $this->assertInstanceOf(Collection::class, $page->values()); $this->assertEquals(['foo' => 'bar'], $page->values()->all()); $this->assertEquals('bar', $page->value('foo')); @@ -455,6 +459,8 @@ public function it_gets_values_and_falls_back_to_values_from_the_entry() 'foo' => 'entry bar', 'baz' => 'entry qux', ], $page->data()->all()); + $this->assertInstanceOf(Collection::class, $page->pageData()); + $this->assertEquals([], $page->pageData()->all()); $this->assertInstanceOf(Collection::class, $page->values()); $this->assertEquals([ 'foo' => 'entry bar', @@ -466,13 +472,15 @@ public function it_gets_values_and_falls_back_to_values_from_the_entry() $this->assertEquals('entry qux', $page->get('baz')); $this->assertEquals('fallback', $page->get('unknown', 'fallback')); - $page->setData(['foo' => 'page bar']); + $page->setPageData(['foo' => 'page bar']); $this->assertInstanceOf(Collection::class, $page->data()); $this->assertEquals([ 'foo' => 'page bar', 'baz' => 'entry qux', ], $page->data()->all()); + $this->assertInstanceOf(Collection::class, $page->pageData()); + $this->assertEquals(['foo' => 'page bar'], $page->pageData()->all()); $this->assertInstanceOf(Collection::class, $page->values()); $this->assertEquals([ 'foo' => 'page bar', From 91203437ef4bafa3003c5bc16e14dd4e236e9b2a Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 5 Jul 2021 11:55:22 -0400 Subject: [PATCH 11/67] Page will only have a blueprint if its in a nav, not a collection. --- src/Structures/AugmentedPage.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Structures/AugmentedPage.php b/src/Structures/AugmentedPage.php index 53eb76dfca7..abec370298a 100644 --- a/src/Structures/AugmentedPage.php +++ b/src/Structures/AugmentedPage.php @@ -54,7 +54,9 @@ protected function getFromData($key) protected function blueprintFields() { - $fields = $this->page->blueprint()->fields()->all(); + $fields = ($pageBlueprint = $this->page->blueprint()) + ? $pageBlueprint->fields()->all() + : collect(); if ($this->page !== $this->data) { $entryFields = $this->data->blueprint()->fields()->all(); From b8acfaa8e607e560e468df619bb0feaabe035263 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 5 Jul 2021 12:00:29 -0400 Subject: [PATCH 12/67] data gets passed through pages --- src/Structures/Pages.php | 3 ++- tests/Data/Structures/PagesTest.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Structures/Pages.php b/src/Structures/Pages.php index babe400cd81..957b9ce1901 100644 --- a/src/Structures/Pages.php +++ b/src/Structures/Pages.php @@ -68,7 +68,8 @@ public function all() ->setUrl($branch['url'] ?? null) ->setTitle($branch['title'] ?? null) ->setDepth($this->depth) - ->setChildren($branch['children'] ?? []); + ->setChildren($branch['children'] ?? []) + ->setPageData($branch['data'] ?? []); if ($this->route) { $page->setRoute($this->route); diff --git a/tests/Data/Structures/PagesTest.php b/tests/Data/Structures/PagesTest.php index f0ef29bc7f5..55ee6a774ef 100644 --- a/tests/Data/Structures/PagesTest.php +++ b/tests/Data/Structures/PagesTest.php @@ -32,7 +32,7 @@ public function it_gets_a_list_of_pages() $pages = (new Pages) ->setParent($parent) ->setPages([ - ['entry' => 'one', 'children' => [ + ['entry' => 'one', 'data' => ['foo' => 'bar'], 'children' => [ ['entry' => 'one-one'], ['entry' => 'one-two', 'children' => [ ['entry' => 'one-two-one'], @@ -46,6 +46,7 @@ public function it_gets_a_list_of_pages() $this->assertCount(3, $list); $this->assertEveryItemIsInstanceOf(Page::class, $list); $this->assertEquals(['the-root', 'one', 'two'], $list->map->reference()->all()); + $this->assertEquals(['foo' => 'bar'], $list[1]->pageData()->all()); } /** @test */ From 2972d45f491a3e5924d021d8c833c843acda2192 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 5 Jul 2021 12:14:59 -0400 Subject: [PATCH 13/67] Hook up blueprint ui in edit pane --- resources/js/components/navigation/View.vue | 19 ++-- resources/js/components/structures/Branch.vue | 6 +- .../js/components/structures/PageEditor.vue | 102 ++++++++++++++---- resources/views/navigation/show.blade.php | 1 + .../CP/Structures/NavigationController.php | 1 + .../Structures/StructurePagesController.php | 3 +- src/Structures/Page.php | 7 +- src/Structures/TreeBuilder.php | 20 +++- 8 files changed, 125 insertions(+), 34 deletions(-) diff --git a/resources/js/components/navigation/View.vue b/resources/js/components/navigation/View.vue index 3e6c68032c6..7eca7a8d2d2 100644 --- a/resources/js/components/navigation/View.vue +++ b/resources/js/components/navigation/View.vue @@ -127,14 +127,20 @@ @@ -178,7 +184,8 @@ export default { maxDepth: { type: Number, default: Infinity, }, expectsRoot: { type: Boolean, required: true }, site: { type: String, required: true }, - sites: { type: Array, required: true } + sites: { type: Array, required: true }, + blueprint: { type: Object, required: true } }, data() { @@ -263,18 +270,14 @@ export default { return !this.isEntryBranch(branch) && !this.isLinkBranch(branch); }, - editPage(page, vm, store, $event) { - if (page.id) { - const url = page.edit_url; - $event.metaKey ? window.open(url) : window.location = url; - } else { - this.editingPage = { page, vm, store }; - } + editPage(page, vm, store) { + this.editingPage = { page, vm, store }; }, updatePage(page) { this.editingPage.page.url = page.url; this.editingPage.page.title = page.title; + this.editingPage.page.values = page.values; this.$refs.tree.pageUpdated(this.editingPage.store); this.editingPage = false; diff --git a/resources/js/components/structures/Branch.vue b/resources/js/components/structures/Branch.vue index 0db95eac218..b26cb39990c 100644 --- a/resources/js/components/structures/Branch.vue +++ b/resources/js/components/structures/Branch.vue @@ -9,7 +9,7 @@ + v-text="title" /> @@ -45,34 +50,89 @@ export default { props: { + type: String, initialTitle: String, initialUrl: String, + initialValues: Object, + initialMeta: Object, + blueprint: Object, }, data() { return { - title: this.initialTitle, - url: this.initialUrl, + values: this.initValues(this.initialValues), + meta: this.initiMeta(this.initialMeta), + errors: {} + } + }, + + computed: { + adjustedBlueprint() { + let blueprint = clone(this.blueprint); + + // todo only add the fields if they're not already in the blueprint. + + if (this.type == 'url') { + blueprint.sections[0].fields.unshift({ + handle: 'url', + type: 'text', + display: __('URL'), + instructions: __('Enter any internal or external URL. Leave blank for a text-only item.'), + }); + } + + blueprint.sections[0].fields.unshift({ + handle: 'title', + type: 'text', + display: __('Title'), + instructions: __('Link display text. Leave blank to use the URL.'), + }); + + return blueprint; + }, + + fields() { + return _.chain(this.adjustedBlueprint.sections) + .map(section => section.fields) + .flatten(true) + .value(); } }, methods: { submit() { - if (!this.title && !this.url) { + let title = this.values.title; + let url = this.values.url; + + // todo: actual blueprint validation. submit to server side. + if (!title && !url) { alert('You need at least a title or URL.'); return; } this.$emit('submitted', { - title: this.title, - url: this.url, + title, + url, + values: _.omit(this.values, ['title', 'url']), }); + }, + + initValues(values) { + return { + ...values, + title: this.initialTitle, + url: this.initialUrl + }; + }, + + initiMeta(meta) { + return {...meta, title: null, url: null}; } }, created() { this.$keys.bindGlobal('enter', this.submit) - }, + } } diff --git a/resources/views/navigation/show.blade.php b/resources/views/navigation/show.blade.php index ce92c35005c..304d5456d9b 100644 --- a/resources/views/navigation/show.blade.php +++ b/resources/views/navigation/show.blade.php @@ -16,6 +16,7 @@ :collections="{{ json_encode($collections) }}" :max-depth="{{ $nav->maxDepth() ?? 'Infinity' }}" :expects-root="{{ $str::bool($expectsRoot) }}" + :blueprint="{{ json_encode($blueprint) }}" > @endsection diff --git a/src/Http/Controllers/CP/Structures/NavigationController.php b/src/Http/Controllers/CP/Structures/NavigationController.php index ef59582b519..1a2d63ebd45 100644 --- a/src/Http/Controllers/CP/Structures/NavigationController.php +++ b/src/Http/Controllers/CP/Structures/NavigationController.php @@ -83,6 +83,7 @@ public function show(Request $request, $nav) 'url' => $tree->showUrl(), ]; })->values()->all(), + 'blueprint' => $nav->blueprint()->toPublishArray(), ]); } diff --git a/src/Http/Controllers/CP/Structures/StructurePagesController.php b/src/Http/Controllers/CP/Structures/StructurePagesController.php index f0e600a6b73..0a85a1168b8 100644 --- a/src/Http/Controllers/CP/Structures/StructurePagesController.php +++ b/src/Http/Controllers/CP/Structures/StructurePagesController.php @@ -40,8 +40,9 @@ protected function toTree($items) return collect($items)->map(function ($item) { return Arr::removeNullValues([ 'entry' => $ref = $item['id'] ?? null, - 'title' => $ref ? null : ($item['title'] ?? null), + 'title' => $item['title'] ?? null, 'url' => $ref ? null : ($item['url'] ?? null), + 'data' => Arr::removeNullValues($item['values']), 'children' => $this->toTree($item['children']), ]); })->all(); diff --git a/src/Structures/Page.php b/src/Structures/Page.php index b55233ac58c..24750104050 100644 --- a/src/Structures/Page.php +++ b/src/Structures/Page.php @@ -11,6 +11,7 @@ use Statamic\Contracts\Entries\Entry; use Statamic\Contracts\GraphQL\ResolvesValues as ResolvesValuesContract; use Statamic\Contracts\Routing\UrlBuilder; +use Statamic\Contracts\Structures\Nav; use Statamic\Data\HasAugmentedInstance; use Statamic\Data\TracksQueriedColumns; use Statamic\Facades\Blink; @@ -382,8 +383,10 @@ public function private() public function blueprint() { - // TODO: make the actual blueprint. - return new \Statamic\Fields\Blueprint; + // TODO: maybe don't have nav-specific logic right here. + if ($this->structure() instanceof Nav) { + return $this->structure()->blueprint(); + } } public function collection() diff --git a/src/Structures/TreeBuilder.php b/src/Structures/TreeBuilder.php index 307ed1ff22d..186e291f32b 100644 --- a/src/Structures/TreeBuilder.php +++ b/src/Structures/TreeBuilder.php @@ -83,9 +83,25 @@ protected function transformTreeForController($tree) $page = $item['page']; $collection = $page->collection(); + // TODO: Refactor? This is only relevant to navs. + if ($blueprint = $page->blueprint()) { + $values = $page->pageData()->merge([ + 'title' => $page->title(), + 'url' => $page->reference() ? null : $page->url(), + ])->all(); + + $fields = $blueprint + ->fields() + ->addValues($values) + ->preProcess(); + $values = $fields->values(); + $meta = $fields->meta(); + } + return [ 'id' => $page->id(), - 'title' => $page->title(), + 'title' => $page->hasCustomTitle() ? $page->title() : null, + 'entry_title' => $page->referenceExists() ? $page->entry()->value('title') : null, 'url' => $page->url(), 'edit_url' => $page->editUrl(), 'can_delete' => $page->referenceExists() ? User::current()->can('delete', $page->entry()) : true, @@ -98,6 +114,8 @@ protected function transformTreeForController($tree) 'edit_url' => $collection->showUrl(), 'create_url' => $collection->createEntryUrl(), ], + 'values' => $values ?? [], + 'meta' => $meta ?? [], 'children' => (! empty($item['children'])) ? $this->transformTreeForController($item['children']) : [], ]; })->values()->all(); From 73a0de531fbbe8af9fb7c24106eae17dd370629a Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 5 Jul 2021 12:22:31 -0400 Subject: [PATCH 14/67] Add edit blueprint to twirldown --- resources/js/components/navigation/View.vue | 2 +- resources/views/navigation/show.blade.php | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/resources/js/components/navigation/View.vue b/resources/js/components/navigation/View.vue index 7eca7a8d2d2..d4699f17332 100644 --- a/resources/js/components/navigation/View.vue +++ b/resources/js/components/navigation/View.vue @@ -9,7 +9,7 @@

- + diff --git a/resources/views/navigation/show.blade.php b/resources/views/navigation/show.blade.php index 304d5456d9b..53d851dbeee 100644 --- a/resources/views/navigation/show.blade.php +++ b/resources/views/navigation/show.blade.php @@ -17,6 +17,15 @@ :max-depth="{{ $nav->maxDepth() ?? 'Infinity' }}" :expects-root="{{ $str::bool($expectsRoot) }}" :blueprint="{{ json_encode($blueprint) }}" - > + > + + @endsection From 746d315be0cc8eae41e56a79a40fb26a071a5eab Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 5 Jul 2021 12:32:05 -0400 Subject: [PATCH 15/67] Add blueprint field to edit nav form --- resources/lang/da/messages.php | 1 + resources/lang/de/messages.php | 1 + resources/lang/de_CH/messages.php | 1 + resources/lang/en/messages.php | 1 + resources/lang/es/messages.php | 1 + resources/lang/fr/messages.php | 1 + resources/lang/id/messages.php | 1 + resources/lang/it/messages.php | 1 + resources/lang/nl/messages.php | 1 + resources/lang/pt/messages.php | 1 + resources/lang/sl/messages.php | 1 + resources/lang/zh_TW/messages.php | 1 + .../CP/Structures/NavigationController.php | 12 ++++++++++-- 13 files changed, 22 insertions(+), 2 deletions(-) diff --git a/resources/lang/da/messages.php b/resources/lang/da/messages.php index a9df4dbeb11..05c64d7608c 100644 --- a/resources/lang/da/messages.php +++ b/resources/lang/da/messages.php @@ -116,6 +116,7 @@ 'licensing_utility_description' => 'Se og løs licensoplysninger.', 'max_depth_instructions' => 'Indstil et maksimalt antal niveauer, der muligvis er indlejret. Uden begrænsning hvis denne indstilling er blank.', 'max_items_instructions' => 'Indstil et maksimalt antal valgbare emner.', + 'navigation_configure_blueprint_instructions' => 'Vælg mellem eksisterende blueprints, eller opret et nyt.', 'navigation_configure_collections_instructions' => 'Aktiver link til poster i disse samlinger.', 'navigation_configure_handle_instructions' => 'Bruges til at henvise til denne navigation på frontend. Det er besværligt at ændre senere.', 'navigation_configure_intro' => 'Navigationer er lister over flere niveauer af links, der kan bruges til at oprette navbars, sidefødder, sitemaps og andre former for frontend navigation.', diff --git a/resources/lang/de/messages.php b/resources/lang/de/messages.php index ceb8e90f307..39150c4813c 100644 --- a/resources/lang/de/messages.php +++ b/resources/lang/de/messages.php @@ -118,6 +118,7 @@ 'licensing_utility_description' => 'Lizenzdetails anzeigen und auflösen.', 'max_depth_instructions' => 'Maximale Anzahl von Ebenen festlegen, in denen Seiten verschachtelt werden dürfen. Für eine unbegrenzte Tiefe leer lassen.', 'max_items_instructions' => 'Maximale Anzahl auswählbarer Einträge festlegen.', + 'navigation_configure_blueprint_instructions' => 'Wähle aus vorhandenen Blueprints oder erstelle einen Neuen.', 'navigation_configure_collections_instructions' => 'Aus diesen Sammlungen können Einträge verknüpft werden.', 'navigation_configure_handle_instructions' => 'Verweist im Frontend auf diese Navigation. Es ist nicht unproblematisch, dies nachträglich zu ändern.', 'navigation_configure_intro' => 'Navigationen sind mehrstufige Linklisten, mit denen Navbars, Footers, Sitemaps und andere Formen der Frontend-Navigation erstellt werden können.', diff --git a/resources/lang/de_CH/messages.php b/resources/lang/de_CH/messages.php index e7a0c350f14..00cadb21727 100644 --- a/resources/lang/de_CH/messages.php +++ b/resources/lang/de_CH/messages.php @@ -118,6 +118,7 @@ 'licensing_utility_description' => 'Lizenzdetails anzeigen und auflösen.', 'max_depth_instructions' => 'Maximale Anzahl von Ebenen festlegen, in denen Seiten verschachtelt werden dürfen. Für eine unbegrenzte Tiefe leer lassen.', 'max_items_instructions' => 'Maximale Anzahl auswählbarer Einträge festlegen.', + 'navigation_configure_blueprint_instructions' => 'Wähle aus vorhandenen Blueprints oder erstelle einen Neuen.', 'navigation_configure_collections_instructions' => 'Aus diesen Sammlungen können Einträge verknüpft werden.', 'navigation_configure_handle_instructions' => 'Verweist im Frontend auf diese Navigation. Es ist nicht unproblematisch, dies nachträglich zu ändern.', 'navigation_configure_intro' => 'Navigationen sind mehrstufige Linklisten, mit denen Navbars, Footers, Sitemaps und andere Formen der Frontend-Navigation erstellt werden können.', diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php index 5314e5a23a5..16ac76e2656 100644 --- a/resources/lang/en/messages.php +++ b/resources/lang/en/messages.php @@ -118,6 +118,7 @@ 'licensing_utility_description' => 'View and resolve licensing details.', 'max_depth_instructions' => 'Set a maximum number of levels page may be nested. Leave blank for no limit.', 'max_items_instructions' => 'Set a maximum number of selectable items.', + 'navigation_configure_blueprint_instructions' => 'Choose from existing Blueprints or create a new one.', 'navigation_configure_collections_instructions' => 'Enable linking to entries in these collections.', 'navigation_configure_handle_instructions' => 'Used to reference this navigation on the frontend. It\'s non-trivial to change later.', 'navigation_configure_intro' => 'Navigations are multi-level lists of links that can be used to build navbars, footers, sitemaps, and other forms of frontend navigation.', diff --git a/resources/lang/es/messages.php b/resources/lang/es/messages.php index 8920fdb6b19..b573b82dcbf 100644 --- a/resources/lang/es/messages.php +++ b/resources/lang/es/messages.php @@ -117,6 +117,7 @@ 'licensing_utility_description' => 'Ver y resolver los detalles de la licencia.', 'max_depth_instructions' => 'Establece el número máximo de niveles en los que una página pueda ser anidada. Déjalo en blanco para que sea ilimitado.', 'max_items_instructions' => 'Establece un número máximo de elementos seleccionables.', + 'navigation_configure_blueprint_instructions' => 'Elige entre planos existentes o crea uno nuevo.', 'navigation_configure_collections_instructions' => 'Habilita el enlace a entradas en estas colecciones.', 'navigation_configure_handle_instructions' => 'Se usa para hacer referencia a esta navegación en la interfaz. Es complicado cambiarlo más tarde.', 'navigation_configure_intro' => 'Las navegaciones son listas de enlaces de varios niveles que se pueden utilizar para crear barras de navegación, menús, mapas del sitio y cualquier forma de navegación en el front-end.', diff --git a/resources/lang/fr/messages.php b/resources/lang/fr/messages.php index e26619808d9..7897a69c2c1 100644 --- a/resources/lang/fr/messages.php +++ b/resources/lang/fr/messages.php @@ -118,6 +118,7 @@ 'licensing_utility_description' => 'Affichez et résolvez les détails de la licence.', 'max_depth_instructions' => 'Définissez le nombre maximum de niveaux sur lesquels une page peut être imbriquée. Laissez vide pour aucune limite.', 'max_items_instructions' => 'Définissez un nombre maxi d’éléments sélectionnables.', + 'navigation_configure_blueprint_instructions' => 'Choisissez un Blueprint existant ou créez-en un nouveau.', 'navigation_configure_collections_instructions' => 'Activer le lien vers les entrées de ces collections.', 'navigation_configure_handle_instructions' => 'Comment vous allez faire référence à cette navigation sur le frontal. Ne peut pas être facilement changé.', 'navigation_configure_intro' => 'Les navigations sont des listes multi-niveaux de liens qui peuvent être utilisées pour construire des barres de navigation, des pieds de page, des plans de site et d’autres formes de navigation sur le frontal.', diff --git a/resources/lang/id/messages.php b/resources/lang/id/messages.php index b87dfa8531f..24a70c5a98d 100644 --- a/resources/lang/id/messages.php +++ b/resources/lang/id/messages.php @@ -115,6 +115,7 @@ 'licensing_utility_description' => 'Lihat dan selesaikan detail lisensi.', 'max_depth_instructions' => 'Tetapkan jumlah maksimum halaman level yang dapat disarangkan. Biarkan kosong untuk tidak ada batas.', 'max_items_instructions' => 'Tetapkan jumlah maksimum item yang dapat dipilih.', + 'navigation_configure_blueprint_instructions' => 'Pilih dari Cetak Biru yang ada atau buat yang baru.', 'navigation_configure_collections_instructions' => 'Aktifkan penautan ke entri dalam koleksi ini.', 'navigation_configure_handle_instructions' => 'Digunakan untuk mereferensikan navigasi ini di frontend. Tidak sepele untuk berubah nanti.', 'navigation_configure_intro' => 'Navigasi adalah daftar tautan multi-level yang dapat digunakan untuk membuat bilah navigasi, footer, peta situs, dan bentuk navigasi frontend lainnya..', diff --git a/resources/lang/it/messages.php b/resources/lang/it/messages.php index 5d625508d43..9685bde48a2 100644 --- a/resources/lang/it/messages.php +++ b/resources/lang/it/messages.php @@ -115,6 +115,7 @@ 'licensing_utility_description' => 'Visualizza e risolvi i dettagli della licenza.', 'max_depth_instructions' => 'Imposta il numero massimo di livelli di profondità di una pagina. Lascia vuoto per nessun limite.', 'max_items_instructions' => 'Imposta il numero massimo di voci selezionabili.', + 'navigation_configure_blueprint_instructions' => 'Scegli tra i progetti esistenti o creane uno nuovo.', 'navigation_configure_collections_instructions' => 'Abilita il collegamento alle voci in queste raccolte.', 'navigation_configure_handle_instructions' => 'Utilizzato come riferimento a questo menu sul frontend. Non è semplice modificarlo successivamente.', 'navigation_configure_intro' => 'I menu sono elenchi multi-livello di collegamenti che possono essere utilizzati per creare barre di navigazione, footer, sitemap e così via.', diff --git a/resources/lang/nl/messages.php b/resources/lang/nl/messages.php index 796b3f98f1c..de7d387a9ea 100644 --- a/resources/lang/nl/messages.php +++ b/resources/lang/nl/messages.php @@ -118,6 +118,7 @@ 'licensing_utility_description' => 'Bekijk en los licentiegegevens op.', 'max_depth_instructions' => 'Stel een maximum aan het aantal pagina\'s dat genest mag worden. Laat leeg voor geen limiet.', 'max_items_instructions' => 'Stel een maximum aan het aantal items dat geselecteerd mag worden.', + 'navigation_configure_blueprint_instructions' => 'Kies uit bestaande blueprints of maak een nieuwe aan.', 'navigation_configure_collections_instructions' => 'Sta toe dat er naar entries in deze collectie gelinkt mogen worden.', 'navigation_configure_handle_instructions' => 'Wordt gebruikt om aan deze navigatie te refereren aan in de frontend. Het is niet persé eenvoudig om dit nadien te wijzigen.', 'navigation_configure_intro' => 'Navigaties zijn multi-level lijsten met links die gebruikt kunnen worden om navigatiemenu\'s, footers, sitemaps en andere frontendnavigaties te maken.', diff --git a/resources/lang/pt/messages.php b/resources/lang/pt/messages.php index cbbdde5cf86..40fdc7983a4 100644 --- a/resources/lang/pt/messages.php +++ b/resources/lang/pt/messages.php @@ -115,6 +115,7 @@ 'licensing_utility_description' => 'Ver e resolver detalhes de licenciamento.', 'max_depth_instructions' => 'O número máximo de níveis em uma página pode estar encadeado. Deixe em branco sem limite.', 'max_items_instructions' => 'Defina um número máximo de itens seleccionáveis.', + 'navigation_configure_blueprint_instructions' => 'Escolha entre os diagramas existentes ou crie um novo.', 'navigation_configure_collections_instructions' => 'Habilitar a ligação às entradas nestas colecções.', 'navigation_configure_handle_instructions' => 'Utilizado para fazer referência a esta navegação no frontend. É não trivial mudar mais tarde.', 'navigation_configure_intro' => 'As navegações são listas de vários níveis de ligações que podem ser usados para construir barras de navegação, rodapés, sitesmaps e outras formas de navegação front-end.', diff --git a/resources/lang/sl/messages.php b/resources/lang/sl/messages.php index 4ead103607a..0dff09dd09b 100644 --- a/resources/lang/sl/messages.php +++ b/resources/lang/sl/messages.php @@ -115,6 +115,7 @@ 'licensing_utility_description' => 'Oglejte si in razrešite podrobnosti o licenciranju.', 'max_depth_instructions' => 'Nastavite lahko največje število ravni. Pustite prazno brez omejitev.', 'max_items_instructions' => 'Nastavite največje število izbirnih elementov.', + 'navigation_configure_blueprint_instructions' => 'Izberite med obstoječimi načrti ali ustvarite novega.', 'navigation_configure_collections_instructions' => 'Omogoči povezavo do vnosov v teh zbirkah.', 'navigation_configure_handle_instructions' => 'Uporablja se za sklicevanje na to navigacijo na čelni strani. Pozneje se spreminjati ni trivialno.', 'navigation_configure_intro' => 'Navigacije so večstopenjski seznami povezav, ki jih je mogoče uporabiti za izdelavo navbarov, nogic, zemljevidov mest in drugih oblik čelne navigacije.', diff --git a/resources/lang/zh_TW/messages.php b/resources/lang/zh_TW/messages.php index 3e6ceb61739..115be394419 100644 --- a/resources/lang/zh_TW/messages.php +++ b/resources/lang/zh_TW/messages.php @@ -117,6 +117,7 @@ 'licensing_utility_description' => '檢視並解析授權詳情。', 'max_depth_instructions' => '設定頁面可嵌套的最大層級數。留空表示無限制。', 'max_items_instructions' => '設定最大可選項目的數量。', + 'navigation_configure_blueprint_instructions' => '從現有的藍圖中選擇或建立新藍圖。', 'navigation_configure_collections_instructions' => '允許連結到這些條目集中的條目。', 'navigation_configure_handle_instructions' => '用於在前端參照到此導航。設定後將難以更改。', 'navigation_configure_intro' => '導航列是多層級的連結列表,可用於建構導航列、頁腳連結、網站地圖、以及其他格式的前端導航。', diff --git a/src/Http/Controllers/CP/Structures/NavigationController.php b/src/Http/Controllers/CP/Structures/NavigationController.php index 1a2d63ebd45..b907658f668 100644 --- a/src/Http/Controllers/CP/Structures/NavigationController.php +++ b/src/Http/Controllers/CP/Structures/NavigationController.php @@ -93,7 +93,7 @@ public function update(Request $request, $nav) $this->authorize('update', $nav, __('You are not authorized to configure navs.')); - $fields = $this->editFormBlueprint()->fields()->addValues($request->all()); + $fields = $this->editFormBlueprint($nav)->fields()->addValues($request->all()); $fields->validate(); @@ -152,7 +152,7 @@ public function store(Request $request) return ['redirect' => $structure->showUrl()]; } - public function editFormBlueprint() + public function editFormBlueprint($nav) { $contents = [ 'name' => [ @@ -169,6 +169,14 @@ public function editFormBlueprint() 'options' => [ 'display' => __('Options'), 'fields' => [ + 'blueprint' => [ + 'type' => 'html', + 'instructions' => __('statamic::messages.navigation_configure_blueprint_instructions'), + 'html' => ''. + '', + ], 'collections' => [ 'display' => __('Collections'), 'instructions' => __('statamic::messages.navigation_configure_collections_instructions'), From 154bd477026c8e2396f4ae2d2c6c088cc5a4b81f Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 5 Jul 2021 12:50:00 -0400 Subject: [PATCH 16/67] Add edit entry link to page editor --- resources/js/components/navigation/View.vue | 1 + resources/js/components/structures/PageEditor.vue | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/resources/js/components/navigation/View.vue b/resources/js/components/navigation/View.vue index d4699f17332..a6da57bb252 100644 --- a/resources/js/components/navigation/View.vue +++ b/resources/js/components/navigation/View.vue @@ -128,6 +128,7 @@ + + @@ -56,6 +63,7 @@ export default { initialValues: Object, initialMeta: Object, blueprint: Object, + editEntryUrl: String }, data() { From 54dccc9eb6737e85cf033e1ce8d25bd6972c4e93 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 5 Jul 2021 12:53:08 -0400 Subject: [PATCH 17/67] Add link to edit entry in the page twirldown --- resources/js/components/navigation/View.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/js/components/navigation/View.vue b/resources/js/components/navigation/View.vue index a6da57bb252..eaa75f278aa 100644 --- a/resources/js/components/navigation/View.vue +++ b/resources/js/components/navigation/View.vue @@ -102,6 +102,10 @@