File Coverage

File:lib/Yukki/Web/View.pm
Coverage:83.9%

linestmtbrancondsubpodtimecode
1package Yukki::Web::View;
2
3
7
7
51
20
use v5.24;
4
7
7
7
21
7
35
use utf8;
5
7
7
7
96
26
20
use Moo;
6
7
7
7
7
3566
116465
40
use Type::Params qw( validate );
8
7
7
7
1105
8
217
use Scalar::Util qw( blessed reftype );
9
7
7
7
2231
108696
128
use Spreadsheet::Engine;
10
7
7
7
5172
191105
150
use Template::Pure;
11
7
7
7
3280
135663
208
use Text::MultiMarkdown;
12
7
7
7
219
577
184
use Try::Tiny;
13
7
7
7
464
4338
45
use Type::Utils;
14
7
7
7
6385
9
31
use Types::Standard qw( Dict Str ArrayRef HashRef slurpy );
15
16
7
7
7
4580
4464
40
use namespace::clean;
17
18# ABSTRACT: base class for Yukki::Web views
19
20 - 30
=head1 DESCRIPTION

This is the base class for all L<Yukki::Web> views.

=head1 ATTRIBUTES

=head2 app

This is the L<Yukki::Web> singleton.

=cut
31
32has app => (
33    is          => 'ro',
34    isa         => class_type('Yukki::Web'),
35    required    => 1,
36    weak_ref    => 1,
37    handles     => 'Yukki::Role::App',
38);
39
40 - 47
=head2 markdown

This is the L<Text::MultiMarkdown> object for rendering L</yukkitext>. Do not
use.

Provides a C<format_markdown> method delegated to C<markdown>. Do not use.

=cut
48
49has markdown => (
50    is          => 'ro',
51    isa         => class_type('Text::MultiMarkdown'),
52    required    => 1,
53    lazy        => 1,
54    builder     => '_build_markdown',
55    handles     => {
56        'format_markdown' => 'markdown',
57    },
58);
59
60sub _build_markdown {
61
0
0
    Text::MultiMarkdown->new(
62        markdown_in_html_blocks => 1,
63        heading_ids             => 0,
64    );
65}
66
67 - 71
=head2 messages_template

This is the template used to render info, warning, and error messages to the page.

=cut
72
73has messages_template => (
74    is          => 'ro',
75    isa         => class_type('Template::Pure'),
76    lazy        => 1,
77    builder     => '_build_messages_template',
78);
79
80sub _build_messages_template {
81
2
24
    my $self = shift;
82
2
19
    return $self->prepare_template(
83        template   => 'messages.html',
84        directives => [
85            '.error'   => {
86                'error<-errors' => [
87                    '.' => 'error',
88                ],
89            },
90            '.warning' => {
91                'warning<-warnings' => [
92                    '.' => 'warning',
93                ],
94            },
95            '.info'    => {
96                'one_info<-info' => [
97                    '.' => 'one_info',
98                ],
99            },
100        ],
101    );
102}
103
104has _page_templates => (
105    is          => 'ro',
106    isa         => HashRef,
107    required    => 1,
108    default     => sub { +{} },
109);
110
111 - 115
=head2 links_template

This is the template object used to render links.

=cut
116
117has links_template => (
118    is          => 'ro',
119    isa         => class_type('Template::Pure'),
120    lazy        => 1,
121    builder     => '_build_links_template',
122);
123
124sub _build_links_template {
125
0
0
    my $self = shift;
126
0
0
    $self->prepare_template(
127        template   => 'links.html',
128        directives => [
129            '.links' => {
130                'link<-links' => [
131                    'a'      => 'link.label',
132                    'a@href' => 'link.href',
133                ],
134            },
135        ],
136    );
137}
138
139 - 147
=head1 METHODS

=head2 page_template

  my $template = $self->page_template('default');

Returns the template used to render pages for the given style name.

