File Coverage

blib/lib/Mojito/Template.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1 3     3   27004 use strictures 1;
  3         891  
  3         82  
2             package Mojito::Template;
3             {
4             $Mojito::Template::VERSION = '0.24';
5             }
6 3     3   2207 use Moo;
  3         21414  
  3         18  
7 3     3   2929 use 5.010;
  3         10  
  3         442  
8 3     3   1145 use MooX::Types::MooseLike::Base qw(:all);
  3         14932  
  3         1494  
9 3     3   1929 use Mojito::Model::Link;
  0            
  0            
10             use Mojito::Collection::CRUD;
11             use Mojito::Page::Publish;
12             use DateTime;
13             use Syntax::Keyword::Junction qw/ any /;
14             use HTML::Entities;
15             use Data::Dumper::Concise;
16              
17             with('Mojito::Template::Role::Javascript');
18             with('Mojito::Template::Role::CSS');
19              
20             =head1 Name
21              
22             Mojito::Template - the main HTML class
23              
24             =cut
25              
26             has 'template' => (
27             is => 'rw',
28             lazy => 1,
29             builder => '_build_template',
30             );
31              
32             has 'page_id' => (
33             is => 'rw',
34             );
35              
36             # Allow Template to receive a db attribute upon construction
37             # currently it's passed to the $mojito->tmpl handler
38             has 'db' => ( is => 'ro', lazy => 1);
39              
40             has 'base_url' => ( is => 'rw', );
41              
42             has linker => (
43             is => 'ro',
44             isa => sub { die "Need a Link Model object. Have ref($_[0]) instead." unless $_[0]->isa('Mojito::Model::Link') },
45             lazy => 1,
46             handles => {
47             recent_links_view => 'get_recent_links',
48             collection_page_view => 'view_collection_page',
49             select_collection_pages_view => 'view_selectable_page_list',
50             sort_collection_pages_view => 'view_sortable_page_list',
51             collections_index_view => 'view_collections_index',
52             get_docs_for_month => 'get_docs_for_month',
53             get_collection_list => 'get_collections_index_link_data',
54             },
55             writer => '_set_linker',
56             );
57              
58             has collector => (
59             is => 'ro',
60             isa => sub { die "Need a Collection::CRUD object" unless $_[0]->isa('Mojito::Collection::CRUD') },
61             'default' => sub { my $self = shift; return Mojito::Collection::CRUD->new(config => $self->config, db => $self->db) },
62             lazy => 1,
63             );
64              
65             has 'home_page' => (
66             is => 'rw',
67             lazy => 1,
68             builder => '_build_home_page',
69             );
70              
71             has 'collect_page_form' => (
72             is => 'ro',
73             lazy => 1,
74             builder => '_build_collect_page_form',
75             );
76              
77             has 'collections_index' => (
78             is => 'ro',
79             lazy => 1,
80             builder => '_build_collections_index',
81             );
82              
83             has 'recent_links' => (
84             is => 'ro',
85             lazy => 1,
86             builder => '_build_recent_links',
87             );
88              
89             has 'wiki_language_selection' => (
90             is => 'ro',
91             lazy => 1,
92             builder => '_build_wiki_language_selection',
93             );
94             has js_css_html => (
95             is => 'ro',
96             isa => Value,
97             lazy => 1,
98             default => sub { my $self = shift; join "\n", @{$self->javascript_html}, @{$self->css_html} }
99             );
100              
101             has page_wrap_end => (
102             is => 'ro',
103             lazy => 1,
104             builder => '_build_page_wrap_end',
105             );
106              
107             sub _build_template {
108             my $self = shift;
109              
110             my $base_url = $self->base_url;
111             my $mojito_version = $self->config->{VERSION};
112             my $wiki_language_selection = $self->wiki_language_selection;
113             my $js_css = $self->js_css_html;
114             my $page_id = $self->page_id||'';
115             my $publisher = Mojito::Page::Publish->new(config => $self->config);
116             my $publish_form = '';
117             $publish_form = $publisher->publish_form||'' if $page_id;
118             my @collections_for_page = $self->collector->editer->collections_for_page($page_id);
119             my $collection_list = $self->get_collection_list||[];
120             my $collection_size = scalar @{$collection_list} + 1;
121             my $collection_options = "<select id='collection_select' name='collection_select'
122             multiple='multiple' size='${collection_size}' form='editForm' style='font-size: 1.00em; display:none;' >\n";
123             $collection_options .= "<option value='0'>- No Collection -</option>\n";
124             foreach my $collection (@{$collection_list}) {
125             $collection_options .= "<option value='$collection->{id}'";
126             if ($collection->{id} eq any(@collections_for_page)) {
127             $collection_options .= " selected='selected' ";
128             }
129             $collection_options .= ">$collection->{title}</option>\n";
130             }
131             $collection_options .= "</select>\n";
132             my $edit_page = <<"END_HTML";
133             <!doctype html>
134             <html>
135             <head>
136             <meta charset=utf-8>
137             <meta http-equiv="powered by" content="Mojito $mojito_version" />
138             <title>Mojito page</title>
139             $js_css
140             <script></script>
141             <style></style>
142             </head>
143             <body class="html_body">
144             <header>
145             <nav id="edit_link" class="edit_link"></nav>
146             <nav id="new_link" class="new_link"> <a href=${base_url}page>New</a></nav>
147             </header>
148             <section id="message_area"></section>
149             <section id="collection_nav_area"></section>
150             <article id="body_wrapper">
151             <input type="hidden" id ="page_id" name="page_id" value="$page_id" />
152             <section id="edit_area">
153             <span style="font-size: 0.82em;"><label id="feeds_label">feeds+</label>
154             <input id="feeds" name="feeds" form="editForm" value="" size="12" style="font-size: 1.00em; display:none;" />
155             </span>
156             <span style="font-size: 0.82em;">
157             <label id="collection_label">collections+</label>
158             $collection_options
159             </span>
160             <form id="editForm" action="" accept-charset="UTF-8" method="post">
161             <div id="wiki_language">
162             $wiki_language_selection
163             </div>
164             <input id="mongo_id" name="mongo_id" type="hidden" form="editForm" value="" />
165             <input id="wiki_language" name="wiki_language" type="hidden" form="editForm" value="" />
166             <input id="page_title" name="page_title" type="hidden" form="editForm" value="" />
167             <textarea id="content" name="content" rows="32" required="required"/></textarea>
168             <input id="commit_message" name="commit_message" value="commit message" onclick="this.value == 'commit message' ? this.value = '' : true"/>
169             <input id="submit_save" name="submit" type="submit" value="Save" style="font-size: 66.7%;" />
170             <input id="submit_view" name="submit" type="submit" value="Done" style="font-size: 66.7%;" />
171             <label style="font-size: 0.667em;" for="public" title="Check if you want a publicly viewable page" >
172             <input id="public" name="public" value="1" type="checkbox" style="font-size: 0.667em;" />
173             public
174             </label>
175             </form>
176             </section>
177             <section id="view_area" class="view_area_edit_mode"></section>
178             <nav id="side">
179             <section id="search_area">
180             <form action=${base_url}search method=POST>
181             <input type="text" name="word" value="Search" onclick="this.value == 'Search' ? this.value = '' : true"/>
182             </form>
183             </section><br />
184             <section id="publish_area">$publish_form</section>
185             <section id="collections_area"></section>
186             <section id="calendar_area"><a href="${base_url}calendar">Calendar</a></section>
187             <section id="recent_area"></section>
188             </nav>
189             </article>
190             <footer id="footer_area">
191             <nav id="edit_link" class="edit_link"></nav>
192             <nav id="new_link" class="new_link"> <a href=${base_url}page>New</a></nav>
193             </footer>
194             </body>
195             </html>
196             END_HTML
197             $edit_page =~ s/<script><\/script>/<script>mojito.base_url = '${base_url}';<\/script>/s;
198             return $edit_page;
199             }
200              
201             sub _build_wiki_language_selection {
202             my ($self) = @_;
203            
204             my $selection;
205             my $default_wiki_language =$self->config->{default_wiki_language}||'markdown';
206             foreach my $language (qw/textile markdown creole html pod/) {
207             if ($language =~ m/$default_wiki_language/) {
208             $selection .= qq{<input type="radio" id="$language" name="wiki_language" value="$language" checked="checked" /><label for="$language">$language</label>};
209             }
210             else {
211             $selection .= qq{<input type="radio" id="$language" name="wiki_language" value="$language" /><label for="$language">$language</label>};
212             }
213             }
214             return $selection;
215             }
216              
217              
218             sub page_wrap_start {
219             my ($self, $title) = @_;
220             my $mojito_version = $self->config->{VERSION};
221             my $js_css = $self->js_css_html;
222             my $page_start = <<"START_HTML";
223             <!doctype html>
224             <html>
225             <head>
226             <meta charset=utf-8>
227             <meta name="application-name" content="Mojito $mojito_version" />
228             <title>$title</title>
229             $js_css
230             <script></script>
231             <style></style>
232             </head>
233             <body class="html_body">
234             <section id="message_area"></section>
235             <article id="body_wrapper">
236             START_HTML
237              
238             return $page_start;
239             }
240              
241             sub page_wrap_start_vanilla {
242             my ($self, $title) = @_;
243             my $mojito_version = $self->config->{VERSION};
244             my $static_url = $self->config->{static_url};
245             my $page_start = <<"START_HTML";
246             <!doctype html>
247             <html>
248             <head>
249             <meta charset=utf-8>
250             <meta name="application-name" content="Mojito $mojito_version" />
251             <title>$title</title>
252             <link href=${static_url}css/mojito.css type=text/css rel=stylesheet />
253             </head>
254             <body class="vanilla_body">
255             <article id="body_wrapper_vanilla">
256             START_HTML
257              
258             return $page_start;
259             }
260              
261             sub _build_page_wrap_end {
262             my $self = shift;
263              
264             my $page_end =<<'END_HTML';
265              
266             </article>
267             </body>
268             </html>
269             END_HTML
270              
271             return $page_end;
272             }
273              
274             =head2 wrap_page
275              
276             Wrap a page body with start and end HTML.
277              
278             =cut
279              
280             sub wrap_page {
281             my ($self, $page_body, $title) = @_;
282             $title //= 'Mojito page';
283             $page_body //= 'Empty page';
284             return ($self->page_wrap_start($title) . $page_body . $self->page_wrap_end);
285             }
286              
287             =head2 wrap_page_vanilla
288              
289             Wrap a page body with start and end HTML, and NO Javascript or CSS links
290              
291             =cut
292              
293             sub wrap_page_vanilla {
294             my ($self, $page_body, $title) = @_;
295             $title //= 'Mojito page';
296             $page_body //= 'Empty page';
297             return ($self->page_wrap_start_vanilla($title) . $page_body . $self->page_wrap_end);
298             }
299              
300             sub _build_collect_page_form {
301             my $self = shift;
302             return $self->wrap_page($self->select_collection_pages_view);
303             }
304              
305             sub _build_collections_index {
306             my $self = shift;
307             return $self->wrap_page($self->collections_index_view);
308             }
309              
310             sub _build_recent_links {
311             my $self = shift;
312             return $self->wrap_page($self->recent_links_view({want_delete_link => 1}));
313             }
314              
315             =head2 sort_collection_form
316              
317             A form to sort a collection of pages.
318              
319             =cut
320              
321             sub sort_collection_form {
322             my ($self, $params) = (shift, shift);
323             return $self->wrap_page($self->sort_collection_pages_view({ collection_id => $params->{id} }));
324             }
325              
326             =head2 collection_page
327              
328             Given a collection id, show a list of belonging pages.
329              
330             =cut
331              
332             sub collection_page {
333             my ($self, $params) = (shift, shift);
334              
335             my $base_url = $self->base_url;
336             $base_url .= 'public/' if $params->{public};
337             my $collector = Mojito::Collection::CRUD->new(config => $self->config, db => $self->db);
338             my $collection = $collector->read( $params->{id} );
339             return $self->wrap_page(
340             $self->collection_page_view( { collection_id => $params->{id}, is_public => $params->{public} }),
341             $collection->{collection_name},
342             );
343             }
344              
345             sub _build_home_page {
346             my $self = shift;
347              
348             my $base_url = $self->base_url;
349             my $mojito_version = $self->config->{VERSION};
350             my $js_css = $self->js_css_html;
351             my $home_page = <<"END_HTML";
352             <!doctype html>
353             <html>
354             <head>
355             <meta charset=utf-8>
356             <meta name="application-name" content="Mojito $mojito_version" />
357             <title>Mojito page</title>
358             $js_css
359             <script></script>
360             <style></style>
361             </head>
362             <body class=html_body>
363             <header>
364             <nav id="new_link" class="new_link"> <a href=${base_url}page>New</a></nav>
365             </header>
366             <article id="body_wrapper">
367             <nav id="side">
368             <section id="search_area"><form action=${base_url}search method=POST><input type="text" name="word" value="Search" onclick="this.value == 'Search' ? this.value = '' : true" /></form></section><br />
369             <section id="recent_area"></section>
370             </nav>
371             </article>
372             <footer id="footer_area">
373             <nav id="new_link" class="new_link"> <a href=${base_url}page>New</a></nav>
374             </footer>
375             </body>
376             </html>
377             END_HTML
378              
379             return $home_page;
380             }
381              
382             =head1 Methods
383              
384             =head2 fillin_edit_page
385              
386             Get the contents of the edit page proper given the starting template and some data.
387              
388             =cut
389              
390             sub fillin_edit_page {
391             my ( $self, $page, $page_view, $mongo_id ) = @_;
392              
393             # Encode source content with HTML entities since it will be displayed in a textarea
394             my $page_source = encode_entities($page->{page_source});
395             my $wiki_language = $page->{default_format};
396             my $page_title = $page->{title}||'no title';
397             my @feeds = ();
398             if (ref($page->{feeds}) eq 'ARRAY') {
399             @feeds = @{$page->{feeds}};
400             }
401             my $feeds = join ':', @feeds;
402             my $public = $page->{public};
403              
404             # Set page_id for the template
405             $self->page_id($mongo_id);
406             my $output = $self->template;
407             my $base_url = $self->base_url;
408             $output =~
409             s/<script><\/script>/<script>mojito.preview_url = '${base_url}preview';<\/script>/s;
410             # Set some form values
411             $output =~ s/(<input id="mongo_id".*?value=)""/$1"${mongo_id}"/si;
412             # $output =~ s/(<input .*? id ="page_id" .*? value=)""/$1"${mongo_id}"/si;
413             $output =~ s/(<input id="wiki_language".*?value=)""/$1"${wiki_language}"/si;
414             $output =~ s/(<input id="page_title".*?value=)""/$1"${page_title}"/si;
415             $output =~ s/(<input id="feeds".*?value=)""/$1"${feeds}"/si;
416             if ($public) {
417             $output =~ s/(<input id="public")/$1 checked="checked"/si;
418             }
419             $output =~
420             s/(<textarea\s+id="content"[^>]*>)<\/textarea>/$1${page_source}<\/textarea>/si;
421             $output =~
422             s/(<section\s+id="view_area"[^>]*>)<\/section>/$1${page_view}<\/section>/si;
423              
424             # An Experiment in Design: take out the save button, because we have autosave every few seconds
425             $output =~ s/<input id="submit_save".*?>//sig;
426              
427             # Remove side, recent area and wiki_language (for create only)
428             $output =~ s/<nav id="side">.*?<\/nav>//si;
429             # $output =~ s/<section id="recent_area".*?><\/section>//si;
430             $output =~ s/<div id="wiki_language".*?>.*?<\/div>//si;
431             $output =~ s|(<section id="edit_area">)|$1\n<button id="toggle_view">Toggle View</button>|si;
432              
433             # Remove edit and new links
434             $output =~ s/<nav id="edit_link".*?><\/nav>//sig;
435             $output =~ s/<nav id="new_link".*?>.*?<\/nav>//sig;
436              
437             # body with no style
438             $output =~ s/<body.*?>/<body>/si;
439            
440             # Give the page a title
441             if ($page_title) {
442             $output =~ s/<title>.*?<\/title>/<title>${page_title}<\/title>/si;
443             }
444             return $output;
445             }
446              
447             =head2 fillin_create_page
448              
449             Get the contents of the create page proper given the starting template and some data.
450              
451             =cut
452              
453             sub fillin_create_page {
454             my ($self) = @_;
455              
456             my $output = $self->template;
457             my $base_url = $self->base_url;
458              
459             # Set mojito preiview_url variable
460             $output =~
461             s/<script><\/script>/<script>mojito.preview_url = '${base_url}preview'<\/script>/;
462              
463             # Take out view button and change save to create.
464             $output =~ s/<input id="submit_view".*?>//;
465             $output =~ s/<input id="submit_save"(.*?>)/<input id="submit_create"$1/;
466             $output =~ s/(id="submit_create".*?value=)"Save"/$1"Create"/i;
467              
468             # Remove side nav area
469             $output =~ s/<nav id="side">.*?<\/nav>//si;
470              
471             # Remove wiki_language hidden input (for edit)
472             $output =~ s/<input id="wiki_language".*?\/>//sig;
473              
474             # Remove edit and new links
475             $output =~ s/<nav id="edit_link".*?><\/nav>//sig;
476             $output =~ s/<nav id="new_link".*?>.*?<\/nav>//sig;
477              
478             # body with no style
479             $output =~ s/<body.*?>/<body>/si;
480              
481             return $output;
482             }
483              
484             sub calendar_for_month {
485             my $self = shift;
486             my %args = ref($_[0]) ? %{ $_[0] } : @_;
487             my ($month, $year, $title) = @args{qw/month year title/};
488             my $base_url = $self->base_url;
489             #$title ||= 'Monthly Notes';
490             my %monthly_docs = $self->get_docs_for_month($month, $year);
491             my ($m_j, $m_k, $y_j, $y_k) = next_and_previous_month_year($month, $year);
492             my $calendar = "<section id='calendar_month'>\n";
493             $calendar .= "<h1>${title}</h1>\n" if $title;
494             $calendar .= "<table class='monthly_calendar'>";
495              
496             # Got to turn off highlight of current day else we'd have to strip the \b's around it.
497             # TODO: Allow variable path to cal
498             open(my $CAL, '-|', "/usr/bin/cal -h $month $year")
499             || die "Can't open /usr/bin/cal\n";
500             while (<$CAL>) {
501              
502             last if /^\s+$/; ## ignore cal's terminating blank line
503             s/^\s*(.*?)\s*$/$1/; ## trim whitespace
504              
505             if ($. == 1) {
506             $calendar .=
507             "<tr><th class='previous' colspan='3'><a href='${base_url}calendar/year/${y_j}/month/${m_j}'>Previous Month</a></th>
508             <th class='calendar_month' colspan='1'>$_</th>
509             <th class='next' colspan='3'><a href='${base_url}calendar/year/${y_k}/month/${m_k}'>Next Month</a></th></tr>\n";
510             next;
511             }
512              
513             my $tag;
514             if ($. == 2) {
515             $tag = "th class='weekdays'";
516             }
517             else { $tag = "td" }
518              
519             ## make a row, padding first week of the month as necessary
520             my @data = ();
521             @data = split(/\s+/, $_);
522             $calendar .= "<tr>\n";
523             if ($. == 3) {
524             for (1 .. 6 - $#data) { $calendar .= "<td> </td>\n"; }
525             }
526             for my $i (0 .. $#data) {
527             $calendar .= "<$tag>$data[$i]";
528             # Just process the digits found.
529             next if ($data[$i] !~ m/^\d+$/);
530             foreach my $ref (@{ $monthly_docs{ $data[$i] } }) {
531             # Make srue we have some type of title
532             $ref->{title} ||= 'No title found';
533             $calendar .=
534             "<div class='calendar_note'><a href='${base_url}page/$ref->{id}'>* $ref->{title}</a></div>";
535             }
536             $calendar .= "</td>\n";
537             }
538             $calendar .= "</tr>\n";
539             }
540             close($CAL);
541             $calendar .= "</table>\n</section>\n";
542              
543             return $calendar;
544             }
545              
546             sub calendar_month_page {
547             my $self = shift;
548             my %args = ref($_[0]) ? %{ $_[0] } : @_;
549             @args{qw/month year/} = get_current_month_year()
550             if not($args{month} && $args{year});
551             my ($month, $year, $title) = @args{qw/month year title/};
552             $title ||= "Notes Calendar for $month/$year";
553             return $self->wrap_page_vanilla($self->calendar_for_month(\%args), $title);
554             }
555              
556             sub get_current_month_year {
557             my $now = DateTime->now;
558             return ($now->month, $now->year);
559             }
560              
561             sub next_and_previous_month_year {
562             my ($month, $year) = @_;
563             ## Set up for previous and next month link
564             my ($m_j, $m_k, $y_j, $y_k);
565             if ($month == 12) {
566             $m_j = 11;
567             $m_k = 1;
568             $y_j = $year;
569             $y_k = $year + 1;
570             }
571             elsif ($month == 1) {
572             $m_j = 12;
573             $m_k = 2;
574             $y_j = $year - 1;
575             $y_k = $year;
576             }
577             else {
578             $m_j = $month - 1;
579             $m_k = $month + 1;
580             $y_j = $year;
581             $y_k = $year;
582             }
583             return ($m_j, $m_k, $y_j, $y_k);
584             }
585              
586             sub BUILD {
587             my $self = shift;
588             my $constructor_args_href = shift;
589              
590             # pass the options into the delegatees
591             $self->_set_linker(Mojito::Model::Link->new($constructor_args_href));
592             }
593             1