| File: | lib/Yukki/Web/View/Page.pm |
| Coverage: | 35.2% |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Yukki::Web::View::Page; | ||||||
| 2 | |||||||
| 3 | 1 1 | 3348 10 | use v5.24; | ||||
| 4 | 1 1 1 | 9 4 19 | use utf8; | ||||
| 5 | 1 1 1 | 38 4 21 | use Moo; | ||||
| 6 | |||||||
| 7 | 1 1 1 | 588 4 20 | use Type::Utils; | ||||
| 8 | |||||||
| 9 | 1 1 1 | 1581 2 15 | use namespace::clean; | ||||
| 10 | |||||||
| 11 | extends 'Yukki::Web::View'; | ||||||
| 12 | |||||||
| 13 | # ABSTRACT: render HTML for viewing and editing wiki pages | ||||||
| 14 | |||||||
| 15 - 19 | =head1 DESCRIPTION Renders wiki pages. =cut | ||||||
| 20 | |||||||
| 21 | has blank_template => ( | ||||||
| 22 | is => 'ro', | ||||||
| 23 | isa => class_type('Template::Pure'), | ||||||
| 24 | lazy => 1, | ||||||
| 25 | builder => '_build_blank_template', | ||||||
| 26 | ); | ||||||
| 27 | |||||||
| 28 | sub _build_blank_template { | ||||||
| 29 | shift->prepare_template( | ||||||
| 30 | 0 | 0 | template => 'page/blank.html', | ||||
| 31 | directives => [ | ||||||
| 32 | '#yukkiname' => 'page', | ||||||
| 33 | '#create-page@href' => 'link', | ||||||
| 34 | '#file-list' => 'attachments | encoded_string', | ||||||
| 35 | ], | ||||||
| 36 | ); | ||||||
| 37 | } | ||||||
| 38 | |||||||
| 39 | has view_template => ( | ||||||
| 40 | is => 'ro', | ||||||
| 41 | isa => class_type('Template::Pure'), | ||||||
| 42 | lazy => 1, | ||||||
| 43 | builder => '_build_view_template', | ||||||
| 44 | ); | ||||||
| 45 | |||||||
| 46 | sub _build_view_template { | ||||||
| 47 | shift->prepare_template( | ||||||
| 48 | 1 | 28 | template => 'page/view.html', | ||||
| 49 | directives => [ | ||||||
| 50 | '#yukkitext' => 'html | encoded_string', | ||||||
| 51 | ], | ||||||
| 52 | ); | ||||||
| 53 | } | ||||||
| 54 | |||||||
| 55 | has history_template => ( | ||||||
| 56 | is => 'ro', | ||||||
| 57 | isa => class_type('Template::Pure'), | ||||||
| 58 | lazy => 1, | ||||||
| 59 | builder => '_build_history_template', | ||||||
| 60 | ); | ||||||
| 61 | |||||||
| 62 | sub _build_history_template { | ||||||
| 63 | shift->prepare_template( | ||||||
| 64 | template => 'page/history.html', | ||||||
| 65 | directives => [ | ||||||
| 66 | 'form@action' => 'form_action', | ||||||
| 67 | '.revision' => { | ||||||
| 68 | 'rev<-revisions' => [ | ||||||
| 69 | '.first-revision input@value' => 'rev.object_id', | ||||||
| 70 | '.second-revision input@value' => 'rev.object_id', | ||||||
| 71 | '.date' => 'rev.time_ago', | ||||||
| 72 | '.author' => 'rev.author_name', | ||||||
| 73 | '.diffstat' => '+={rev.lines_added}/-={rev.lines_removed}', | ||||||
| 74 | '.comment' => 'rev.comment | default("(no comment)")', | ||||||
| 75 | '.first-revision input' => sub { | ||||||
| 76 | 0 | 0 | my ($t, $input, $vars) = @_; | ||||
| 77 | $input->attr(checked => 'checked') | ||||||
| 78 | 0 | 0 | if $vars->{index} == 2; | ||||
| 79 | }, | ||||||
| 80 | '.second-revision input' => sub { | ||||||
| 81 | 0 | 0 | my ($t, $input, $vars) = @_; | ||||
| 82 | $input->attr(checked => 'checked') | ||||||
| 83 | 0 | 0 | if $vars->{index} == 1; | ||||
| 84 | }, | ||||||
| 85 | 0 | 0 | ], | ||||
| 86 | }, | ||||||
| 87 | ], | ||||||
| 88 | ); | ||||||
| 89 | } | ||||||
| 90 | |||||||
| 91 | has diff_template => ( | ||||||
| 92 | is => 'ro', | ||||||
| 93 | isa => class_type('Template::Pure'), | ||||||
| 94 | lazy => 1, | ||||||
| 95 | builder => '_build_diff_template', | ||||||
| 96 | ); | ||||||
| 97 | |||||||
| 98 | sub _build_diff_template { | ||||||
| 99 | shift->prepare_template( | ||||||
| 100 | 0 | 0 | template => 'page/diff.html', | ||||
| 101 | directives => [ | ||||||
| 102 | '#diff' => 'html | encoded_string', | ||||||
| 103 | ], | ||||||
| 104 | ); | ||||||
| 105 | } | ||||||
| 106 | |||||||
| 107 | has edit_template => ( | ||||||
| 108 | is => 'ro', | ||||||
| 109 | isa => class_type('Template::Pure'), | ||||||
| 110 | lazy => 1, | ||||||
| 111 | builder => '_build_edit_template', | ||||||
| 112 | ); | ||||||
| 113 | |||||||
| 114 | sub _build_edit_template { | ||||||
| 115 | shift->prepare_template( | ||||||
| 116 | 0 | 0 | template => 'page/edit.html', | ||||
| 117 | directives => [ | ||||||
| 118 | '#yukkiname' => 'page', | ||||||
| 119 | '#yukkitext' => 'source', | ||||||
| 120 | '#yukkitext_position@value' => 'position', | ||||||
| 121 | '#preview-yukkitext' => 'html | encoded_string', | ||||||
| 122 | '#attachments-list' => 'attachments | encoded_string', | ||||||
| 123 | ], | ||||||
| 124 | ); | ||||||
| 125 | } | ||||||
| 126 | |||||||
| 127 | has rename_template => ( | ||||||
| 128 | is => 'ro', | ||||||
| 129 | isa => class_type('Template::Pure'), | ||||||
| 130 | lazy => 1, | ||||||
| 131 | builder => '_build_rename_template', | ||||||
| 132 | ); | ||||||
| 133 | |||||||
| 134 | sub _build_rename_template { | ||||||
| 135 | shift->prepare_template( | ||||||
| 136 | 0 | 0 | template => 'page/rename.html', | ||||
| 137 | directives => [ | ||||||
| 138 | '#yukkiname' => 'page', | ||||||
| 139 | '#yukkiname_new@value' => 'page', | ||||||
| 140 | ], | ||||||
| 141 | ); | ||||||
| 142 | } | ||||||
| 143 | |||||||
| 144 | has remove_template => ( | ||||||
| 145 | is => 'ro', | ||||||
| 146 | isa => class_type('Template::Pure'), | ||||||
| 147 | lazy => 1, | ||||||
| 148 | builder => '_build_remove_template', | ||||||
| 149 | ); | ||||||
| 150 | |||||||
| 151 | sub _build_remove_template { | ||||||
| 152 | shift->prepare_template( | ||||||
| 153 | 0 | 0 | template => 'page/remove.html', | ||||
| 154 | directives => [ | ||||||
| 155 | '.yukkiname' => 'page', | ||||||
| 156 | '#cancel_remove@href' => 'return_link', | ||||||
| 157 | ], | ||||||
| 158 | ); | ||||||
| 159 | } | ||||||
| 160 | |||||||
| 161 | has attachments_template => ( | ||||||
| 162 | is => 'ro', | ||||||
| 163 | isa => class_type('Template::Pure'), | ||||||
| 164 | lazy => 1, | ||||||
| 165 | builder => '_build_attachments_template', | ||||||
| 166 | ); | ||||||
| 167 | |||||||
| 168 | sub _build_attachments_template { | ||||||
| 169 | shift->prepare_template( | ||||||
| 170 | 0 | 0 | template => 'page/attachments.html', | ||||
| 171 | directives => [ | ||||||
| 172 | '.file' => { | ||||||
| 173 | 'file<-files' => [ | ||||||
| 174 | '@id' => 'file.file_id', | ||||||
| 175 | '.filename' => 'file.file_name | encoded_string', | ||||||
| 176 | '.size' => 'file.file_size', | ||||||
| 177 | '.action' => 'file.action | encoded_string', | ||||||
| 178 | ], | ||||||
| 179 | }, | ||||||
| 180 | ], | ||||||
| 181 | ); | ||||||
| 182 | } | ||||||
| 183 | |||||||
| 184 - 191 | =head1 METHODS =head2 blank Renders a page that links to the edit page for this location. This helps you create the links. =cut | ||||||
| 192 | |||||||
| 193 | sub blank { | ||||||
| 194 | 0 | 1 | 0 | my ($self, $ctx, $vars) = @_; | |||
| 195 | |||||||
| 196 | 0 | 0 | my $link = "/page/edit/$vars->{repository}/$vars->{page}"; | ||||
| 197 | |||||||
| 198 | 0 | 0 | $ctx->response->page_title($vars->{title}); | ||||
| 199 | 0 | 0 | $ctx->response->breadcrumb($vars->{breadcrumb}); | ||||
| 200 | |||||||
| 201 | return $self->render_page( | ||||||
| 202 | template => $self->blank_template, | ||||||
| 203 | context => $ctx, | ||||||
| 204 | vars => { | ||||||
| 205 | page => $vars->{page}, | ||||||
| 206 | link => $link, | ||||||
| 207 | 0 | 0 | attachments => $self->attachments($ctx, $vars->{files}), | ||||
| 208 | }, | ||||||
| 209 | ); | ||||||
| 210 | } | ||||||
| 211 | |||||||
| 212 - 216 | =head2 page_navigation Sets up the page navigation menu. =cut | ||||||
| 217 | |||||||
| 218 | sub page_navigation { | ||||||
| 219 | 1 | 1 | 14 | my ($self, $response, $this_action, $vars) = @_; | |||
| 220 | |||||||
| 221 | 1 | 4 | for my $action (qw( edit history rename remove )) { | ||||
| 222 | 4 | 21 | next if $action eq $this_action; | ||||
| 223 | |||||||
| 224 | $response->add_navigation_item([ qw( page page_bottom ) ] => { | ||||||
| 225 | label => ucfirst $action, | ||||||
| 226 | 4 | 24 | href => join('/', 'page', $action, $vars->{repository}, $vars->{page}), | ||||
| 227 | sort => 20, | ||||||
| 228 | }); | ||||||
| 229 | } | ||||||
| 230 | |||||||
| 231 | 1 1 | 7 38 | for my $view_name (keys %{ $self->app->settings->page_views }) { | ||||
| 232 | 3 | 55 | my $view_info = $self->app->settings->page_views->{$view_name}; | ||||
| 233 | |||||||
| 234 | 3 | 22 | next if $view_info->{hide}; | ||||
| 235 | |||||||
| 236 | 2 | 5 | my $args = "?view=$view_name"; | ||||
| 237 | 2 | 6 | $args = '' if $view_name eq 'default'; | ||||
| 238 | |||||||
| 239 | $response->add_navigation_item([ qw( page page_bottom ) ] => { | ||||||
| 240 | label => $view_info->{label}, | ||||||
| 241 | href => join('/', 'page/view', $vars->{repository}, $vars->{page}) | ||||||
| 242 | . $args, | ||||||
| 243 | sort => $view_info->{sort}, | ||||||
| 244 | 2 | 13 | }); | ||||
| 245 | } | ||||||
| 246 | } | ||||||
| 247 | |||||||
| 248 - 252 | =head2 view Renders a page as a view. =cut | ||||||
| 253 | |||||||
| 254 | sub view { | ||||||
| 255 | 1 | 1 | 6 | my ($self, $ctx, $vars) = @_; | |||
| 256 | 1 | 5 | my $file = $vars->{file}; | ||||
| 257 | |||||||
| 258 | 1 | 54 | $ctx->response->page_title($vars->{title}); | ||||
| 259 | 1 | 80 | $ctx->response->breadcrumb($vars->{breadcrumb}); | ||||
| 260 | |||||||
| 261 | 1 | 77 | my $html = $file->fetch_formatted($ctx, -1); | ||||
| 262 | |||||||
| 263 | 1 | 26 | $self->page_navigation($ctx->response, 'view', $vars); | ||||
| 264 | |||||||
| 265 | 1 | 23 | return $self->render_page( | ||||
| 266 | template => $self->view_template, | ||||||
| 267 | context => $ctx, | ||||||
| 268 | vars => { | ||||||
| 269 | 'html' => $html, | ||||||
| 270 | }, | ||||||
| 271 | ); | ||||||
| 272 | } | ||||||
| 273 | |||||||
| 274 - 278 | =head2 history Display the history for a page. =cut | ||||||
| 279 | |||||||
| 280 | sub history { | ||||||
| 281 | 0 | 1 | my ($self, $ctx, $vars) = @_; | ||||
| 282 | |||||||
| 283 | 0 | $ctx->response->page_title($vars->{title}); | |||||
| 284 | 0 | $ctx->response->breadcrumb($vars->{breadcrumb}); | |||||
| 285 | |||||||
| 286 | 0 | $self->page_navigation($ctx->response, 'history', $vars); | |||||
| 287 | |||||||
| 288 | 0 | my $i = 0; | |||||
| 289 | return $self->render_page( | ||||||
| 290 | template => $self->history_template, | ||||||
| 291 | context => $ctx, | ||||||
| 292 | vars => { | ||||||
| 293 | 'form_action' => join('/', '/page/diff', $vars->{repository}, $vars->{page}), | ||||||
| 294 | 'revisions' => $vars->{revisions}, | ||||||
| 295 | }, | ||||||
| 296 | 0 | ); | |||||
| 297 | } | ||||||
| 298 | |||||||
| 299 - 303 | =head2 diff Display a diff for a file. =cut | ||||||
| 304 | |||||||
| 305 | sub diff { | ||||||
| 306 | 0 | 1 | my ($self, $ctx, $vars) = @_; | ||||
| 307 | 0 | my $file = $vars->{file}; | |||||
| 308 | |||||||
| 309 | 0 | $ctx->response->page_title($vars->{title}); | |||||
| 310 | 0 | $ctx->response->breadcrumb($vars->{breadcrumb}); | |||||
| 311 | |||||||
| 312 | 0 | $self->page_navigation($ctx->response, 'diff', $vars); | |||||
| 313 | |||||||
| 314 | 0 | my $html = $file->fetch_formatted($ctx); | |||||
| 315 | |||||||
| 316 | 0 | return $self->render_page( | |||||
| 317 | template => $self->diff_template, | ||||||
| 318 | context => $ctx, | ||||||
| 319 | vars => { | ||||||
| 320 | html => $html, | ||||||
| 321 | }, | ||||||
| 322 | ); | ||||||
| 323 | } | ||||||
| 324 | |||||||
| 325 - 329 | =head2 edit Renders the editor for a page. =cut | ||||||
| 330 | |||||||
| 331 | sub edit { | ||||||
| 332 | 0 | 1 | my ($self, $ctx, $vars) = @_; | ||||
| 333 | 0 | my $file = $vars->{file}; | |||||
| 334 | |||||||
| 335 | 0 | $ctx->response->page_title($vars->{title}); | |||||
| 336 | 0 | $ctx->response->breadcrumb($vars->{breadcrumb}); | |||||
| 337 | |||||||
| 338 | 0 | my $html = $file->fetch_formatted($ctx, $vars->{position}); | |||||
| 339 | |||||||
| 340 | 0 | $self->page_navigation($ctx->response, 'edit', $vars); | |||||
| 341 | |||||||
| 342 | return $self->render_page( | ||||||
| 343 | template => $self->edit_template, | ||||||
| 344 | context => $ctx, | ||||||
| 345 | vars => { | ||||||
| 346 | page => $vars->{page}, | ||||||
| 347 | source => scalar $vars->{file}->fetch // '', | ||||||
| 348 | position => $vars->{position}, | ||||||
| 349 | html => $html, | ||||||
| 350 | 0 | attachments => $self->attachments($ctx, $vars->{attachments}), | |||||
| 351 | }, | ||||||
| 352 | ); | ||||||
| 353 | } | ||||||
| 354 | |||||||
| 355 - 359 | =head2 rename Renders the rename form for a page. =cut | ||||||
| 360 | |||||||
| 361 | sub rename { | ||||||
| 362 | 0 | 1 | my ($self, $ctx, $vars) = @_; | ||||
| 363 | 0 | my $file = $vars->{file}; | |||||
| 364 | |||||||
| 365 | 0 | $ctx->response->page_title($vars->{title}); | |||||
| 366 | 0 | $ctx->response->breadcrumb($vars->{breadcrumb}); | |||||
| 367 | |||||||
| 368 | $self->page_navigation($ctx->response, 'rename', $vars) | ||||||
| 369 | 0 | unless $ctx->request->path_parameters->{file}; | |||||
| 370 | |||||||
| 371 | return $self->render_page( | ||||||
| 372 | template => $self->rename_template, | ||||||
| 373 | context => $ctx, | ||||||
| 374 | vars => { | ||||||
| 375 | page => $vars->{page}, | ||||||
| 376 | }, | ||||||
| 377 | 0 | ); | |||||
| 378 | } | ||||||
| 379 | |||||||
| 380 - 384 | =head2 remove Renders the remove confirmation page. =cut | ||||||
| 385 | |||||||
| 386 | sub remove { | ||||||
| 387 | 0 | 1 | my ($self, $ctx, $vars) = @_; | ||||
| 388 | 0 | my $file = $vars->{file}; | |||||
| 389 | |||||||
| 390 | 0 | $ctx->response->page_title($vars->{title}); | |||||
| 391 | 0 | $ctx->response->breadcrumb($vars->{breadcrumb}); | |||||
| 392 | |||||||
| 393 | $self->page_navigation($ctx->response, 'remove', $vars) | ||||||
| 394 | 0 | unless $ctx->request->path_parameters->{file}; | |||||
| 395 | |||||||
| 396 | return $self->render_page( | ||||||
| 397 | template => $self->remove_template, | ||||||
| 398 | context => $ctx, | ||||||
| 399 | vars => { | ||||||
| 400 | page => $vars->{page}, | ||||||
| 401 | return_link => $vars->{return_link}, | ||||||
| 402 | }, | ||||||
| 403 | 0 | ); | |||||
| 404 | } | ||||||
| 405 | |||||||
| 406 - 410 | =head2 attachments Renders the attachments table. =cut | ||||||
| 411 | |||||||
| 412 | sub attachments { | ||||||
| 413 | 0 | 1 | my ($self, $ctx, $attachments) = @_; | ||||
| 414 | |||||||
| 415 | return $self->render( | ||||||
| 416 | template => $self->attachments_template, | ||||||
| 417 | context => $ctx, | ||||||
| 418 | vars => { | ||||||
| 419 | files => @$attachments ? [ map { | ||||||
| 420 | 0 0 | my @links = $self->attachment_links($ctx, $_); | |||||
| 421 | |||||||
| 422 | 0 0 | my %primary_link = %{ $links[0] }; | |||||
| 423 | 0 | $primary_link{label} = $_->file_name; | |||||
| 424 | |||||||
| 425 | 0 | my $file_name = $self->render_links( | |||||
| 426 | context => $ctx, | ||||||
| 427 | links => [ \%primary_link ], | ||||||
| 428 | ); | ||||||
| 429 | |||||||
| 430 | { | ||||||
| 431 | 0 | file_id => $_->file_id, | |||||
| 432 | file_name => $file_name, | ||||||
| 433 | file_size => $_->formatted_file_size, | ||||||
| 434 | action => $self->render_attachment_links($ctx, \@links), | ||||||
| 435 | } | ||||||
| 436 | } @$attachments ] : undef, | ||||||
| 437 | }, | ||||||
| 438 | ); | ||||||
| 439 | } | ||||||
| 440 | |||||||
| 441 - 443 | =head2 attachment_links =cut | ||||||
| 444 | |||||||
| 445 | sub attachment_links { | ||||||
| 446 | 0 | 1 | my ($self, $ctx, $attachment) = @_; | ||||
| 447 | |||||||
| 448 | 0 | my @links; | |||||
| 449 | |||||||
| 450 | 0 | if ($attachment->has_format) { | |||||
| 451 | 0 | push @links, { | |||||
| 452 | label => 'View', | ||||||
| 453 | href => join('/', 'page', 'view', | ||||||
| 454 | $attachment->repository_name, | ||||||
| 455 | $attachment->full_path), | ||||||
| 456 | }; | ||||||
| 457 | } | ||||||
| 458 | else { | ||||||
| 459 | 0 | push @links, { | |||||
| 460 | label => 'View', | ||||||
| 461 | href => join('/', 'attachment', 'view', | ||||||
| 462 | $attachment->repository_name, | ||||||
| 463 | $attachment->full_path), | ||||||
| 464 | } if $attachment->media_type ne 'application/octet'; | ||||||
| 465 | |||||||
| 466 | 0 | push @links, { | |||||
| 467 | label => 'Download', | ||||||
| 468 | href => join('/', 'attachment', 'download', | ||||||
| 469 | $attachment->repository_name, | ||||||
| 470 | $attachment->full_path), | ||||||
| 471 | }; | ||||||
| 472 | } | ||||||
| 473 | |||||||
| 474 | 0 | push @links, { | |||||
| 475 | label => 'Rename', | ||||||
| 476 | href => join('/', 'attachment', 'rename', | ||||||
| 477 | $attachment->repository_name, | ||||||
| 478 | $attachment->full_path), | ||||||
| 479 | }; | ||||||
| 480 | |||||||
| 481 | 0 | push @links, { | |||||
| 482 | label => 'Remove', | ||||||
| 483 | href => join('/', 'attachment', 'remove', | ||||||
| 484 | $attachment->repository_name, | ||||||
| 485 | $attachment->full_path), | ||||||
| 486 | }; | ||||||
| 487 | |||||||
| 488 | 0 | return @links; | |||||
| 489 | } | ||||||
| 490 | |||||||
| 491 - 495 | =head2 render_attachment_links Renders the links listed in the action column of the attachments table. =cut | ||||||
| 496 | |||||||
| 497 | sub render_attachment_links { | ||||||
| 498 | 0 | 1 | my ($self, $ctx, $links) = @_; | ||||
| 499 | 0 | return $self->render_links(context => $ctx, links => $links); | |||||
| 500 | } | ||||||
| 501 | |||||||
| 502 - 506 | =head2 preview Renders a preview of an edit in progress. =cut | ||||||
| 507 | |||||||
| 508 | sub preview { | ||||||
| 509 | 0 | 1 | my ($self, $ctx, $vars) = @_; | ||||
| 510 | 0 | my $file = $vars->{file}; | |||||
| 511 | |||||||
| 512 | 0 | my $html = $file->fetch_formatted($ctx); | |||||
| 513 | |||||||
| 514 | 0 | return $html; | |||||
| 515 | } | ||||||
| 516 | |||||||
| 517 | 1; | ||||||