=cut
148
149sub page_template {
150
2
1
6
    my ($self, $which) = @_;
151
152    return $self->_page_templates->{ $which }
153
2
11
        if $self->_page_templates->{ $which };
154
155
2
7
    my $view = $which // 'default';
156
2
44
    my $view_args = $self->app->settings->page_views->{ $view }
157                 // { template => 'shell.html' };
158
2
28
    $view_args->{directives} //= [];
159
160    my %menu_vars = map {
161
8
27
        my $menu_name = $_;
162
8
48
        "#nav-$menu_name .navigation" => {
163            "menu_item<-$menu_name" => [
164                'a'      => 'menu_item.label',
165                'a@href' => 'menu_item.href',
166            ],
167        },
168
2
2
5
41
    } @{ $self->app->settings->menu_names };
169
170    return $self->_page_templates->{ $which } = $self->prepare_template(
171        template   => $view_args->{template},
172        directives => [
173            $view_args->{directives}->@*,
174
2
28
            'head script.local' => {
175                'script<-scripts' => [
176                    '@src' => 'script',
177                ],
178            },
179            'head link.local'   => {
180                'link<-links' => [
181                    '@href' => 'link',
182                ],
183            },
184            '#messages'   => 'messages | encoded_string',
185            'title'       => 'main_title',
186            '.masthead-title' => 'title',
187            %menu_vars,
188            '#breadcrumb li' => {
189                'crumb<-breadcrumb' => [
190                    'a'      => 'crumb.label',
191                    'a@href' => 'crumb.href',
192                ],
193            },
194            '#content'    => 'content | encoded_string',
195        ],
196    );
197}
198
199 - 212
=head2 prepare_template

  my $template = $self->prepare_template({
      template   => 'foo.html',
      directives => { ... },
  });

This prepares a template for later rendering.

The C<template> is the name of the template file to use.

The C<directives> are the L<Template::Pure> directives to apply data given at render time to modify the template to create the output.

=cut
213
214sub prepare_template {
215
6
1
28
    my ($self, $opt)
216        = validate(\@_, class_type(__PACKAGE__),
217            slurpy Dict[
218                template   => Str,
219                directives => ArrayRef,
220            ]);
221
6
6
29441
422
    my ($template, $directives) = @{$opt}{qw( template directives )};
222
223
6
31
    my $template_content =
224        $self->app->locate_dir('template_path', $template)->slurp;
225
226
6
853
    return Template::Pure->new(
227        template   => $template_content,
228        directives => $directives,
229    );
230}
231
232 - 247
=head2 render_page

  my $document = $self->render_page({
      template => 'foo.html',
      context  => $ctx,
      vars     => { ... },
  });

This renders the given template and places it into the content section of the
F<shell.html> template.

The C<context> is used to render parts of the shell template.

The C<vars> are processed against the given template with L<Template::Pure>.

=cut
248
249sub render_page {
250
2
1
946
    my ($self, $opt)
251        = validate(\@_, class_type(__PACKAGE__),
252            slurpy Dict[
253                template => class_type('Template::Pure'),
254                context  => class_type('Yukki::Web::Context'),
255                vars     => HashRef,
256            ]);
257
2
2
13842
180
    my ($template, $ctx, $vars) = @{$opt}{qw( template context vars )};
258
2
9
    $vars //= {};
259
260
2
46
    my $messages = $self->render(
261        template => $self->messages_template,
262        context  => $ctx,
263        vars     => {
264            errors   => $ctx->has_errors   ? [ $ctx->list_errors   ] : undef,
265            warnings => $ctx->has_warnings ? [ $ctx->list_warnings ] : undef,
266            info     => $ctx->has_info     ? [ $ctx->list_info     ] : undef,
267        },
268    );
269
270
2
4133
    my ($main_title, $title);
271
2
61
    if ($ctx->response->has_page_title) {
272
2
60
        $title      = $ctx->response->page_title;
273
2
90
        $main_title = $ctx->response->page_title . ' - Yukki';
274    }
275    else {
276
0
0
        $title = $main_title = 'Yukki';
277    }
278
279    my %menu_vars = map {
280
8
42
        $_ => $self->available_menu_items($ctx, $_)
281
2
2
50
41
    } @{ $self->app->settings->menu_names };
282
283
2
43
    my @scripts = $self->app->settings->all_scripts;
284
2
39
    my @styles  = $self->app->settings->all_styles;
285
286
2
39
    my $view      = $ctx->request->parameters->{view} // 'default';
287
288
2
592
    $vars->{'head script.local'} //= [];
289
2
23
    $vars->{'head link.local'}   //= [];
290
291    return $self->render(
292        template => $self->page_template($view),
293        context  => $ctx,
294        vars     => {
295            $vars->%*,
296            scripts      => [
297
12
20032
                map { $ctx->rebase_url($_) }
298                    @scripts,
299                    $vars->{'head script.local'}->@*,
300            ],
301            links        => [
302
4
668
                map { $ctx->rebase_url($_) }
303                    @styles,
304                    $vars->{'head link.local'}->@*,
305            ],
306            'messages'   => $messages,
307            'main_title' => $main_title,
308            'title'      => $title,
309            %menu_vars,
310            'breadcrumb' => $ctx->response->has_breadcrumb ? [
311                map {
312
2
12
                    +{
313                        %$_,
314
1
9
                        href => $ctx->rebase_url($_->{href}),
315                    }
316                } $ctx->response->breadcrumb_links
317            ] : undef,
318            'content'    => $self->render(
319                template => $template,
320                context  => $ctx,
321                vars     => $vars,
322            ),
323        },
324    );
325}
326
327 - 333
=head2 available_menu_items

  my @items = $self->available_menu_items($ctx, 'menu_name');

