Skip to content

Commit d740601

Browse files
committed
feat: Follow navigation in preview view
1 parent 7b4df29 commit d740601

File tree

6 files changed

+196
-26
lines changed

6 files changed

+196
-26
lines changed

config/areas/site/views.php

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
use Kirby\Cms\App;
44
use Kirby\Cms\Find;
5-
use Kirby\Exception\PermissionException;
5+
use Kirby\Panel\Controller\View\ModelPreviewViewController;
6+
use Kirby\Panel\Panel;
67
use Kirby\Panel\Ui\Buttons\ViewButtons;
8+
use Kirby\Toolkit\A;
79
use Kirby\Toolkit\I18n;
810

911
return [
@@ -20,16 +22,15 @@
2022
'page.preview' => [
2123
'pattern' => 'pages/(:any)/preview/(changes|latest|compare)',
2224
'action' => function (string $path, string $versionId) {
25+
// handle redirect if view was reloaded with a redirect URL
26+
// after navigating to a different page inside the preview browser
27+
if ($redirect = ModelPreviewViewController::redirect($versionId)) {
28+
Panel::go($redirect);
29+
}
30+
2331
$page = Find::page($path);
2432
$view = $page->panel()->view();
25-
$src = [
26-
'latest' => $page->previewUrl('latest'),
27-
'changes' => $page->previewUrl('changes'),
28-
];
29-
30-
if ($src['latest'] === null) {
31-
throw new PermissionException('The preview is not available');
32-
}
33+
$src = ModelPreviewViewController::src($page);
3334

3435
return [
3536
'component' => 'k-preview-view',
@@ -64,16 +65,15 @@
6465
'site.preview' => [
6566
'pattern' => 'site/preview/(changes|latest|compare)',
6667
'action' => function (string $versionId) {
68+
// handle redirect if view was reloaded with a redirect URL
69+
// after navigating to a different page inside the preview browser
70+
if ($redirect = ModelPreviewViewController::redirect($versionId)) {
71+
Panel::go($redirect);
72+
}
73+
6774
$site = App::instance()->site();
6875
$view = $site->panel()->view();
69-
$src = [
70-
'latest' => $site->previewUrl('latest'),
71-
'changes' => $site->previewUrl('changes'),
72-
];
73-
74-
if ($src['latest'] === null) {
75-
throw new PermissionException('The preview is not available');
76-
}
76+
$src = ModelPreviewViewController::src($site);
7777

7878
return [
7979
'component' => 'k-preview-view',

panel/src/components/Views/Preview/PreviewBrowser.vue

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
@submit="$emit('submit', $event)"
2222
/>
2323
</template>
24+
2425
<k-button :link="src" icon="open" size="xs" target="_blank" />
2526
</k-button-group>
2627
</header>
2728

28-
<iframe ref="browser" :src="srcWithPreviewParam" />
29+
<iframe ref="browser" :src="src" @load="onLoad" />
2930
</div>
3031
</template>
3132

@@ -39,12 +40,10 @@ export default {
3940
src: String,
4041
versionId: String
4142
},
42-
emits: ["discard", "submit"],
43+
emits: ["discard", "navigate", "submit"],
4344
computed: {
44-
srcWithPreviewParam() {
45-
const uri = new URL(this.src, this.$panel.urls.site);
46-
uri.searchParams.append("_preview", true);
47-
return uri.toString();
45+
window() {
46+
return this.$refs.browser.contentWindow;
4847
}
4948
},
5049
mounted() {
@@ -56,8 +55,25 @@ export default {
5655
this.$events.off("content.publish", this.reload);
5756
},
5857
methods: {
58+
onLoad() {
59+
for (const link of this.window.document.querySelectorAll("a")) {
60+
if (!link.href || link.onclick) {
61+
continue;
62+
}
63+
64+
if (link.href.startsWith(location.origin) === false) {
65+
link.target = "_blank";
66+
continue;
67+
}
68+
69+
link.addEventListener("click", (e) => {
70+
e.preventDefault();
71+
this.$emit("navigate", link.href);
72+
});
73+
}
74+
},
5975
reload() {
60-
this.$refs.browser.contentWindow.location.reload();
76+
this.window.location.reload();
6177
}
6278
}
6379
};

panel/src/components/Views/Preview/PreviewView.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,21 @@
3333
<k-preview-browser
3434
v-bind="browserProps('latest')"
3535
@discard="onDiscard"
36+
@navigate="onNavigate"
3637
@submit="onSubmit"
3738
/>
3839
<k-preview-browser
3940
v-bind="browserProps('changes')"
4041
@discard="onDiscard"
42+
@navigate="onNavigate"
4143
@submit="onSubmit"
4244
/>
4345
</template>
4446
<template v-else>
4547
<k-preview-browser
4648
v-bind="browserProps(versionId)"
4749
@discard="onDiscard"
50+
@navigate="onNavigate"
4851
@submit="onSubmit"
4952
/>
5053
</template>
@@ -101,6 +104,9 @@ export default {
101104
102105
const url = this.$api.pages.url(page.id, "preview/" + this.versionId);
103106
this.$panel.view.open(url);
107+
},
108+
onNavigate(redirect) {
109+
this.$panel.view.reload({ query: { redirect } });
104110
}
105111
}
106112
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
namespace Kirby\Panel\Controller\View;
4+
5+
use Kirby\Cms\App;
6+
use Kirby\Cms\ModelWithContent;
7+
use Kirby\Cms\Page;
8+
use Kirby\Cms\Site;
9+
use Kirby\Exception\LogicException;
10+
use Kirby\Exception\PermissionException;
11+
use Kirby\Http\Uri;
12+
use Kirby\Toolkit\A;
13+
14+
/**
15+
* Temporary class to support the preview view
16+
* and bundle logic before the actual class will be
17+
* added in Kirby 6.0.0
18+
*
19+
* @internal
20+
*/
21+
class ModelPreviewViewController
22+
{
23+
public static function redirect(string $versionId): string|null
24+
{
25+
$kirby = App::instance();
26+
27+
// Get redirect URL path
28+
if ($redirect = $kirby->request()->get('redirect')) {
29+
$redirect = new Uri($redirect);
30+
31+
// Look up new model and redirect to its preview
32+
if ($result = $kirby->call($redirect->path, 'GET')) {
33+
34+
// @codeCoverageIgnoreStart
35+
if ($result instanceof ModelWithContent === false) {
36+
throw new LogicException(
37+
message: 'Cannot redirect the preview view to an URL that does not belong to any model'
38+
);
39+
}
40+
// @codeCoverageIgnoreEnd
41+
42+
$url = $result->panel()->url() . '/preview/' . $versionId;
43+
$url = new Uri($url);
44+
45+
// Preserve the redirect URL's query and params
46+
// and inject them into the new URL
47+
unset(
48+
$redirect->query()->_token,
49+
$redirect->query()->_version,
50+
$redirect->query()->_preview
51+
);
52+
53+
if ($redirect->query->isNotEmpty() === true) {
54+
$url->query->_query = $redirect->query->toString();
55+
}
56+
57+
if ($redirect->params->isNotEmpty() === true) {
58+
$url->query->_params = $redirect->params->toString();
59+
}
60+
61+
return $url->toString();
62+
}
63+
}
64+
65+
return null;
66+
}
67+
68+
public static function src(Page|Site $model): array
69+
{
70+
$src = [
71+
'latest' => $model->previewUrl('latest'),
72+
'changes' => $model->previewUrl('changes'),
73+
];
74+
75+
if ($src['latest'] === null) {
76+
throw new PermissionException('The preview is not available');
77+
}
78+
79+
return A::map(
80+
$src,
81+
function (string $url) use ($model): string {
82+
$uri = new Uri($url);
83+
84+
// set the preview flag
85+
$uri->query()->_preview = 'true';
86+
87+
// inject params and query from a redirect
88+
$uri->params->merge($model->kirby()->request()->get('_params'));
89+
$uri->query->merge($model->kirby()->request()->get('_query'));
90+
91+
return $uri->toString();
92+
}
93+
);
94+
}
95+
}

tests/Panel/Areas/SiteTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public function testPagePreview(): void
143143

144144
$this->assertSame('k-preview-view', $view['component']);
145145
$this->assertSame('Test | Preview', $view['title']);
146-
$this->assertSame('/test?_token=' . $token . '&_version=changes', $props['src']['changes']);
146+
$this->assertSame('/test?_token=' . $token . '&_version=changes&_preview=true', $props['src']['changes']);
147147
}
148148

149149
public function testSiteWithoutAuthentication(): void
@@ -246,7 +246,7 @@ public function testSitePreview(): void
246246

247247
$this->assertSame('k-preview-view', $view['component']);
248248
$this->assertSame('Site | Preview', $view['title']);
249-
$this->assertSame('/?_token=' . $token . '&_version=changes', $props['src']['changes']);
249+
$this->assertSame('/?_token=' . $token . '&_version=changes&_preview=true', $props['src']['changes']);
250250
}
251251

252252
public function testSiteTitle(): void
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace Kirby\Panel\Controller\View;
4+
5+
use Kirby\Cms\App;
6+
use Kirby\TestCase;
7+
use PHPUnit\Framework\Attributes\CoversClass;
8+
9+
#[CoversClass(ModelPreviewViewController::class)]
10+
class ModelPreviewViewControllerTest extends TestCase
11+
{
12+
public function testRedirect(): void
13+
{
14+
$redirect = ModelPreviewViewController::redirect('latest');
15+
$this->assertSame(null, $redirect);
16+
17+
new App([
18+
'site' => [
19+
'children' => [
20+
['slug' => 'notes']
21+
]
22+
],
23+
'request' => [
24+
'query' => [
25+
'redirect' => 'https://getkirby.com/notes/page:2?foo=bar&_preview=true'
26+
],
27+
]
28+
]);
29+
30+
$redirect = ModelPreviewViewController::redirect('latest');
31+
$this->assertSame('/panel/pages/notes/preview/latest?_query=foo%3Dbar&_params=page%3A2', $redirect);
32+
}
33+
34+
public function testSrc(): void
35+
{
36+
$app = new App([
37+
'site' => [
38+
'children' => [
39+
['slug' => 'notes']
40+
]
41+
]
42+
]);
43+
44+
$app->impersonate('kirby');
45+
46+
$model = $app->site()->page('notes');
47+
$token = $model->version('changes')->previewToken();
48+
$src = ModelPreviewViewController::src($model);
49+
50+
$this->assertSame('/notes?_preview=true', $src['latest']);
51+
$this->assertSame('/notes?_token=' . $token . '&_version=changes&_preview=true', $src['changes']);
52+
}
53+
}

0 commit comments

Comments
 (0)