bin/upgrading/s2layers/zesty/layout.s2
author fu
Fri Jan 27 17:59:59 2012 +0800
changeset 4240 5bab31c9f015
parent 4224 701719de9628
child 4302 4fc7eee083ff
permissions -rw-r--r--
http://bugs.dwscoalition.org/show_bug.cgi?id=4245

Call keywords in list context, so that we get a list instead of a comma-separated string. Add a bit of padding because we no longer have a physical comma.

Patch by fu.
     1 # -*-s2-*-
     2 layerinfo "type" = "layout";
     3 layerinfo "name" = "Zesty";
     4 layerinfo "lang" = "en";
     5 layerinfo "author" = "Sam Angove";
     6 layerinfo "author_email" = "net.rephrase@sam";
     7 layerinfo "is_public" = 1;
     8 layerinfo "source_viewable" = 1;
     9 layerinfo "redist_uniq" = "zesty/layout";
    10 
    11 ###################################################
    12 #                                                 #
    13 #                   [S2] Zesty                    #
    14 #                                                 #
    15 #                Table of Contents                #
    16 #                =================                #
    17 #                                                 #
    18 # ~i.   Changelog                                 #
    19 # ~ii.  License                                   #
    20 # ~iii. Notes                                     #
    21 #                                                 #
    22 # Customization/i18n Properties                   #
    23 # -----------------------------                   #
    24 # ~1.  Properties                                 #
    25 #                                                 #
    26 # Utility functions                               #
    27 # -----------------                               #
    28 # ~2.  Utility functions                          #
    29 #                                                 #
    30 # CSS                                             #
    31 # ---                                             #
    32 # ~3.  Stylesheet                                 #
    33 #                                                 #
    34 # Shared methods                                  #
    35 # --------------                                  #
    36 # Methods used on multiple views for getting or   #
    37 # printing information about entries.             #
    38 #                                                 #
    39 # ~4. EntryLite                                   #
    40 # ~5. CommentInfo                                 #
    41 # ~6. Entry                                       #
    42 #                                                 #
    43 # Global view                                     #
    44 # -----------                                     #
    45 # Templates used on all views as well as methods  #
    46 # overridden by specific views.                   #
    47 #                                                 #
    48 # ~7. Page                                        #
    49 #                                                 #
    50 # Regular views                                   #
    51 # -------------                                   #
    52 # These four views have substantially similar     #
    53 # logic.                                          #
    54 #                                                 #
    55 # ~8.  RecentPage                                 #
    56 # ~9.  FriendsPage                                #
    57 # ~10. DayPage                                    #
    58 # ~11. MonthPage                                  #
    59 #                                                 #
    60 # Entry views                                     #
    61 # -----------                                     #
    62 # These views require significant extra logic.    #
    63 # They are not available to free users.           #
    64 #                                                 #
    65 # ~12. EntryPage                                  #
    66 # ~13. ReplyPage                                  #
    67 #                                                 #
    68 # Miscellaneous views                             #
    69 # -------------------                             #
    70 # These views cannot print entries.               #
    71 #                                                 #
    72 # ~14. YearPage                                   #
    73 # ~15. MessagePage                                #
    74 # ~16. TagsPage                                   #
    75 #                                                 #
    76 ###################################################
    77 
    78 ###################################################
    79 #      #                                          #
    80 # ~i.  # ~Changelog                               #
    81 #      #                                          #
    82 ###################################################
    83 
    84 # 2006-07-18 -- I can't remember, but I'm releasing it now. ;)
    85 # 2006-07-10 -- English stripping
    86 # 2006-07-09 -- general cleanup, fix footer
    87 # 2006-07-03 -- initial build
    88 #
    89 
    90 ###################################################
    91 #      #                                          #
    92 # ~ii. # ~License                                 #
    93 #      #                                          #
    94 ###################################################
    95 
    96 # "Zesty" LiveJournal S2 style
    97 #
    98 # Copyright (c) 2006 Sam Angove
    99 #
   100 # This program is free software; you can redistribute it and/or modify
   101 # it under the terms of the GNU General Public License as published by
   102 # the Free Software Foundation; either version 2 of the License, or
   103 # (at your option) any later version.
   104 #
   105 # This program is distributed in the hope that it will be useful,
   106 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   107 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   108 # GNU General Public License for more details.
   109 #
   110 # You should have received a copy of the GNU General Public License along
   111 # with this program; if not, write to the Free Software Foundation, Inc.,
   112 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
   113 
   114 ###################################################
   115 #      #                                          #
   116 # ~ii. # ~Notes                                   #
   117 #      #                                          #
   118 ###################################################
   119 
   120 # - The CSS is very messy. Haven't had time to clean it up. Sorry!
   121 #
   122 # - Most customization properties are for i18n purposes. Note that many of
   123 #   them use the `lay_string_placeholders()` function; it's not wholly
   124 #   satisfactory but it's more flexible than the core.
   125 #
   126 # - There are no props for colours, borders etc. It's a huge headache and
   127 #   I can't be bothered.  Since this isn't a core style, no-one but a paid
   128 #   user can use it anyway, and they'll be able to edit the CSS directly.
   129 #
   130 # - I have deliberately ignored OOP and used
   131 #      `View::lay_print_obj(Obj o)` over `var Obj o; $o->print()`
   132 #   wherever possible. It's usually futile to use the latter because
   133 #   the method needs to be overridable in different views -- different
   134 #   requirements for entry printing on MonthPage and EntryPage, for example.
   135 #
   136 #   Global functions have been avoided for the same reason.
   137 
   138 
   139 ###################################################
   140 #      #                                          #
   141 # !1.  # !Properties                              #
   142 #      #                                          #
   143 ###################################################
   144 
   145 propgroup presentation {
   146     property use num_items_recent;
   147     property use num_items_reading;
   148     property use use_journalstyle_entry_page;
   149     property use tags_page_type;
   150     property use use_shared_pic;
   151     property use userlite_interaction_links;
   152     property use entry_management_links;
   153     property use comment_management_links;
   154 }
   155 
   156 set num_items_recent = 10;
   157 set num_items_reading = 20;
   158 
   159 propgroup Text {
   160     property use text_day_next;
   161     property use text_day_prev;
   162     property use text_skiplinks_back;
   163     property use text_skiplinks_forward;
   164 
   165     property use text_permalink;
   166     property use text_stickyentry_subject;
   167     property use text_post_comment;
   168     property use text_post_comment_friends;
   169 
   170         set text_permalink = "permalink";
   171         set text_stickyentry_subject = "Sticky: ";
   172         set text_post_comment = "reply";
   173         set text_post_comment_friends = "reply";
   174 
   175     property use text_nosubject;
   176     property use text_poster_anonymous;
   177 
   178         set text_nosubject = "(no subject)";
   179         set text_poster_anonymous = "(anonymous)";
   180 
   181     property use text_meta_mood;
   182     property use text_meta_music;
   183 
   184         set text_meta_mood = "Mood";
   185         set text_meta_music = "Music";
   186 
   187     property use text_view_archive;
   188     property use text_view_recent;
   189     property use text_view_friends;
   190     property use text_view_month;
   191     property use text_view_userinfo;
   192 
   193         set text_view_archive = "Calendar";
   194         set text_view_recent = "Recent";
   195         set text_view_friends = "Read";
   196         set text_view_month = "Monthly Archive";
   197         set text_view_userinfo = "Profile";
   198 
   199 
   200     # For these properties I use a format vaguely similar to printf/sprintf.
   201     # They're passed to a method which gives them an array of strings.
   202     # you can use printf-style %s to insert the strings one at a time, or
   203     # use %1, %2 .. %9 to select them by number.
   204     #
   205     # Example: this string "posted by %1 at %2 on %3" is passed an array
   206     # containing the entry poster, the time of posting and the date of
   207     # posting.
   208     #
   209     # %1 will always refer to the poster, so a reformulation might be
   210     # something like "at %2 on %3, %1 wrote:".
   211     #
   212     # It's perfectly okay to ignore some or all of the arguments. That is,
   213     # there's nothing wrong with something like "I said on %3".
   214 
   215     property string posted_by_at_on {
   216         noui = 1;
   217         des = "Posted by [1:poster] at [2:time] on [3:date] string.";
   218     }
   219         set posted_by_at_on = "posted by %1 at %2 on %3";
   220 
   221     property string posted_by_at_on_in {
   222         noui = 1;
   223         des = "Posted by [1:poster] at [2:time] on [3:date] under [4:tags] string.";
   224     }
   225         set posted_by_at_on_in = "posted by %1 at %2 on %3 under %4";
   226 
   227     property string posted_by_at_on_from {
   228         noui = 1;
   229         des = "Posted by [1:poster] at [2:time] on [3:date] from [4:ip address] string.";
   230     }
   231         set posted_by_at_on_from = "posted by %1 at %2 on %3 from %4";
   232 
   233     property string poster_in_journal {
   234         noui = 1;
   235         des = "[1:poster] in [2:journal] string";
   236     }
   237         set poster_in_journal = "%1 in %2";
   238 
   239     property string posted_time_format {
   240         noui = 1;
   241         des = "[time] format for from 'posted by [poster] at [time] ...'";
   242     }
   243         set posted_time_format = "%%hh%%:%%min%%%%a%%m";
   244 
   245     property string posted_time_format_24 {
   246         noui = 1;
   247         des = "[time] format for from 'posted by [poster] at [time] ...'";
   248     }
   249         set posted_time_format_24 = "%%HH%%:%%min%%";
   250 
   251     property string posted_date_format {
   252         noui = 1;
   253         des = "[date] format for 'posted by [poster] at [time] on [date] ...'";
   254     }
   255         set posted_date_format = "%%dd%%/%%mm%%/%%yyyy%%";
   256 
   257 
   258     # I want to have links like this:
   259     #
   260     #   There are <a href="...">2 comments</a> on this entry.
   261     #
   262     # The HTML can't be part of the property, because S2 "helpfully" escapes
   263     # it for me. This ugly hack is used instead, wrapping $*text_a_comment_link
   264     # inside $*text_a_comment.
   265     #
   266     # ('There is <a href="%1">%2 comment</a> on this entry.')
   267     property string text_a_comment_link { }
   268     property string text_a_comment { }
   269         set text_a_comment_link = "%1 comment";
   270         set text_a_comment = "There is %1 on this entry.";
   271 
   272     property string text_some_comments_link { }
   273     property string text_some_comments { }
   274         set text_some_comments_link = "%1 comments";
   275         set text_some_comments = "There are %1 on this entry.";
   276 
   277     property string text_some_comments_over_pages { }
   278         set text_some_comments_over_pages = "There are %1 over %2 pages.";
   279 
   280     property string text_no_comments {}
   281         set text_no_comments = "There are no comments on this entry.";
   282 
   283     property string text_comments_disabled {}
   284         set text_comments_disabled = "Comments are disabled.";
   285 
   286     property string text_errorpage_title {
   287         des = "Error page title.";
   288         noui = 1;
   289     }
   290         set text_errorpage_title = "No content";
   291 
   292 
   293     # For some reason the core only provides a message for recent and day pages.
   294     #
   295     property string error_monthpage_no_entries {
   296         noui = 1;
   297         des = "Error message shown if no entries are available on a MonthPage";
   298     }
   299         set error_monthpage_no_entries = "No entries were posted on the selected month.";
   300 
   301     property string error_yearpage_no_entries {
   302         noui = 1;
   303         des = "Error message shown if no entries are available on a YearPage";
   304     }
   305         set error_yearpage_no_entries = "No entries were posted on the selected year.";
   306 
   307     property string text_html_title {
   308         des = "Title that goes in the HTML <title> element. Is given two parameters, global title and view title.";
   309     }
   310         set text_html_title = "%2 [%1]";
   311 
   312     property string collapsed_entry_comments_disabled {
   313         des = "String shown on a collapsed entry if comments are disabled.";
   314     }
   315         set collapsed_entry_comments_disabled = "(-)";
   316 
   317     property string collapsed_entry_comments_max_flag {
   318         des = "Passed into the comment-count string as %2 if an entry's maximum comments have been reached.";
   319     }
   320         set collapsed_entry_comments_max_flag = "!";
   321     property string collapsed_entry_comments_screened_flag {
   322         des = "Passed into the comment-count string as %3 if an entry has screened comments visible to the user.";
   323     }
   324         set collapsed_entry_comments_screened_flag = "*";
   325 
   326     property string collapsed_entry_comments_count {
   327         des = "Comment-count shown on a collapsed entry, not shown if there are no comments.";
   328         note = "'%1' will be replaced by the number of comments. If the maximum number of comments
   329         has been reached, %2 will contain the max flag. If there are screened comments visible to
   330         the user, %3 will contain the screened flag.";
   331     }
   332         set collapsed_entry_comments_count = "(%1%2%3)";
   333 
   334     property string linklist_default_title {
   335         des = "Linklist title.";
   336     }
   337         set linklist_default_title = "Links";
   338 
   339     property string reply_link_link_text {
   340         des = "Text for the reply link.";
   341     }
   342         set reply_link_link_text = "Reply";
   343 
   344     property string reply_link_text {
   345         des = "Non-linked reply link text. %1 is replaced with the link.";
   346     }
   347     set reply_link_text = "(%1.)";
   348 
   349     property string top_link_text {
   350         des = "Text of the link to return to the top of the page.";
   351     }
   352         set top_link_text = "Top";
   353 
   354     property use text_comment_frozen;
   355     property use text_comment_parent;
   356     property use text_comment_reply;
   357 
   358         set text_comment_frozen = "thread is frozen";
   359         set text_comment_parent = "parent";
   360         set text_comment_reply = "reply";
   361 
   362 
   363     property string text_comment_permalink {
   364         des = "Permalink to the comment.";
   365     }
   366         set text_comment_permalink = "link";
   367 
   368     property string text_comment_poster_is_suspended {
   369         des = "Show on comments posted by suspended users.";
   370         note = "Due to limitations in S2 this text will only be displayed if the comment is shown directly, i.e. as the focus of the thread.";
   371     }
   372         set text_comment_poster_is_suspended = "user is suspended";
   373 
   374     property string text_comment_parent_entry {
   375         des = "Text for linking to a comment's parent.";
   376     }
   377         set text_comment_parent_entry = "parent entry";
   378 }
   379 
   380 
   381 
   382 propgroup Miscellaneous {
   383 
   384     property string custom_favicon {
   385         des = "URL of custom favicon.";
   386         example = "http://example.com/favicon.ico";
   387         }
   388         set custom_favicon = "";
   389     
   390     property string default_view_mode {
   391         des = "Show entries expanded or collapsed by default. Currently this setting affects the reading page only.";
   392         values = "collapsed|Entries collapsed|expanded|Entries expanded";
   393         }
   394         set default_view_mode = "expanded";
   395 }
   396 
   397 
   398 #
   399 # Yes, tags are enabled.
   400 #
   401 set tags_aware = true;
   402 
   403 ###################################################
   404 #      #                                          #
   405 # !2.  # Utility functions.                       #
   406 #      #                                          #
   407 ###################################################
   408 
   409 
   410 # Converts an associative array to an argument list:
   411 #
   412 #   var string var = {"id" => "5", "page" => "b"};
   413 #   lay_array_to_args($var);
   414 #
   415 #       "?id=5&page=b"
   416 #
   417 function lay_array_to_args(string{} items) : string
   418 "Converts an associative array to an argument list, i.e. {\"id\" => \"5\", \"page\" => \"b\"} => ?id=5&page=b"
   419 {
   420     var string args;
   421     var bool q = false;
   422 
   423     foreach var string key ($items) {
   424         if ($key != "") {
   425             if (not $q) {
   426                 $args = "?";
   427                 $q = true;
   428             } else {
   429                 $args = $args + "&amp;";
   430             }
   431             $args = $args + "$key=" + $items{"$key"};
   432         }
   433     }
   434     return $args;
   435 }
   436 
   437 # pushes a string on to the end of an array, assuming that it's
   438 # indexed naturally from zero.
   439 #
   440 function lay_array_push(string[] input, string add) : string[]
   441 "Pushes a new element on to the end of an array."
   442 {
   443     $input[size $input] = $add;
   444     return $input;
   445 }
   446 
   447 # A bit like sprintf, this inserts an array of strings into a string.
   448 # Knows %s, literal %%, and numbered placeholders %1 .. %9.
   449 #
   450 function lay_string_placeholders( string format, string[] args ) : string
   451 "A bit like sprintf, this inserts an array of strings into a string.
   452 Handles %s, literal %%, and numbered placeholders %1 .. %9."
   453 {
   454     var string output = "";
   455 
   456     var bool state_found_placeholder = false;
   457     var int found_count = 0;
   458 
   459     foreach var string s ($format) {
   460         if ( $state_found_placeholder ) {
   461             if ( $s == "%" ) {
   462                 $output = $output + $s;
   463             }
   464             # string placeholder
   465             elseif ( $s == "s" ) {
   466                 $output = $output + $args[$found_count];
   467                 $found_count++;
   468                 $state_found_placeholder = false;
   469             }
   470             # numbered placeholder
   471             elseif ( $s == "1" or $s == "2" or $s == "3" or $s == "4" or $s == "5" or $s == "6" or $s == "7" or $s == "8" or $s == "9" ) {
   472                 $output = $output + $args[int($s) - 1];
   473                 $state_found_placeholder = false;
   474             }
   475         } elseif ( $s == "%" ) {
   476             $state_found_placeholder = true;
   477         } else {
   478             $output = $output + $s;
   479         }
   480     }
   481     return $output;
   482 }
   483 
   484 
   485 # Returns the current url plus arguments. Needs to be overridden
   486 # on most views where it's used.
   487 #
   488 function Page::lay_build_url(string{} items) : string {
   489     return $.base_url + lay_array_to_args($items);
   490 }
   491 
   492 # For paid user override in theme layers
   493 function lay_print_extra_boxes() : void
   494     "Paid users can override this in theme layers to easily add content in the 'extra boxes' section of the footer."
   495     { }
   496 
   497 ###################################################
   498 #      #                                          #
   499 # !3.  # Stylesheet.                              #
   500 #      #                                          #
   501 ###################################################
   502 
   503 function print_stylesheet() { """
   504 
   505 html, body {
   506     margin: 0;
   507     padding: 0;
   508     font-family: Verdana, sans-serif;
   509 }
   510 
   511 /* regular links */
   512 
   513 a {
   514     color: #2452FF;
   515 }
   516 a:visited {
   517     color: #142D8B;
   518 }
   519 a:active, a:hover {
   520     color: #178FFF;
   521 }
   522 
   523 img {
   524     border: 0px;
   525 }
   526 
   527 
   528 /* the main header */
   529 
   530 #header {
   531     background: #eee;
   532     padding: 20px 10px 20px 10px;
   533     margin: 0px;
   534 }
   535 #header h1 {
   536     font: normal 4em Georgia, serif;
   537     color: #333;
   538     margin: 0px;
   539     padding: 40px 0 0 0;
   540 }
   541 #header p {
   542     color: #999;
   543     font: 1.2em normal Verdana, sans-serif;
   544     margin-top: 5px;
   545 }
   546 
   547 /* the navigation menu */
   548 
   549 /*
   550 This had to be hacked up to work with IE and I haven't gotten around
   551 to cleaning it up yet. Sorry!
   552 */
   553 
   554 #navi {
   555     float:left;
   556     width:100%;
   557     background: #fff;
   558     line-height:normal;
   559     font: normal 0.6em Verdana, sans-serif;
   560     color: #666;
   561 }
   562 #navi ul {
   563     margin:0;
   564     padding:0px 10px 0 5px;
   565     list-style:none;
   566 }
   567 #navi li {
   568     display:block;
   569     float:left;
   570     margin: 0 0 0 0;
   571     padding:0;
   572     text-align: center;
   573     border-top: 1px solid #bbb;
   574 }
   575 
   576 #navi span {
   577     float:left;
   578     display:block;
   579     padding:4px 12px 5px 10px;
   580     margin: 0 1px 0 1px;
   581 }
   582 #navi a {
   583     display: block;
   584     color: #666;
   585     text-decoration: none;
   586     background: #ddd;
   587     float: left;
   588     padding: 0;
   589     margin-right: 1px;
   590     border-bottom: 1px solid white;
   591 }
   592 
   593 #navi a:hover,
   594 #navi a:active {
   595     background: #888;
   596     color: #fff;
   597 }
   598 
   599 #navi li#tab-current {
   600     border-top: 1px solid #eee;
   601 }
   602 
   603 #navi li#tab-current a {
   604     display: inline;
   605     float: none;
   606     background: #eee;
   607     border: 0;
   608     margin: 0;
   609 }
   610 
   611 #navi li#tab-current span {
   612     background: #eee;
   613     border-bottom: 1px solid #eee;
   614     color: #555;
   615 }
   616 
   617 /* back-and-forward navigation */
   618 
   619 .back-forward {
   620     width: 100%;
   621     float: left;
   622     clear: both;
   623 }
   624 .back-forward a, .back-forward a:visited {
   625     color: #999;
   626     text-decoration: none;
   627 }
   628 
   629 .back-forward a:active,
   630 .back-forward a:hover {
   631     color: #333;
   632 }
   633 .back-forward .back,
   634 .back-forward .forward {
   635     padding: 10px;
   636     font: normal 2em Verdana, sans-serif;
   637 
   638 }
   639 .back-forward .back {
   640     float: left;
   641     clear: left;
   642 }
   643 .back-forward .forward {
   644     float: right;
   645     clear: right;
   646 }
   647 
   648 
   649 /* global footer */
   650 
   651 #footer {
   652     color: #999;
   653     font: 0.6em normal Verdana, sans-serif;
   654     margin: 0;
   655     text-align: right;
   656     padding: 10px 5px 5px 5px;
   657     background-color: #fff;
   658     clear: both;
   659 }
   660 
   661 .top-link {
   662     float: left;
   663 }
   664 
   665 /* extra boxes below main content */
   666 
   667 .extra-box {
   668     float:left;
   669     width: 25%;
   670     margin: 20px;
   671     padding: 10px;
   672 }
   673 .extra-box > ul {
   674     list-style-type: square;
   675     margin: 0;
   676     padding: 2px 2px 2px 10px;
   677 }
   678 .extra-box .title {
   679     color: #3c0;
   680     font: normal 1.4em Verdana, sans-serif;
   681 }
   682 
   683 /* entries */
   684 
   685 #entries {
   686     clear: both;
   687     margin: 10px;
   688     margin-left: 10px;
   689     padding: 10px;
   690 }
   691 .entry .left {
   692     text-align: center;
   693     float: left;
   694     width: 120px;
   695     padding-top: 10px;
   696 }
   697 .entry .right {
   698     margin-left: 150px;
   699 }
   700 
   701 
   702 /* ENTRY */
   703 
   704 h2,
   705 h3 {
   706     color: #3c0;
   707     font: normal 2em Verdana, sans-serif;
   708     letter-spacing: -0.1em;
   709     margin: 0;
   710     padding: 0;
   711     display: inline;
   712 }
   713 
   714 .title a {
   715     color: #3c0;
   716     text-decoration: none;
   717 }
   718 .title a:visited {
   719     color: #2b0;
   720 }
   721 .title a:hover,
   722 .title a:active {
   723     color: #4d1;
   724 }
   725 
   726 /* shared entry and comments */
   727 
   728 .comment-title h4 {
   729     font-size: inherit;
   730     font-weight: inherit;
   731     margin: 0;
   732     padding: 0;
   733 }
   734 
   735 .tools {
   736     text-align: center;
   737     padding: 10px;
   738     border: 1px solid #cde;
   739     background: #def;
   740     clear: both;
   741 }
   742 
   743 .frozen .tools {
   744     border: 1px solid #dee;
   745     background: #eff;
   746 }
   747 .screened .tools {
   748     border: 1px dashed #999;
   749     background: #fff;
   750 }
   751 .text {
   752     font-size: 90%;
   753 }
   754 .userpic {
   755     margin-bottom: 5px;
   756 }
   757 .userpic.empty {
   758     height: 100px;
   759     margin: 0 10px 5px 10px;
   760     border: 1px solid #eee;
   761 }
   762 
   763 
   764 /* Entries */
   765 
   766 .entry {
   767     line-height: 1.3em;
   768     letter-spacing: 0.01em;
   769     margin: 10px 0 40px 0;
   770 }
   771 .entry .header {
   772     color: #999;
   773     padding: 0px 10px 10px 0;
   774     margin-bottom: 10px;
   775 }
   776 
   777 .entry .posted {
   778     margin-left: 5px;
   779 }
   780 
   781 .entry .datetime {
   782     margin-left: 20px;
   783 }
   784 .entry .security {
   785     margin: 0.5em;
   786 }
   787 
   788 .entry .meta {
   789     float: left;
   790     clear: both;
   791     padding: 5px;
   792     margin: 10px;
   793     font-size: 80%;
   794     color: #333;
   795     background-color: #def;
   796     border: 1px solid #cde;
   797 }
   798 
   799 .entry .links {
   800     color: #999;
   801     clear: both;
   802 }
   803 
   804 .entry .meta-label {
   805     font-weight: bold;
   806 }
   807 
   808 
   809 
   810 .new-day {
   811     margin: 2px 0 2px 150px;
   812     font: normal 1.4em Verdana, sans-serif;
   813     color: #666;
   814 }
   815 
   816 
   817 /* collapsed entries */
   818 
   819 .collapsed-entry {
   820     margin-left: 130px;
   821 
   822 }
   823 .collapsed-entry .poster {
   824     font-weight: bold;
   825     font-size: 0.8em;
   826 }
   827 .expand {
   828     font: normal 1.4em Verdana, sans-serif;
   829 }
   830 .expand a,
   831 .expand a:visited {
   832     color: #ccc;
   833     text-decoration: none;
   834 }
   835 .expand a:hover,
   836 .expand a:active {
   837     color: #333;
   838 }
   839 .collapsed-entry .title {
   840     font: normal 1.2em Verdana, sans-serif;
   841     letter-spacing: -0.1em;
   842     margin: 0;
   843     padding: 0;
   844     display: inline;
   845 }
   846 
   847 
   848 
   849 /* Comments */
   850 
   851 #comments {
   852     clear: both;
   853     margin: 10px;
   854     margin-left: 10px;
   855     padding: 10px;
   856 }
   857 
   858 .nest {
   859     margin-left: 20px;
   860 }
   861 
   862 
   863 .comment {
   864     line-height: 1.3em;
   865     letter-spacing: 0.01em;
   866     margin: 0;
   867 }
   868 
   869 .comment .left {
   870     text-align: center;
   871     float: left;
   872     padding: 5px;
   873     width: 120px;
   874     margin-top: 15px;
   875 }
   876 
   877 .comment .right {
   878     padding: 10px;
   879     margin-left: 130px;
   880     background: #fff;
   881     border-bottom: 1px solid #eee;
   882 }
   883 
   884 .comment h2 {
   885     color: #3c0;
   886     font: normal 1.3em Verdana, sans-serif;
   887     letter-spacing: -0.1em;
   888     margin: 0;
   889     padding: 0;
   890     display: inline;
   891 }
   892 
   893 .comment.odd {
   894     background: #fff;
   895 }
   896 .comment.even {
   897     background: #fff;
   898 }
   899 .comment .header {
   900     color: #999;
   901     padding: 10px 10px 10px 0;
   902     margin-bottom: 10px;
   903 }
   904 .comment .posted {
   905     margin-left: 5px;
   906 }
   907 .comment .datetime {
   908     margin-left: 20px;
   909 }
   910 .comment .icon {
   911     margin: 0.5em;
   912 }
   913 
   914 .comment .meta {
   915     float: left;
   916     padding: 5px;
   917     margin: 10px;
   918     font-size: 80%;
   919     color: #333;
   920     background-color: #def;
   921     border: 1px solid #cde;
   922 }
   923 
   924 .comment .links {
   925     color: #999;
   926     clear: both;
   927 }
   928 
   929 /* Collapsed comments */
   930 
   931 .collapsed-comment {
   932     margin: 5px;
   933 }
   934 .collapsed-comment .title {
   935     font: normal 1.2em Verdana, sans-serif;
   936     letter-spacing: -0.1em;
   937     text-decoration: none;
   938     color: #3c0;
   939 }
   940 .collapsed-comment .poster {
   941     font-size: 0.8em;
   942 }
   943 .comment-pagination {
   944     clear: both;
   945     padding: 10px;
   946 }
   947 
   948 
   949 
   950 .entry-comments-bar {
   951     background: #eee;
   952     clear: both;
   953     padding: 10px;
   954 }
   955 .entry-comments-bar .comments-title {
   956     font: normal 1.5em Georgia, serif;
   957     color: #333;
   958     padding: 5px;
   959     letter-spacing: 0;
   960     display: block;
   961 }
   962 
   963 
   964 #multiform {
   965     font-size: 0.8em;
   966     margin: 10px;
   967     padding: 10px;
   968     border: 1px solid #cde;
   969     background: #def;
   970 }
   971 
   972 
   973 /* YearPage calendar */
   974 
   975 #calendar {
   976     margin: 10px;
   977     padding: 5px;
   978 }
   979 
   980 #calendar .month {
   981     margin: 10px;
   982     float: left;
   983 }
   984 
   985 #calendar .header a {
   986     color: #3c0;
   987     text-decoration: none;
   988 }
   989 
   990 .month th.weekday {
   991     color: #333;
   992 }
   993 
   994 .month .cell {
   995     height: 3em;
   996     width: 3em;
   997 }
   998 .month .cell.full {
   999     background: #def;
  1000     border: 1px solid #cde;
  1001 }
  1002 .month .cell.empty {
  1003     border: 1px solid #eee;
  1004 }
  1005 
  1006 .month .day {
  1007     text-align: left;
  1008     color: #999;
  1009     font-size: 0.8em;
  1010 }
  1011 .month .cell.empty .day {
  1012     color: #ddd;
  1013 }
  1014 .month .count {
  1015     text-align: center;
  1016 }
  1017 
  1018 .extra-box .month {
  1019     font-size: 0.5em;
  1020 }
  1021 
  1022 /* Comment quickreply */
  1023 
  1024 .quickreply {
  1025     padding: 5px;
  1026 }
  1027 .quickreply table {
  1028     border: 0px !important;
  1029 
  1030 }
  1031 .quickreply span.de {
  1032     display: block;
  1033     float: left;
  1034     font-size: 0.7em;
  1035     background: #def;
  1036     padding: 5px;
  1037     margin: 5px;
  1038     border: 1px solid #cde;
  1039 }
  1040 .quickreply td[align="right"] {
  1041     font-size: 0.8em;
  1042 }
  1043 
  1044 
  1045 /* TagsPage tag cloud */
  1046 
  1047 #tag-cloud {
  1048     margin: 10px;
  1049     padding: 5px;
  1050 }
  1051 
  1052 #tag-cloud a {
  1053     color: #3c0;
  1054     text-decoration: none;
  1055 }
  1056 
  1057 .module-tags_cloud li, .tags_cloud li {
  1058     display: inline;
  1059     list-style-type: none;
  1060 }
  1061 
  1062 
  1063 /* IconsPage */
  1064 
  1065 .icons-container {
  1066     margin: 10px;
  1067     padding: 10px;
  1068     }
  1069 
  1070 .sorting-options ul {
  1071     padding-left: 0;
  1072     }
  1073 
  1074 .sorting-options ul li {
  1075     display: inline;
  1076     }
  1077 
  1078 .icons-container .icon {
  1079     margin: 1em 0;
  1080     }
  1081 
  1082 .icon-image {
  1083     float: left;
  1084     clear: left;
  1085     margin-bottom: .25em;
  1086     min-width: 100px;
  1087     padding-right: 1em;
  1088     }
  1089 
  1090 .icon-info {
  1091     min-height: 100px;
  1092     }
  1093 
  1094 .icon-info span {
  1095     font-weight: bold;
  1096     }
  1097 
  1098 .icon-info .default {
  1099     text-decoration: underline;
  1100     }
  1101 
  1102 .icon-info .keywords ul {
  1103     display: inline;
  1104     padding-left: 0;
  1105     }
  1106 
  1107 .icon-info .keywords ul li {
  1108     display: inline;
  1109     list-style: none;
  1110     padding: 0 .25em 0 0;
  1111     }
  1112 
  1113 /* ReplyPage reply box */
  1114 
  1115 #reply {
  1116     margin: 10px 10px 10px 165px;
  1117     padding: 5px;
  1118 }
  1119 
  1120 #postform {
  1121     background: #def;
  1122     border: 1px solid #cde;
  1123     padding: 5px;
  1124     margin-top: 10px;
  1125     font-size: 0.8em;
  1126 }
  1127 
  1128 
  1129 """; }
  1130 
  1131 
  1132 
  1133 ###################################################
  1134 #      #                                          #
  1135 #  ~4. # EntryLite                                #
  1136 #      #                                          #
  1137 ###################################################
  1138 
  1139 # Shared methods used on/for both entries and comments.
  1140 #
  1141 #
  1142 
  1143 
  1144 # Gets the entry or comment's link icons (freeze, add to memories etc.),
  1145 # with the exception of the 'nav_prev' and 'nav_next' which are handled by
  1146 # Page::lay_back_forward().
  1147 #
  1148 function EntryLite::lay_get_linkbar() : string {
  1149     var string o;
  1150     var Link link;
  1151     foreach var string k ($.link_keyseq) {
  1152         if ( $k != "nav_prev" and $k != "nav_next" ) {
  1153             $link = $this->get_link($k);
  1154             if ( defined $link ) {
  1155                 $o = $o + $link->as_string();
  1156             }
  1157         }
  1158     }
  1159     return $o;
  1160 }
  1161 
  1162 # Returns a comma-separated string of tags.
  1163 #
  1164 function EntryLite::lay_get_tags() : string {
  1165     var string tags = "";
  1166     if ($.tags) {
  1167         foreach var int i (0 .. (size $.tags - 1)) {
  1168             var Tag t = $.tags[$i];
  1169 
  1170             $tags = $tags + """<a rel="tag" href="$t.url">$t.name</a>""";
  1171 
  1172             if ( $i < size $.tags - 1 ) {
  1173                 $tags = $tags + ", ";
  1174             }
  1175         }
  1176     }
  1177     return $tags;
  1178 }
  1179 
  1180 # Return a string representing the poster of this entry or comment.
  1181 #
  1182 function Page::lay_get_poster(EntryLite e) : string {
  1183     if ( not defined $e.poster ) {
  1184         return $*text_poster_anonymous;
  1185     } elseif ( not $e.poster->equals($e.journal) and $.view == "read" ) {
  1186 
  1187         # default: "%1 in %2"
  1188         return lay_string_placeholders( $*poster_in_journal, [$e.poster->as_string(), $e.journal->as_string()] );
  1189     } else {
  1190         return $e.poster->as_string();
  1191     }
  1192 }
  1193 
  1194 
  1195 
  1196 # Print a string containing any or all of the poster, date, time, tags
  1197 # and ip address of this entry or comment.
  1198 #
  1199 function Page::lay_print_posted_by(EntryLite e) : void {
  1200     var string format;
  1201     var string[] args;
  1202     var string tags;
  1203     var string timeformat;
  1204 
  1205     if ($this.timeformat24) {
  1206         $timeformat = $*posted_time_format_24;
  1207     } else {
  1208         $timeformat = $*posted_time_format;
  1209     }
  1210 
  1211     # default: "posted by %1 at %2 on %3";
  1212     $format = $*posted_by_at_on;
  1213 
  1214     $args = [
  1215           $this->lay_get_poster($e),
  1216           $e.time->date_format( $timeformat ),
  1217           $e.time->date_format( $*posted_date_format )
  1218           ];
  1219 
  1220     $tags = $e->lay_get_tags();
  1221     if ( $tags != "" ) {
  1222 
  1223         # default: "posted by %1 at %2 on %3 under %4";
  1224         $format = $*posted_by_at_on_in;
  1225         lay_array_push($args, $tags);
  1226 
  1227     } elseif ($e.metadata{"poster_ip"}) {
  1228 
  1229         # default: "posted by %1 at %2 on %3 from %4";
  1230         $format = $*posted_by_at_on_from;
  1231         lay_array_push($args, $e.metadata{"poster_ip"});
  1232     }
  1233 
  1234     print lay_string_placeholders( $format, $args );
  1235 }
  1236 
  1237 # Prints an entry or comment's text.
  1238 #
  1239 function Page::lay_print_text(EntryLite e) : void
  1240 "Prints an entry or comment's text."
  1241 {
  1242     println """<div class="text">""";
  1243 
  1244     $e->print_text();
  1245 
  1246     println "</div>";
  1247 }
  1248 
  1249 
  1250 ###################################################
  1251 #      #                                          #
  1252 #  ~5. # CommentInfo                              #
  1253 #      #                                          #
  1254 ###################################################
  1255 
  1256 # Get details of an entry's comments.
  1257 # show_read_link is included so EntryPage needn't show a link to itself.
  1258 #
  1259 function CommentInfo::lay_get_details(int pages, bool show_read_link) : string {
  1260     var string link;
  1261     if ($.count > 0) {
  1262 
  1263         if ( lang_map_plural($.count) ) {
  1264             # "%1 comments"
  1265             $link = lay_string_placeholders($*text_some_comments_link, [string($.count)]);
  1266 
  1267             if ( $show_read_link ) {
  1268                 $link = """<a href="$.read_url">$link</a>""";
  1269             }
  1270 
  1271             if ( $pages > 1 ) {
  1272                 $link = lay_string_placeholders($*text_some_comments_over_pages, [$link, string($pages)]);
  1273             } else {
  1274                 $link = lay_string_placeholders($*text_some_comments, [$link]);
  1275             }
  1276         } else {
  1277             # "%1 comment"
  1278             $link = lay_string_placeholders($*text_a_comment_link, [string($.count)]);
  1279             if ( $show_read_link ) {
  1280                 $link = """<a href="$.read_url">$link</a>""";
  1281             }
  1282             $link = lay_string_placeholders($*text_a_comment, [$link]);
  1283         }
  1284 
  1285     } else {
  1286 
  1287         # default: "There are no comments on this entry."
  1288         $link = $*text_no_comments;
  1289     }
  1290 
  1291     if (not $.enabled) {
  1292 
  1293         # default: "Comments are disabled."
  1294         $link = $link + " " + $*text_comments_disabled;
  1295     }
  1296     return $link;
  1297 }
  1298 
  1299 function Page::lay_print_comment_details(CommentInfo c) : void {
  1300     print $c->lay_get_details(0, true);
  1301 }
  1302 
  1303 # Prints a link to an entry's ReplyPage.
  1304 #
  1305 function Page::lay_print_entry_reply_link(CommentInfo c) : void {
  1306     if ($c.show_postlink) {
  1307         var string link = """<a href="$c.post_url">$*reply_link_link_text</a>""";
  1308         print " " + lay_string_placeholders( $*reply_link_text, [$link] );
  1309     }
  1310 }
  1311 
  1312 
  1313 ###################################################
  1314 #      #                                          #
  1315 #  ~6. # Entry                                    #
  1316 #      #                                          #
  1317 ###################################################
  1318 
  1319 function Page::lay_print_entry_linkbar(Entry e) {
  1320 
  1321     var string bar = $e->lay_get_linkbar();
  1322     if ( $bar == "" ) {
  1323         return;
  1324     }
  1325     println """<div class="tools">$bar</div>""";
  1326 }
  1327 
  1328 function Page::lay_print_entry_meta(Entry e) : void {
  1329     var string o = "";
  1330     var string caption;
  1331     var string val;
  1332     var Image i;
  1333 
  1334     if (size $e.metadata == 0) {
  1335         return;
  1336     }
  1337 
  1338     """
  1339     <div class="meta">
  1340     """;
  1341 
  1342     foreach var string k ($e.metadata) {
  1343         $caption = $k;
  1344         $val = $e.metadata{$k};
  1345         if ($k == "music") {
  1346             $caption = $*text_meta_music;
  1347         } elseif ($k == "mood") {
  1348             $caption = $*text_meta_mood;
  1349             if (defined $e.mood_icon) {
  1350                 $i = $e.mood_icon;
  1351                 $val = $i->as_string("'$e.metadata{$k}'")+" "+$val;
  1352             }
  1353         }
  1354         """
  1355         <div class="meta-item"><span class="meta-label">$caption:</span> $val</div>
  1356         """;
  1357     }
  1358     """
  1359     </div>
  1360     """;
  1361 }
  1362 
  1363 
  1364 function Page::lay_print_entry_header(Entry e) {
  1365     var string subject = ($e.subject != "" ? $e.subject : $*text_nosubject);
  1366     """
  1367     <div class="header">
  1368     <div class="title">
  1369         <h2 id="entry-$e.itemid"><a href="$e.permalink_url">$subject</a></h2>
  1370         """;
  1371         if ($e.security != "") {
  1372             print """<span class="security"><img src="$e.security_icon.url" alt="[$e.security]" /></span>""";
  1373         }
  1374         """
  1375     </div>
  1376     <div class="posted">"""; $this->lay_print_posted_by($e); """</div>""";
  1377     """</div>""";
  1378 }
  1379 
  1380 
  1381 function Page::lay_print_entry_left(Entry e) : void {
  1382     """
  1383     <div class="userpic">
  1384     """;
  1385     if ($e.userpic) {
  1386         print $e.userpic->as_string();
  1387     }
  1388     """
  1389     </div>
  1390     """;
  1391 }
  1392 
  1393 function Page::lay_print_entry_footer(Entry e) {
  1394     """
  1395     <div class="links">
  1396     """;
  1397     $this->lay_print_comment_details( $e.comments );
  1398     $this->lay_print_entry_reply_link( $e.comments );
  1399     """
  1400     </div>
  1401     """;
  1402 }
  1403 
  1404 
  1405 function Page::lay_print_entry(Entry e) {
  1406     """
  1407     <div class="entry">
  1408         <div class="left">
  1409         """;
  1410             $this->lay_print_entry_left($e);
  1411         """
  1412         </div>
  1413         <div class="right">
  1414         """;
  1415             $this->lay_print_entry_header($e);
  1416             $this->lay_print_text($e);
  1417             $this->lay_print_entry_meta($e);
  1418             $this->lay_print_entry_footer($e);
  1419         """
  1420         </div>
  1421     </div>
  1422     """;
  1423 }
  1424 
  1425 function Page::print_entry(Entry e) {
  1426     $this->lay_print_entry($e);
  1427 }
  1428 
  1429 function RecentPage::print_sticky_entry(StickyEntry s) {
  1430     $this->lay_print_entry($s);
  1431 }
  1432 
  1433 ###################################################
  1434 #      #                                          #
  1435 # ~6b. # Collapsed Entry                          #
  1436 #      #                                          #
  1437 ###################################################
  1438 
  1439 # MonthPage and FriendsPage by default show only a shortened version of an
  1440 # entry. I'm considering the same thing for RecentPage past a threshold --
  1441 # one or two full entries followed by a longer list of previously-posted
  1442 # titles.
  1443 #
  1444 # On FriendsPage these entries can be expanded in-place; on MonthPage
  1445 # the entry text isn't populated so they can only link to the full entry.
  1446 #
  1447 
  1448 # Print "expand" link.
  1449 #
  1450 function Page::lay_print_collapsed_entry_expand(Entry e) : void {
  1451     var string expand_url = $this->lay_build_url({".id" => string($e.itemid)}) + "#entry-$e.itemid";
  1452     print """<span class="expand"><a href="$expand_url" title="Expand this entry.">+</a></span>""";
  1453 }
  1454 
  1455 # Entry title.
  1456 #
  1457 function Page::lay_print_collapsed_entry_title(Entry e) : void {
  1458     var string subject = ($e.subject != "" ? $e.subject : $*text_nosubject);
  1459     print """<span class="title"><a href="$e.permalink_url" title="View this entry.">$subject</a></span>""";
  1460 }
  1461 
  1462 # Entry security unless public.
  1463 #
  1464 function Page::lay_print_collapsed_entry_security(Entry e) : void {
  1465     if ($e.security != "") {
  1466         print """ <span class="security">""" + $e.security_icon->as_string() + "</span>";
  1467     }
  1468 }
  1469 
  1470 # Entry comment count / "comments disabled" message.
  1471 #
  1472 function Page::lay_print_collapsed_entry_comments(Entry e) : void {
  1473     var string count = "";
  1474     var string max = "";
  1475     var string screened = "";
  1476     if ($e.comments.count > 0) {
  1477         if ($e.comments.maxcomments) {
  1478             # default: "!"
  1479             $max = $*collapsed_entry_comments_max_flag;
  1480         }
  1481         if ($e.comments.screened) {
  1482             # default "*"
  1483             $screened = $*collapsed_entry_comments_screened_flag;
  1484         }
  1485         # Assuming max and screened comments, default output is "(5000!*)"
  1486         # Just screened comments is "(343*)" etc.
  1487         $count = lay_string_placeholders( $*collapsed_entry_comments_count, [string($e.comments.count), $max, $screened] );
  1488     } elseif (not $e.comments.enabled) {
  1489         $count = $*collapsed_entry_comments_disabled;
  1490     }
  1491     print """ <span class="comments">$count</span>""";
  1492 }
  1493 # Entry poster.
  1494 #
  1495 function Page::lay_print_collapsed_entry_poster(Entry e) : void {
  1496     if ( $.view == "read" or not $e.poster->equals($.journal as UserLite) ) {
  1497         print """ &mdash; <span class="poster">""" + $this->lay_get_poster($e) + "</span>";
  1498     }
  1499 }
  1500 
  1501 # Print the actual entry.
  1502 #
  1503 function Page::lay_print_collapsed_entry(Entry e) {
  1504 
  1505     println """<div class="collapsed-entry">""";
  1506 
  1507     $this->lay_print_collapsed_entry_expand($e);
  1508     $this->lay_print_collapsed_entry_title($e);
  1509     $this->lay_print_collapsed_entry_security($e);
  1510     $this->lay_print_collapsed_entry_comments($e);
  1511     $this->lay_print_collapsed_entry_poster($e);
  1512 
  1513     println """</div>""";
  1514 }
  1515 
  1516 
  1517 
  1518 ###################################################
  1519 #      #                                          #
  1520 # ~7.  #  Page                                    #
  1521 #      #                                          #
  1522 #      # These methods do nothing, but are        #
  1523 #      # overridden in child layers.              #
  1524 #      #                                          #
  1525 ###################################################
  1526 
  1527 # Show the full version of an entry, or the collapsed
  1528 # version?
  1529 function Page::lay_entry_is_expanded(Entry e) : bool {
  1530     return true;
  1531 }
  1532 
  1533 # Some pages have extra content to print in the footer.
  1534 #
  1535 function Page::lay_print_extra_box() {
  1536     return;
  1537 }
  1538 
  1539 # Lay back-and-foward navigation. Between pages of
  1540 # entries on RecentPage, between months on MonthPage, etc.
  1541 #
  1542 function Page::lay_back_forward() : void {}
  1543 
  1544 # Returns a combination of page title and view title;
  1545 # only used in the `<title>` element of the HTML output.
  1546 
  1547 function Page::title() : string {
  1548     var string title = $this.global_title;
  1549     var string view = $this->view_title();
  1550 
  1551     return lay_string_placeholders( $*text_html_title, [$title, $view] );
  1552 }
  1553 
  1554 
  1555 # Prints an error page.
  1556 #
  1557 function Page::lay_print_errorpage(string message) {
  1558     """
  1559     <div class="error">
  1560     <h2 class="error-header">$*text_errorpage_title</h2>
  1561     <p>$message</p>
  1562     </div>
  1563     """;
  1564 }
  1565 
  1566 # Print the current tab. Made separate so it can be overridden
  1567 # in FriendsPage.
  1568 #
  1569 function Page::lay_print_navigation_current_tab() : void {
  1570     println """<li id="tab-current"><span>""" + lang_viewname($.view) + "</span></li>";
  1571 }
  1572 function Page::lay_navigation() {
  1573     var string nav;
  1574     var string alt;
  1575 
  1576     # Time to play "making up for S2's deficiencies"!
  1577     # No link is supplied to the TagsPage yet.
  1578     var string{} vu = $.view_url;
  1579     var string[] vo = $.views_order;
  1580 
  1581     if ( $vu{"tags"} == "" ) {
  1582         $vu{"tags"} = $.base_url + "/tag/";
  1583         $vo[size $vo] = "tags";
  1584     }
  1585 
  1586     """
  1587     <div id="navi">
  1588     <ul>
  1589     """;
  1590     foreach var string v ($vo) {
  1591         if ($.view == $v) {
  1592             $this->lay_print_navigation_current_tab();
  1593         } else {
  1594             println """<li><a href="$vu{$v}"><span>""" + lang_viewname($v) + "</span></a></li>";
  1595         }
  1596     }
  1597     """
  1598     </ul>
  1599     </div>
  1600     """;
  1601 }
  1602 
  1603 function Page::lay_print_extra_box_open(string title) : void {
  1604     var string alt = alternate("odd", "even");
  1605     """
  1606     <div class="extra-box $alt">
  1607     <h2 class="title">$title</h3>
  1608     """;
  1609 }
  1610 
  1611 function Page::lay_print_extra_box_close() : void {
  1612     print "</div>";
  1613 }
  1614 
  1615 # Prints a linklist. More complicated than it'd normally be because the
  1616 # style splits a list with headings into multiple lists. Sub-lists are
  1617 # not supported because they're not implemented in the core yet and show
  1618 # no signs of ever being so.
  1619 #
  1620 function Page::print_linklist() {
  1621     if ( size $.linklist == 0 ) {
  1622         return;
  1623     }
  1624     var bool open = false;
  1625     var UserLink l = $.linklist[0];
  1626 
  1627     if (not $l.is_heading) {
  1628         $this->lay_print_extra_box_open( $*linklist_default_title );
  1629         """
  1630         <ul class="linklist">
  1631         """;
  1632         $open = true;
  1633     }
  1634 
  1635     foreach var UserLink l ($.linklist) {
  1636         if ($l.is_heading) {
  1637             if ($open) {
  1638                 """
  1639                 </ul>
  1640                 """;
  1641                 $this->lay_print_extra_box_close();
  1642                 $open = false;
  1643             }
  1644             $this->lay_print_extra_box_open($l.title);
  1645             """
  1646             <ul class="linklist">
  1647             """;
  1648             $open = true;
  1649         } else {
  1650             """
  1651             <li><a href="$l.url">$l.title</a></li>
  1652             """;
  1653         }
  1654     }
  1655 
  1656     if ($open) {
  1657         """
  1658         </ul>
  1659         """;
  1660         $this->lay_print_extra_box_close();
  1661     }
  1662 }
  1663 
  1664 
  1665 
  1666 # Print one week in a calendar month.
  1667 #
  1668 function Page::lay_print_week(YearWeek w) : void {
  1669     """
  1670     <tr>
  1671     """;
  1672     if ($w.pre_empty > 0) {
  1673         foreach var int i (1..$w.pre_empty) {
  1674             """
  1675             <td class="blank cell">&nbsp;</td>
  1676             """;
  1677         }
  1678     }
  1679     foreach var YearDay d ($w.days) {
  1680         if ($d.num_entries > 0) {
  1681             """
  1682             <td class="full cell">
  1683                 <span class="day">$d.day</span>
  1684                 <div class="count"><a href="$d.url">$d.num_entries</a></div>
  1685             </td>
  1686             """;
  1687         } else {
  1688             """
  1689             <td class="empty cell">
  1690                 <span class="day">$d.day</span>
  1691                 <div class="count">&nbsp;</div>
  1692             </td>
  1693             """;
  1694         }
  1695     }
  1696     if ($w.post_empty > 0) {
  1697         foreach var int i (1..$w.post_empty) {
  1698             """
  1699             <td class="blank-cell">&nbsp;</td>
  1700             """;
  1701         }
  1702     }
  1703     """
  1704     </tr>
  1705     """;
  1706 }
  1707 
  1708 # Print a calendar month.
  1709 #
  1710 function Page::lay_print_month(YearMonth m) {
  1711     """
  1712     <table summary="Monthly calendar with links to each day's entries" class="month">
  1713     <tr>
  1714     """;
  1715     foreach var int d (weekdays()) {
  1716         """<th class="weekday">$*lang_dayname_short[$d]</th>""";
  1717     }
  1718     """
  1719     </tr>
  1720     """;
  1721     foreach var YearWeek w ($m.weeks) {
  1722         $this->lay_print_week($w);
  1723     }
  1724     """
  1725     </table>
  1726     """;
  1727 }
  1728 
  1729 
  1730 
  1731 function Page::lay_header() {
  1732     """
  1733     <div id="header">
  1734     """;
  1735 
  1736     var string subtitle;
  1737     if ($.global_subtitle != "") {
  1738         $subtitle = $this.global_subtitle + ". " + $this->view_title() + ".";
  1739     } else {
  1740         $subtitle = $this->view_title() + ".";
  1741     }
  1742     """
  1743     <h1>$.global_title</h1>
  1744     <p>$subtitle</p>
  1745     </div>
  1746     """;
  1747 }
  1748 
  1749 
  1750 function Page::lay_print_mini_calendar_box() {
  1751     var YearMonth m = $this->get_latest_month();
  1752     if ( defined $m and $m.has_entries ) {
  1753         $this->lay_print_extra_box_open( $m->month_format("%%month%%") );
  1754         $this->lay_print_month($m);
  1755         $this->lay_print_extra_box_close();
  1756     }
  1757 }
  1758 
  1759 function Page::lay_footer() {
  1760 
  1761     """
  1762     <div id="extra">
  1763         """;
  1764         $this->lay_print_extra_box();
  1765         $this->print_linklist();
  1766         $this->lay_print_mini_calendar_box();
  1767         lay_print_extra_boxes();
  1768         """
  1769     </div>
  1770 
  1771     <div id="footer">
  1772         <p><span class="top-link"><a href="#header">$*top_link_text</a></span> <a href="http://www.dreamwidth.org/customize/advanced/layerbrowse.bml?id=zesty/layout">Zesty</a>. """; server_sig(); """</p>
  1773     </div>
  1774     """;
  1775 }
  1776 
  1777 
  1778 function Page::print() {
  1779 
  1780 """
  1781 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  1782         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-loose.dtd">
  1783        <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
  1784 <head>
  1785 <title>"""+$this->title()+"""</title>
  1786 """;
  1787     $this->print_head();
  1788 """
  1789 <link rel="stylesheet" type="text/css" href="$.stylesheet_url" />
  1790 """;
  1791     if ($*custom_favicon != "") {
  1792         """<link rel="shortcut icon" href="$*custom_favicon" />""";
  1793     }
  1794 """
  1795 </head>
  1796 <body class="$.journal.username-$.view">
  1797     """;
  1798     $this->print_control_strip();
  1799     $this->lay_header();
  1800     $this->lay_navigation();
  1801 
  1802     $this->print_body();
  1803 
  1804     $this->lay_footer();
  1805     """
  1806 </body>
  1807 </html>
  1808 """;
  1809 }
  1810 
  1811 
  1812 ###################################################
  1813 #      #                                          #
  1814 # ~8.  # RecentPage                               #
  1815 #      #                                          #
  1816 ###################################################
  1817 
  1818 
  1819 function RecentPage::lay_build_url(string{} items) : string {
  1820     if ($.nav.skip != 0) {
  1821         $items{"skip"} = string($.nav.skip);
  1822     }
  1823     return $.base_url + lay_array_to_args($items);
  1824 }
  1825 
  1826 function RecentPage::lay_back_forward() : void {
  1827 
  1828     if ($.nav.backward_url == "" and $.nav.forward_url == "") {
  1829         return;
  1830     }
  1831     """
  1832     <div class="back-forward">
  1833     """;
  1834     if ($.nav.backward_url != "") {
  1835         var string previous = get_plural_phrase($.nav.backward_count, "text_skiplinks_back");
  1836         """
  1837         <div class="back">
  1838             <a href="$.nav.backward_url" title="$previous">&larr;</a>
  1839         </div>
  1840         """;
  1841     }
  1842     if ($.nav.forward_url != "") {
  1843         var string next = get_plural_phrase($.nav.forward_count, "text_skiplinks_forward");
  1844         """
  1845         <div class="forward">
  1846             <a href="$.nav.forward_url" title="$next">&rarr;</a>
  1847         </div>
  1848         """;
  1849     }
  1850     """
  1851     </div>
  1852     """;
  1853 }
  1854 
  1855 
  1856 function RecentPage::print_body() {
  1857     $this->lay_back_forward();
  1858     """
  1859     <div id="entries">
  1860     """;
  1861     if (size $.entries == 0) {
  1862         $this->lay_print_errorpage($*text_noentries_recent);
  1863     } else {
  1864         foreach var Entry e ($.entries) {
  1865             $this->print_entry($e);
  1866         }
  1867     }
  1868     """
  1869     </div>
  1870     """;
  1871     $this->lay_back_forward();
  1872 }
  1873 
  1874 
  1875 ###################################################
  1876 #      #                                          #
  1877 # ~9.  #  FriendsPage                             #
  1878 #      #                                          #
  1879 ###################################################
  1880 
  1881 
  1882 function FriendsPage::lay_build_url(string{} items) : string {
  1883     var string url = $.base_url;
  1884 
  1885     # Page might be "friendsfriends".
  1886     #
  1887     if ($.friends_mode != "") {
  1888         $url = $url + "/$.friends_mode";
  1889     } else {
  1890         $url = $url + "/read";
  1891     }
  1892 
  1893     # Page might be a friends group.
  1894     #
  1895     if ($.filter_active) {
  1896         $url = $url + "/$.filter_name";
  1897     }
  1898 
  1899     if ($.nav.skip != 0) {
  1900         $items{"skip"} = string($.nav.skip);
  1901     }
  1902     return $url + lay_array_to_args($items);
  1903 }
  1904 
  1905 # Is the default view mode for entries "expanded" or "collapsed"?
  1906 # Currently only actually used on the FriendsPage.
  1907 #
  1908 function FriendsPage::lay_get_current_view_mode() : string {
  1909     var string mode = $*default_view_mode;
  1910     if ($.args{"mode"} != "" and $mode != $.args{"mode"}) {
  1911         $mode = $.args{"mode"};
  1912     }
  1913     return $mode;
  1914 }
  1915 
  1916 # Returns the opposite of the current view mode for entries.
  1917 #
  1918 function FriendsPage::lay_get_alternate_view_mode() : string {
  1919     var string current = $this->lay_get_current_view_mode();
  1920     if ( $current == "expanded" ) {
  1921         return "collapsed";
  1922     } else {
  1923         return "expanded";
  1924     }
  1925 }
  1926 
  1927 function FriendsPage::lay_entry_is_expanded(Entry e) : bool {
  1928     var bool expanded = false;
  1929 
  1930     # We expand entries if they match the `.id=$entry_id` argument, but
  1931     # there's a problem in that the item id isn't necessarily unique.
  1932     # Because LJ uses (internally) a composite key of user id and item id,
  1933     # two different users' posts on the same page could have the same item id.
  1934     #
  1935     # I mention it mostly out of interest, since I'm not going to do anything
  1936     # to stop it happening.
  1937     #
  1938     # It's vanishingly unlikely, S2 doesn't expose the user ID (and the
  1939     # username isn't necessarily unique *or* safe), and even if there's a
  1940     # clash, what's the damage? But it might still happen at some point.
  1941     #
  1942     # It's not quite as simple as the birthday paradox, because low item ids
  1943     # are exponentially more likely to appear than high ones, and people who
  1944     # joined the site at the same time and have similar posting habits are
  1945     # quite likely to stay in the same range of item ids.
  1946     #
  1947     # This comment was much too long. Sorry!
  1948     #
  1949     if ($.args{"id"} != "" and int($.args{"id"}) == $e.itemid) {
  1950         $expanded = true;
  1951     } elseif ($this->lay_get_current_view_mode() == "expanded") {
  1952         $expanded = true;
  1953     }
  1954     return $expanded;
  1955 }
  1956 
  1957 
  1958 
  1959 
  1960 
  1961 # The FriendsPage tab has an extra mode-switching button on it.
  1962 #
  1963 function FriendsPage::lay_print_navigation_current_tab() : void {
  1964     var string alt = $this->lay_build_url( {".mode" => $this->lay_get_alternate_view_mode()} );
  1965     println """<li id="tab-current"><span>""" + lang_viewname($.view) + """ <a href="$alt">+</a></span></li>""";
  1966 }
  1967 
  1968 function FriendsPage::print_entry(Entry e) {
  1969     if ($e.new_day) {
  1970         print """<div class="new-day">""" + $e.time->date_format($*lang_fmt_date_long) + "</div>";
  1971     }
  1972     if ( $this->lay_entry_is_expanded($e) ) {
  1973         $this->lay_print_entry($e);
  1974     } else {
  1975         $this->lay_print_collapsed_entry($e);
  1976     }
  1977 
  1978 }
  1979 
  1980 
  1981 ###################################################
  1982 #      #                                          #
  1983 # ~10. #  DayPage                                 #
  1984 #      #                                          #
  1985 ###################################################
  1986 
  1987 function DayPage::lay_back_forward() : void {
  1988 
  1989     if ($.prev_url == "" and $.next_url == "") {
  1990         return;
  1991     }
  1992     """
  1993     <div class="back-forward">
  1994     """;
  1995     if ($.prev_url != "") {
  1996         """
  1997         <div class="back">
  1998             <a href="$.prev_url" title="$*text_day_prev">&larr;</a>
  1999         </div>
  2000         """;
  2001     }
  2002     if ($.next_url != "") {
  2003         """
  2004         <div class="forward">
  2005             <a href="$.next_url" title="$*text_day_next">&rarr;</a>
  2006         </div>
  2007         """;
  2008     }
  2009     """
  2010     </div>
  2011     """;
  2012 }
  2013 
  2014 
  2015 function DayPage::print_body() {
  2016 
  2017     if (not $.has_entries) {
  2018         $this->lay_print_errorpage($*text_noentries_day);
  2019     } else {
  2020         foreach var Entry e ($.entries) {
  2021             $this->print_entry($e);
  2022         }
  2023     }
  2024 
  2025 }
  2026 
  2027 
  2028 ###################################################
  2029 #      #                                          #
  2030 # ~11. #  MonthPage                               #
  2031 #      #                                          #
  2032 ###################################################
  2033 
  2034 # Can't expand MonthPage entries. Bah humbug.
  2035 #
  2036 function MonthPage::lay_print_collapsed_expand(Entry e) : void {
  2037     return;
  2038 }
  2039 
  2040 function MonthPage::lay_back_forward() : void {
  2041 
  2042     if ($.prev_url == "" and $.next_url == "") {
  2043         return;
  2044     }
  2045     """
  2046     <div class="back-forward">
  2047     """;
  2048     if ($.prev_url != "") {
  2049         """
  2050         <div class="back">
  2051             <a href="$.prev_url" title="Previous day.">&larr;</a>
  2052         </div>
  2053         """;
  2054     }
  2055     if ($.next_url != "") {
  2056         """
  2057         <div class="forward">
  2058             <a href="$.next_url" title="Next day.">&rarr;</a>
  2059         </div>
  2060         """;
  2061     }
  2062     """
  2063     </div>
  2064     """;
  2065 }
  2066 
  2067 # Can't get entry text in this view, so no full entries possible.
  2068 #
  2069 function MonthPage::print_entry(Entry e) : void {
  2070     return $this->lay_print_collapsed_entry($e);
  2071 }
  2072 
  2073 
  2074 
  2075 # Print a box containing information about other linkable months.
  2076 #
  2077 function MonthPage::lay_print_extra_box() : void {
  2078     if (size $.months == 0) {
  2079         return;
  2080     }
  2081 
  2082     """
  2083     <div class="extra-box">
  2084         <h3 class="title">$.date.year</h3>
  2085         <ul>
  2086     """;
  2087 
  2088     foreach var MonthEntryInfo m ($.months) {
  2089         if ($.date.year == $m.date.year) {
  2090             println """<li><a href="$m.url">"""+$m.date->date_format("%%month%%")+"</a></li>";
  2091         }
  2092     }
  2093 
  2094     """
  2095         </ul>
  2096     </div>
  2097     """;
  2098 }
  2099 
  2100 
  2101 
  2102 function MonthPage::print_body {
  2103     var bool any = false;
  2104     $this->lay_back_forward();
  2105     """
  2106     <div id="entries">
  2107     """;
  2108     foreach var MonthDay d ($.days) {
  2109         if ($d.has_entries) {
  2110             print """<div class="new-day">""" + $d.date->date_format($*lang_fmt_date_long) + "</div>";
  2111             foreach var Entry e ($d.entries) {
  2112                 $this->print_entry($e);
  2113             }
  2114             $any = true;
  2115         }
  2116     }
  2117     """
  2118     </div>
  2119     """;
  2120     if ( not $any ) {
  2121         # default: "No entries were posted on the selected month."
  2122         return $this->lay_print_errorpage( $*error_monthpage_no_entries );
  2123     }
  2124     $this->lay_back_forward();
  2125 }
  2126 
  2127 
  2128 
  2129 ###################################################
  2130 #      #                                          #
  2131 # ~12. # EntryPage                                #
  2132 #      #                                          #
  2133 ###################################################
  2134 
  2135 # TODO: this is broken in the core.
  2136 # Waiting on http://rt.livejournal.org/Ticket/Display.html?id=1266 .
  2137 #
  2138 function EntryPage::lay_comment_poster_is_suspended(Comment c) : bool {
  2139     return $.viewing_thread and not $c.full and $c.depth == 1;
  2140 }
  2141 
  2142 function EntryPage::lay_print_comment_details(CommentInfo c) : void {
  2143     print $c->lay_get_details($.comment_pages.total, false);
  2144 }
  2145 
  2146 # "Ideally layouts should never override this"... well how about you
  2147 # actually make it work on all views, then?
  2148 function EntryPage::view_title() : string {
  2149     var string subject = ($.entry.subject != "" ? $.entry.subject : $*text_nosubject);
  2150     if ( $.viewing_thread ) {
  2151         $subject = lay_string_placeholders( "%1 : comments", [$subject] );
  2152     }
  2153     return $subject;
  2154 }
  2155 
  2156 function EntryPage::lay_back_forward() : void {
  2157     var Link prev = $.entry->get_link("nav_prev");
  2158     var Link next = $.entry->get_link("nav_next");
  2159 
  2160     if ( isnull $prev and isnull $next ) {
  2161         return;
  2162     }
  2163     """
  2164     <div class="back-forward">
  2165     """;
  2166     if ( defined $prev ) {
  2167         """
  2168         <div class="back">
  2169             <a href="$prev.url" title="$prev.caption">&larr;</a>
  2170         </div>
  2171         """;
  2172     }
  2173     if ( defined $next ) {
  2174         """
  2175         <div class="forward">
  2176             <a href="$next.url" title="$next.caption">&rarr;</a>
  2177         </div>
  2178         """;
  2179     }
  2180     """
  2181     </div>
  2182     """;
  2183 
  2184 }
  2185 
  2186 
  2187 function EntryPage::lay_print_comment_linkbar(Comment c)
  2188 "Same as Page::lay_print_entry_linkbar except that it also prints
  2189 the multiform checkbox if the multiform is on. "
  2190 {
  2191     var string bar = $c->lay_get_linkbar();
  2192 
  2193     if ( $bar == "" and not $.multiform_on ) {
  2194         return;
  2195     }
  2196 
  2197     print """<div class="tools" id="tools$c.talkid">$bar""";
  2198 
  2199     if ($.multiform_on) {
  2200         $c->print_multiform_check();
  2201     }
  2202 
  2203     print "</div>";
  2204 
  2205 }
  2206 
  2207 function EntryPage::lay_print_comment_links(Comment c) : void {
  2208 
  2209     println """<div class="links">""";
  2210 
  2211     if ( $.viewing_thread and $c.depth == 1 ) {
  2212         """[<a href="$.entry.permalink_url">$*text_comment_parent_entry</a>] """;
  2213     }
  2214 
  2215     """[<a href="$c.permalink_url">$*text_comment_permalink</a>] """;
  2216 
  2217     if ( $c.parent_url != "" ) {
  2218         """[<a href="$c.parent_url">$*text_comment_parent</a>] """;
  2219     }
  2220 
  2221     if ( $c.frozen ) {
  2222         """[$*text_comment_frozen]""";
  2223     } elseif ( $this->lay_comment_poster_is_suspended($c) ) {
  2224         """[$*text_comment_poster_is_suspended]""";
  2225     } else {
  2226         """["""; $c->print_reply_link({"linktext" => $*text_comment_reply}); """]""";
  2227     }
  2228 
  2229     println """</div>""";
  2230 
  2231 }
  2232 
  2233 function EntryPage::print_comment(Comment c) : void {
  2234 
  2235     var string class = "comment " + alternate("odd", "even");
  2236     var string subject = ($c.subject == "") ? $*text_nosubject : $c.subject;
  2237 
  2238     var string state = "state";
  2239     if ($c.frozen) {
  2240         $state = "frozen";
  2241     } elseif ($c.screened) {
  2242         $state = "screened";
  2243     }
  2244 
  2245     println """<div class="nest">""";
  2246 
  2247     # This "state" div is a dodgy hack for the JavaScript set_handler stuff.
  2248     # It'll be changed to "frozen", "screened" etc. if the quick-change buttons
  2249     # are used.
  2250     # No apologies. :P
  2251     """
  2252     <div class="$state" id="state$c.talkid">
  2253         <div class="$class" id="$c.dom_id">
  2254             <div class="left">
  2255             """;
  2256             if ($c.userpic) {
  2257                 """<div class="userpic">""";
  2258                 print $c.userpic->as_string();
  2259                 """</div>""";
  2260             } else {
  2261                 """<div class="userpic empty">&nbsp;</div>""";
  2262             }
  2263             $this->lay_print_comment_linkbar($c);
  2264 
  2265             """
  2266             </div>
  2267             <div class="right">
  2268                 <div class="header">
  2269                     <div class="title">
  2270                         <h3 id="t$c.talkid"><a href="$c.permalink_url">$subject</a></h2>
  2271                         """;
  2272                         if (defined $c.subject_icon) {
  2273                             print """<span class="icon">""" + $c.subject_icon->as_string() + "</span>";
  2274                         }
  2275                         """
  2276                     </div>
  2277                     <div class="posted">"""; $this->lay_print_posted_by($c); """</div>
  2278                 </div>
  2279                 """;
  2280                 $this->lay_print_text($c);
  2281                 $this->lay_print_comment_links($c);
  2282                 """
  2283 
  2284             </div>
  2285         </div>
  2286     </div>
  2287     """;
  2288     $c->print_reply_container({"class" => "quickreply"});
  2289     """
  2290     <div id="c-reply-$c.talkid"></div>
  2291     """;
  2292 
  2293     $this->print_comments($c.replies);
  2294 
  2295     """</div>""";
  2296 }
  2297 
  2298 function EntryPage::print_comment_partial(Comment c) {
  2299 
  2300     var string poster = defined $c.poster ? $c.poster->as_string() : $*text_poster_anonymous;
  2301     var string subject = $c.subject != "" ? $c.subject : $*text_nosubject;
  2302 
  2303     """
  2304     <div class="nest">
  2305     <div class="collapsed-comment">
  2306     """;
  2307     if ( $c.deleted ) {
  2308         """<span class="title">(deleted comment)</span>""";
  2309     } elseif ( $c.screened ) {
  2310         """
  2311         <a class="title" href="$c.permalink_url">$subject</a> &mdash; <span class="poster">$poster</span> [screened]
  2312         """;
  2313     } else {
  2314         """
  2315         <a class="title" href="$c.permalink_url">$subject</a> &mdash; <span class="poster">$poster</span>
  2316         """;
  2317     }
  2318 
  2319 
  2320 
  2321     $this->print_comments($c.replies);
  2322     """
  2323     </div>
  2324     </div>
  2325     """;
  2326 }
  2327 
  2328 
  2329 function EntryPage::print_comments(Comment[] comments) {
  2330     if (size $comments == 0) {
  2331         return;
  2332     }
  2333 
  2334     foreach var Comment c ($comments) {
  2335         if ($c.full) {
  2336             $this->print_comment($c);
  2337         }
  2338         # special case for suspended comments.
  2339         elseif ( $this->lay_comment_poster_is_suspended($c) ) {
  2340             $this->print_comment($c);
  2341         } else {
  2342             $this->print_comment_partial($c);
  2343         }
  2344     }
  2345 }
  2346 
  2347 
  2348 
  2349 function EntryPage::lay_print_entry_left(Entry e) : void {
  2350     """
  2351     <div class="userpic">
  2352     """;
  2353     if ($e.userpic) {
  2354         print $e.userpic->as_string();
  2355     }
  2356     """
  2357     </div>
  2358     """;
  2359     $this->lay_print_entry_linkbar($.entry);
  2360 }
  2361 function EntryPage::lay_print_entry_footer(Entry e) {
  2362     return;
  2363 }
  2364 
  2365 function EntryPage::print_entry(Entry e) {
  2366     $this->lay_print_entry($e);
  2367 }
  2368 
  2369 function EntryPage::lay_comment_pagination() : void {
  2370 
  2371     # no comments
  2372     if ($.entry.comments.count == 0) {
  2373         return;
  2374     }
  2375 
  2376     var ItemRange range = $.comment_pages;
  2377 
  2378     # only one page of comments
  2379     if ($range.all_subitems_displayed) {
  2380         return;
  2381     }
  2382 
  2383     """
  2384     <div class="comment-pagination">
  2385     """;
  2386 
  2387     if ( $range.url_last != "" ) {
  2388         """<a href="$range.url_last">&larr;</a>""";
  2389     }
  2390     foreach var int page (1 .. $range.total) {
  2391         if ($range.current != $page) {
  2392             """ <a href='""" + $range->url_of($page) + """'>$page</a> """;
  2393         } else {
  2394             """ $page """;
  2395         }
  2396     }
  2397     if ( $range.url_next != "" ) {
  2398         """<a href="$range.url_next">&rarr;</a>""";
  2399     }
  2400 
  2401     """
  2402     </div>
  2403     """;
  2404 }
  2405 
  2406 function EntryPage::lay_print_comments() : void {
  2407 
  2408     if ( $.entry.comments.enabled and size $.comments > 0 ) {
  2409 
  2410         # JavaScript voodoo.
  2411         #
  2412         #
  2413         set_handler("screen_comment_#", [
  2414             [ "set_class", "state#", "screened" ]
  2415         ]);
  2416         set_handler("freeze_comment_#", [
  2417             [ "set_class", "state#", "frozen" ]
  2418         ]);
  2419         set_handler("unscreen_comment_#", [
  2420             [ "set_class", "state#", "state" ]
  2421         ]);
  2422         set_handler("unfreeze_comment_#", [
  2423             [ "set_class", "state#", "state" ]
  2424         ]);
  2425 
  2426         """
  2427         <div id="comments">
  2428         """;
  2429 
  2430         if ($.multiform_on) {
  2431             $this->print_multiform_start();
  2432         }
  2433 
  2434         $this->print_comments($.comments);
  2435 
  2436         if ($.multiform_on) {
  2437             """
  2438             <div id="multiform">
  2439             """;
  2440             $this->print_multiform_actionline();
  2441             $this->print_multiform_end();
  2442             """
  2443             </div>
  2444             """;
  2445         }
  2446 
  2447         """
  2448         </div>
  2449         """;
  2450     }
  2451 
  2452 }
  2453 
  2454 function EntryPage::lay_print_entry_comments_bar() : void {
  2455     """
  2456     <div class="entry-comments-bar">
  2457         <span class="comments-title">""";
  2458         $this->lay_print_comment_details( $.entry.comments );
  2459         $this->lay_print_entry_reply_link( $.entry.comments );
  2460         """</span>
  2461     """; $this->lay_comment_pagination(); """
  2462     </div>
  2463     """;
  2464 }
  2465 
  2466 function EntryPage::print_body() : void {
  2467 
  2468     if ( $.viewing_thread ) {
  2469         return $this->lay_print_comments();
  2470     }
  2471 
  2472     $this->lay_back_forward();
  2473     """
  2474     <div id="entries">
  2475         """;
  2476         $this->print_entry($.entry);
  2477         """
  2478     </div>
  2479     """;
  2480     $this->lay_back_forward();
  2481 
  2482     $this->lay_print_entry_comments_bar();
  2483 
  2484     $this->lay_print_comments();
  2485 
  2486     # Show comment pagination again if necessary.
  2487     if ( $.comment_pages.total > 1 ) {
  2488         $this->lay_print_entry_comments_bar();
  2489     }
  2490 }
  2491 
  2492 
  2493 
  2494 
  2495 
  2496 
  2497 ###################################################
  2498 #      #                                          #
  2499 # ~13. # ReplyPage                                #
  2500 #      #                                          #
  2501 ###################################################
  2502 
  2503 
  2504 function ReplyPage::lay_print_comment(EntryLite e) {
  2505 
  2506     var string subject = ($e.subject != "" ? $e.subject : $*text_nosubject);
  2507 
  2508     """
  2509     <div class="comment">
  2510         <div class="left">
  2511         <div class="userpic">
  2512         """;
  2513         if ($e.userpic) {
  2514             print $e.userpic->as_string();
  2515         }
  2516         """
  2517         </div>
  2518         </div>
  2519         <div class="right">
  2520             <div class="header">
  2521                 <div class="title">
  2522                     <h2><a href="$e.permalink_url">$subject</a></h2>
  2523                 </div>
  2524                 <div class="posted">"""; $this->lay_print_posted_by($e); """</div>
  2525             </div>
  2526             """;
  2527             $this->lay_print_text($e);
  2528             """
  2529             <div class="links">[<a href="$.entry.permalink_url">parent entry</a>] [<a href="$e.permalink_url">$*text_permalink</a>]</div>
  2530         </div>
  2531     </div>
  2532     """;
  2533 }
  2534 
  2535 function ReplyPage::print_body() : void {
  2536 
  2537     # replying to a comment, not an entry.
  2538     #
  2539     """
  2540     <div id="entries">
  2541     """;
  2542     # replying to an entry?
  2543     if ( $this.replyto.depth == 0 ) {
  2544         $this->print_entry($.entry);
  2545     }
  2546     # no, it's a comment
  2547     else {
  2548         $this->lay_print_comment($.replyto);
  2549     }
  2550     """
  2551     </div>
  2552     <div id="reply">
  2553     <h2>Reply</h2>
  2554     """;
  2555     $.form->print();
  2556     """
  2557     </div>
  2558     """;
  2559 }
  2560 
  2561 
  2562 ###################################################
  2563 #      #                                          #
  2564 # ~14. # YearPage                                 #
  2565 #      #                                          #
  2566 ###################################################
  2567 
  2568 # Prints a list of other linkable years.
  2569 #
  2570 function YearPage::lay_print_extra_box() {
  2571     var string year;
  2572     if (size $.years < 2) {
  2573         return;
  2574     }
  2575 
  2576     """
  2577     <div class="extra-box">
  2578         <h3 class="title">Years</h3>
  2579         <ul>
  2580         """;
  2581         foreach var YearYear y ($.years) {
  2582             if ($y.displayed) {
  2583                 $year = string($y.year);
  2584             } else {
  2585                 $year = """<a href="$y.url">$y.year</a>""";
  2586             }
  2587             println """<li>$year</li>""";
  2588         }
  2589         """
  2590         </ul>
  2591     </div>
  2592     """;
  2593 }
  2594 
  2595 function YearPage::lay_back_forward() : void {
  2596     if (size $.years < 2) {
  2597         return;
  2598     }
  2599 
  2600     var YearYear next;
  2601     var YearYear last;
  2602 
  2603     foreach var YearYear y ($.years) {
  2604         if ( $y.year == $.year - 1 ) {
  2605             $last = $y;
  2606         } elseif ( $y.year == $.year + 1 ) {
  2607             $next = $y;
  2608         }
  2609     }
  2610 
  2611     """
  2612     <div class="back-forward">
  2613     """;
  2614     if ( defined $last ) {
  2615         """
  2616         <div class="back">
  2617             <a href="$last.url" title="Previous year.">&larr;</a>
  2618         </div>
  2619         """;
  2620     }
  2621     if ( defined $next ) {
  2622         """
  2623         <div class="forward">
  2624             <a href="$next.url" title="Next year.">&rarr;</a>
  2625         </div>
  2626         """;
  2627     }
  2628     """
  2629     </div>
  2630     """;
  2631 
  2632 }
  2633 
  2634 
  2635 function YearPage::print_month(YearMonth m) {
  2636     """
  2637     <div class="calendar-month">
  2638         <h2 class="title"><a href="$m.url">"""+$m->month_format("%%month%%")+"""</a></h2>
  2639     """;
  2640     $this->lay_print_month($m);
  2641     """
  2642     </div>
  2643     """;
  2644 }
  2645 
  2646 
  2647 function YearPage::print_body {
  2648     if ( size $.months == 0 ) {
  2649         return $this->lay_print_errorpage( $*error_yearpage_no_entries );
  2650     }
  2651 
  2652     $this->lay_back_forward();
  2653     """
  2654     <div id="calendar">
  2655     """;
  2656     foreach var YearMonth m ($.months) {
  2657         if ($m.has_entries) {
  2658             $this->print_month($m);
  2659         }
  2660     }
  2661     """
  2662     </div>
  2663     """;
  2664     $this->lay_back_forward();
  2665 }
  2666 
  2667 ###################################################
  2668 #      #                                          #
  2669 # ~15. # MessagePage                              #
  2670 #      #                                          #
  2671 #      # Just a stub. AFAICT it's not used in the #
  2672 #      # core yet, so I can't test it.            #
  2673 #      #                                          #
  2674 ###################################################
  2675 
  2676 function MessagePage::print_body() {
  2677     """
  2678     <div id="message">
  2679     <p>"""; $this->print_message(); """</p>
  2680     </div>
  2681     """;
  2682 }
  2683 
  2684 ###################################################
  2685 #      #                                          #
  2686 # ~16. # TagsPage                                 #
  2687 #      #                                          #
  2688 ###################################################
  2689 
  2690 # Weighted tag cloud / heatmap.
  2691 #
  2692 function TagsPage::print_body() {
  2693     # since there is no heading, make invisible one here for
  2694     # screenreaders
  2695     """
  2696     <div id="tag-cloud" class="tags_cloud">
  2697     <h2 class="invisible">Visible tags</h2>
  2698     <ul>
  2699     """;
  2700 
  2701     # font min and max as % values
  2702     var int fontmin = 80;
  2703     var int fontmax = 400;
  2704     var int fontspread = $fontmax - $fontmin;
  2705 
  2706     var int fontstep = 0;
  2707 
  2708     # set later
  2709     var int countspread;
  2710     var int fontsize;
  2711     var string font;
  2712 
  2713     var int highest = 0;
  2714     var int lowest = 999999;
  2715     var int count;
  2716     
  2717     foreach var TagDetail tag ($.tags) {
  2718         if ($tag.use_count > $highest) {
  2719             $highest = $tag.use_count;
  2720         }
  2721         if ($tag.use_count < $lowest) {
  2722             $lowest = $tag.use_count;
  2723         }
  2724     }
  2725 
  2726     $countspread = $highest - $lowest;
  2727 
  2728     if ($countspread > 0) {
  2729         $fontstep = $fontspread/$countspread;
  2730     }
  2731 
  2732     foreach var TagDetail tag ($.tags) {
  2733         if ($highest == $lowest) {
  2734             $font = string($fontmin) + "%";
  2735         } else {
  2736             $fontsize = $fontmin + (($tag.use_count - $lowest) * $fontstep);
  2737             $font = string($fontsize) + "%";
  2738         }
  2739 
  2740         """
  2741         <li>
  2742         <a rel="tag" class="used-$tag.use_count visible-to-$tag.visibility" href="$tag.url" style="font-size: $font;">$tag.name</a>
  2743         <span class ="invisible"> used $tag.use_count times</span></li>
  2744         """;
  2745     }
  2746 
  2747     """
  2748     </ul></div>
  2749     """;
  2750 }