Retrieves the navigation menu from the L<Yukki::Web::Response> and purges any links that the current user does not have access to.

=cut
334
335sub available_menu_items {
336
8
1
20
    my ($self, $ctx, $name) = @_;
337
338    my @items = map {
339        +{
340            %$_,
341
9
1297
            href => $ctx->rebase_url($_->{href}),
342        },
343    } grep {
344
8
15
15
141
33
31
        my $url = $_->{href}; $url =~ s{\?.*$}{};
345
346
15
207
        my $match = $self->app->router->match($url);
347
15
99
        return unless $match;
348
15
35
        my $access_level_needed = $match->access_level;
349        $self->check_access(
350            user       => $ctx->session->{user},
351            repository => $match->mapping->{repository} // '-',
352
15
227
            special    => $match->mapping->{special} // '-',
353            needs      => $access_level_needed,
354        );
355    } $ctx->response->navigation_menu($name);
356
357
8
764
    return @items ? \@items : undef;
358}
359
360 - 366
=head2 render_links

  my $document = $self->render_links($ctx, \@navigation_links);

This renders a set of links using the F<links.html> template.

=cut
367
368sub render_links {
369
0
1
0
    my ($self, $opt)
370        = validate(\@_, class_type(__PACKAGE__),
371            slurpy Dict[
372                context => class_type('Yukki::Web::Context'),
373                links   => ArrayRef[HashRef],
374            ]);
375
0
0
0
0
    my ($ctx, $links) = @{$opt}{qw( context links )};
376
377    return $self->render(
378        template => $self->links_template,
379        context  => $ctx,
380        vars     => {
381            links => [ map {
382
0
0
                +{
383                    label => $_->{label},
384
0
0
                    href  => $ctx->rebase_url($_->{href}),
385                }
386            } @$links ],
387        },
388    );
389}
390
391 - 401
=head2 render

  my $document = $self->render({
      template => $template,
      vars     => { ... },
  });

This renders the given L<Template::Pure>. The C<vars> are
used as the ones passed to the C<process> method.

=cut
402
403sub render {
404
6
1
2337
    my ($self, $opt)
405        = validate(\@_, class_type(__PACKAGE__),
406            slurpy Dict[
407                template => class_type('Template::Pure'),
408                context  => class_type('Yukki::Web::Context'),
409                vars     => HashRef,
410            ]);
411
6
6
39967
560
    my ($template, $ctx, $vars) = @{$opt}{qw( template context vars )};
412
6
21
    $vars //= {};
413
414
6
37
    my %vars = (
415        %$vars,
416        ctx  => $ctx,
417        view => $self,
418    );
419
420
6
25
    return $template->render($vars);
421}
422
4231;