# -*-s2-*- layerinfo "type" = "layout"; layerinfo "name" = "Zesty"; layerinfo "lang" = "en"; layerinfo "author" = "Sam Angove"; layerinfo "author_email" = "net.rephrase@sam"; layerinfo "is_public" = 1; layerinfo "source_viewable" = 1; layerinfo "redist_uniq" = "zesty/layout"; ################################################### # # # [S2] Zesty # # # # Table of Contents # # ================= # # # # ~i. Changelog # # ~ii. License # # ~iii. Notes # # # # Customization/i18n Properties # # ----------------------------- # # ~1. Properties # # # # Utility functions # # ----------------- # # ~2. Utility functions # # # # CSS # # --- # # ~3. Stylesheet # # # # Shared methods # # -------------- # # Methods used on multiple views for getting or # # printing information about entries. # # # # ~4. EntryLite # # ~5. CommentInfo # # ~6. Entry # # # # Global view # # ----------- # # Templates used on all views as well as methods # # overridden by specific views. # # # # ~7. Page # # # # Regular views # # ------------- # # These four views have substantially similar # # logic. # # # # ~8. RecentPage # # ~9. FriendsPage # # ~10. DayPage # # ~11. MonthPage # # # # Entry views # # ----------- # # These views require significant extra logic. # # They are not available to free users. # # # # ~12. EntryPage # # ~13. ReplyPage # # # # Miscellaneous views # # ------------------- # # These views cannot print entries. # # # # ~14. YearPage # # ~15. MessagePage # # ~16. TagsPage # # # ################################################### ################################################### # # # # ~i. # ~Changelog # # # # ################################################### # 2006-07-18 -- I can't remember, but I'm releasing it now. ;) # 2006-07-10 -- English stripping # 2006-07-09 -- general cleanup, fix footer # 2006-07-03 -- initial build # ################################################### # # # # ~ii. # ~License # # # # ################################################### # "Zesty" LiveJournal S2 style # # Copyright (c) 2006 Sam Angove # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ################################################### # # # # ~ii. # ~Notes # # # # ################################################### # - The CSS is very messy. Haven't had time to clean it up. Sorry! # # - Most customization properties are for i18n purposes. Note that many of # them use the `lay_string_placeholders()` function; it's not wholly # satisfactory but it's more flexible than the core. # # - There are no props for colours, borders etc. It's a huge headache and # I can't be bothered. Since this isn't a core style, no-one but a paid # user can use it anyway, and they'll be able to edit the CSS directly. # # - I have deliberately ignored OOP and used # `View::lay_print_obj(Obj o)` over `var Obj o; $o->print()` # wherever possible. It's usually futile to use the latter because # the method needs to be overridable in different views -- different # requirements for entry printing on MonthPage and EntryPage, for example. # # Global functions have been avoided for the same reason. ################################################### # # # # !1. # !Properties # # # # ################################################### propgroup presentation { property use num_items_recent; property use num_items_reading; property use num_items_icons; property use use_journalstyle_entry_page; property use tags_page_type; property use icons_page_sort; property use use_shared_pic; property use userlite_interaction_links; property use entry_management_links; property use comment_management_links; } set num_items_recent = 10; set num_items_reading = 20; propgroup Text { property use text_day_next; property use text_day_prev; property use text_skiplinks_back; property use text_skiplinks_forward; property use text_permalink; property use text_stickyentry_subject; property use text_post_comment; property use text_post_comment_friends; set text_permalink = "permalink"; set text_stickyentry_subject = "Sticky:"; set text_post_comment = "reply"; set text_post_comment_friends = "reply"; property use text_nosubject; property use text_poster_anonymous; set text_nosubject = "(no subject)"; set text_poster_anonymous = "(anonymous)"; property use text_meta_mood; property use text_meta_music; set text_meta_mood = "Mood:"; set text_meta_music = "Music:"; property use text_view_archive; property use text_view_recent; property use text_view_friends; property use text_view_friends_comm; property use text_view_month; property use text_view_userinfo; set text_view_archive = "Calendar"; set text_view_recent = "Recent"; set text_view_friends = "Read"; set text_view_month = "Monthly Archive"; set text_view_userinfo = "Profile"; # For these properties I use a format vaguely similar to printf/sprintf. # They're passed to a method which gives them an array of strings. # you can use printf-style %s to insert the strings one at a time, or # use %1, %2 .. %9 to select them by number. # # Example: this string "posted by %1 at %2 on %3" is passed an array # containing the entry poster, the time of posting and the date of # posting. # # %1 will always refer to the poster, so a reformulation might be # something like "at %2 on %3, %1 wrote:". # # It's perfectly okay to ignore some or all of the arguments. That is, # there's nothing wrong with something like "I said on %3". property string posted_by_at_on { noui = 1; des = "Posted by [1:poster] at [2:time] on [3:date] string."; } set posted_by_at_on = "posted by %1 at %2 on %3"; property string posted_by_at_on_in { noui = 1; des = "Posted by [1:poster] at [2:time] on [3:date] under [4:tags] string."; } set posted_by_at_on_in = "posted by %1 at %2 on %3 under %4"; property string posted_by_at_on_from { noui = 1; des = "Posted by [1:poster] at [2:time] on [3:date] from [4:ip address] string."; } set posted_by_at_on_from = "posted by %1 at %2 on %3 from %4"; property string poster_in_journal { noui = 1; des = "[1:poster] in [2:journal] string"; } set poster_in_journal = "%1 in %2"; property string posted_time_format { noui = 1; des = "[time] format for from 'posted by [poster] at [time] ...'"; } set posted_time_format = "%%hh%%:%%min%%%%a%%m"; property string posted_time_format_24 { noui = 1; des = "[time] format for from 'posted by [poster] at [time] ...'"; } set posted_time_format_24 = "%%HH%%:%%min%%"; property string posted_date_format { noui = 1; des = "[date] format for 'posted by [poster] at [time] on [date] ...'"; } set posted_date_format = "%%dd%%/%%mm%%/%%yyyy%%"; # I want to have links like this: # # There are 2 comments on this entry. # # The HTML can't be part of the property, because S2 "helpfully" escapes # it for me. This ugly hack is used instead, wrapping $*text_a_comment_link # inside $*text_a_comment. # # ('There is %2 comment on this entry.') property string text_a_comment_link { } property string text_a_comment { } set text_a_comment_link = "%1 comment"; set text_a_comment = "There is %1 on this entry."; property string text_some_comments_link { } property string text_some_comments { } set text_some_comments_link = "%1 comments"; set text_some_comments = "There are %1 on this entry."; property string text_some_comments_over_pages { } set text_some_comments_over_pages = "There are %1 over %2 pages."; property string text_no_comments {} set text_no_comments = "There are no comments on this entry."; property string text_comments_disabled {} set text_comments_disabled = "Comments are disabled."; property string text_errorpage_title { des = "Error page title."; noui = 1; } set text_errorpage_title = "No content"; # For some reason the core only provides a message for recent and day pages. # property string error_monthpage_no_entries { noui = 1; des = "Error message shown if no entries are available on a MonthPage"; } set error_monthpage_no_entries = "No entries were posted on the selected month."; property string error_yearpage_no_entries { noui = 1; des = "Error message shown if no entries are available on a YearPage"; } set error_yearpage_no_entries = "No entries were posted on the selected year."; property string text_html_title { des = "Title that goes in the HTML element. Is given two parameters, global title and view title."; } set text_html_title = "%2 [%1]"; property string collapsed_entry_comments_disabled { des = "String shown on a collapsed entry if comments are disabled."; } set collapsed_entry_comments_disabled = "(-)"; property string collapsed_entry_comments_max_flag { des = "Passed into the comment-count string as %2 if an entry's maximum comments have been reached."; } set collapsed_entry_comments_max_flag = "!"; property string collapsed_entry_comments_screened_flag { des = "Passed into the comment-count string as %3 if an entry has screened comments visible to the user."; } set collapsed_entry_comments_screened_flag = "*"; property string collapsed_entry_comments_count { des = "Comment-count shown on a collapsed entry, not shown if there are no comments."; note = "'%1' will be replaced by the number of comments. If the maximum number of comments has been reached, %2 will contain the max flag. If there are screened comments visible to the user, %3 will contain the screened flag."; } set collapsed_entry_comments_count = "(%1%2%3)"; property string linklist_default_title { des = "Linklist title."; } set linklist_default_title = "Links"; property string reply_link_link_text { des = "Text for the reply link."; } set reply_link_link_text = "Reply"; property string reply_link_text { des = "Non-linked reply link text. %1 is replaced with the link."; } set reply_link_text = "(%1.)"; property string top_link_text { des = "Text of the link to return to the top of the page."; } set top_link_text = "Top"; property use text_comment_frozen; property use text_comment_parent; property use text_comment_reply; set text_comment_frozen = "thread is frozen"; set text_comment_parent = "parent"; set text_comment_reply = "reply"; property string text_comment_permalink { des = "Permalink to the comment."; } set text_comment_permalink = "link"; property string text_comment_poster_is_suspended { des = "Show on comments posted by suspended users."; 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."; } set text_comment_poster_is_suspended = "user is suspended"; property string text_comment_parent_entry { des = "Text for linking to a comment's parent."; } set text_comment_parent_entry = "parent entry"; } propgroup Miscellaneous { property string custom_favicon { des = "URL of custom favicon."; example = "http://example.com/favicon.ico"; } set custom_favicon = ""; property string default_view_mode { des = "Show entries expanded or collapsed by default. Currently this setting affects the reading page only."; values = "collapsed|Entries collapsed|expanded|Entries expanded"; } set default_view_mode = "expanded"; } # # Yes, tags are enabled. # set tags_aware = true; ################################################### # # # # !2. # Utility functions. # # # # ################################################### # Converts an associative array to an argument list: # # var string var = {"id" => "5", "page" => "b"}; # lay_array_to_args($var); # # "?id=5&page=b" # function lay_array_to_args(string{} items) : string "Converts an associative array to an argument list, i.e. {\"id\" => \"5\", \"page\" => \"b\"} => ?id=5&page=b" { var string args; var bool q = false; foreach var string key ($items) { if ($key != "") { if (not $q) { $args = "?"; $q = true; } else { $args = $args + "&"; } $args = $args + "$key=" + $items{"$key"}; } } return $args; } # pushes a string on to the end of an array, assuming that it's # indexed naturally from zero. # function lay_array_push(string[] input, string add) : string[] "Pushes a new element on to the end of an array." { $input[size $input] = $add; return $input; } # A bit like sprintf, this inserts an array of strings into a string. # Knows %s, literal %%, and numbered placeholders %1 .. %9. # function lay_string_placeholders( string format, string[] args ) : string "A bit like sprintf, this inserts an array of strings into a string. Handles %s, literal %%, and numbered placeholders %1 .. %9." { var string output = ""; var bool state_found_placeholder = false; var int found_count = 0; foreach var string s ($format) { if ( $state_found_placeholder ) { if ( $s == "%" ) { $output = $output + $s; } # string placeholder elseif ( $s == "s" ) { $output = $output + $args[$found_count]; $found_count++; $state_found_placeholder = false; } # numbered placeholder 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" ) { $output = $output + $args[int($s) - 1]; $state_found_placeholder = false; } } elseif ( $s == "%" ) { $state_found_placeholder = true; } else { $output = $output + $s; } } return $output; } # Returns the current url plus arguments. Needs to be overridden # on most views where it's used. # function Page::lay_build_url(string{} items) : string { return $.base_url + lay_array_to_args($items); } # For paid user override in theme layers function lay_print_extra_boxes() : void "Paid users can override this in theme layers to easily add content in the 'extra boxes' section of the footer." { } ################################################### # # # # !3. # Stylesheet. # # # # ################################################### function print_stylesheet() { """ html, body { margin: 0; padding: 0; font-family: Verdana, sans-serif; } /* regular links */ a { color: #2452FF; } a:visited { color: #142D8B; } a:active, a:hover { color: #178FFF; } img { border: 0px; } /* the main header */ #header { background: #eee; padding: 20px 10px 20px 10px; margin: 0px; } #header h1 { font: normal 4em Georgia, serif; color: #333; margin: 0px; padding: 40px 0 0 0; } #header p { color: #999; font: 1.2em normal Verdana, sans-serif; margin-top: 5px; } /* the navigation menu */ /* This had to be hacked up to work with IE and I haven't gotten around to cleaning it up yet. Sorry! */ #navi { float:left; width:100%; background: #fff; line-height:normal; font: normal 0.6em Verdana, sans-serif; color: #666; } #navi ul { margin:0; padding:0px 10px 0 5px; list-style:none; } #navi li { display:block; float:left; margin: 0 0 0 0; padding:0; text-align: center; border-top: 1px solid #bbb; } #navi span { float:left; display:block; padding:4px 12px 5px 10px; margin: 0 1px 0 1px; } #navi a { display: block; color: #666; text-decoration: none; background: #ddd; float: left; padding: 0; margin-right: 1px; border-bottom: 1px solid white; } #navi a:hover, #navi a:active { background: #888; color: #fff; } #navi li#tab-current { border-top: 1px solid #eee; } #navi li#tab-current a { display: inline; float: none; background: #eee; border: 0; margin: 0; } #navi li#tab-current span { background: #eee; border-bottom: 1px solid #eee; color: #555; } /* back-and-forward navigation */ .back-forward { width: 100%; float: left; clear: both; } .back-forward a, .back-forward a:visited { color: #999; text-decoration: none; } .back-forward a:active, .back-forward a:hover { color: #333; } .back-forward .back, .back-forward .forward { padding: 10px; font: normal 2em Verdana, sans-serif; } .back-forward .back { float: left; clear: left; } .back-forward .forward { float: right; clear: right; } /* global footer */ #footer { color: #999; font: 0.6em normal Verdana, sans-serif; margin: 0; text-align: right; padding: 10px 5px 5px 5px; background-color: #fff; clear: both; } .top-link { float: left; } /* extra boxes below main content */ .extra-box { float:left; width: 25%; margin: 20px; padding: 10px; } .extra-box > ul { list-style-type: square; margin: 0; padding: 2px 2px 2px 10px; } .extra-box .title { color: #3c0; font: normal 1.4em Verdana, sans-serif; } /* entries */ #entries { clear: both; margin: 10px; margin-left: 10px; padding: 10px; } .entry .left { text-align: center; float: left; width: 120px; padding-top: 10px; } .entry .right { margin-left: 150px; } /* ENTRY */ h2, h3 { color: #3c0; font: normal 2em Verdana, sans-serif; letter-spacing: -0.1em; margin: 0; padding: 0; display: inline; } .title a { color: #3c0; text-decoration: none; } .title a:visited { color: #2b0; } .title a:hover, .title a:active { color: #4d1; } /* shared entry and comments */ .comment-title { margin: 0; } .tools { text-align: center; padding: 10px; border: 1px solid #cde; background: #def; clear: both; } .frozen .tools { border: 1px solid #dee; background: #eff; } .screened .tools { border: 1px dashed #999; background: #fff; } .text { font-size: 90%; } .userpic { margin-bottom: 5px; } .userpic.empty { height: 100px; margin: 0 10px 5px 10px; border: 1px solid #eee; } /* Entries */ .entry { line-height: 1.3em; letter-spacing: 0.01em; margin: 10px 0 40px 0; } .entry .header { color: #999; padding: 0px 10px 10px 0; margin-bottom: 10px; } .entry .posted { margin-left: 5px; } .entry .datetime { margin-left: 20px; } .entry .security { margin: 0.5em; } .entry .meta { float: left; clear: both; padding: 5px; margin: 10px; font-size: 80%; color: #333; background-color: #def; border: 1px solid #cde; } .entry .links { color: #999; clear: both; } .entry .meta-label { font-weight: bold; } .new-day { margin: 2px 0 2px 150px; font: normal 1.4em Verdana, sans-serif; color: #666; } /* collapsed entries */ .collapsed-entry { margin-left: 130px; } .collapsed-entry .poster { font-weight: bold; font-size: 0.8em; } .expand { font: normal 1.4em Verdana, sans-serif; } .expand a, .expand a:visited { color: #ccc; text-decoration: none; } .expand a:hover, .expand a:active { color: #333; } .collapsed-entry .title { font: normal 1.2em Verdana, sans-serif; letter-spacing: -0.1em; margin: 0; padding: 0; display: inline; } /* Comments */ #comments { clear: both; margin: 10px; margin-left: 10px; padding: 10px; } .nest { margin-left: 20px; } .comment { line-height: 1.3em; letter-spacing: 0.01em; margin: 0; } .comment .left { text-align: center; float: left; padding: 5px; width: 120px; margin-top: 15px; } .comment .right { padding: 10px; margin-left: 130px; background: #fff; border-bottom: 1px solid #eee; } .comment h2 { color: #3c0; font: normal 1.3em Verdana, sans-serif; letter-spacing: -0.1em; margin: 0; padding: 0; display: inline; } .comment.odd { background: #fff; } .comment.even { background: #fff; } .comment .header { color: #999; padding: 10px 10px 10px 0; margin-bottom: 10px; } .comment .posted { margin-left: 5px; } .comment .datetime { margin-left: 20px; } .comment .icon { margin: 0.5em; } .comment .meta { float: left; padding: 5px; margin: 10px; font-size: 80%; color: #333; background-color: #def; border: 1px solid #cde; } .comment .links { color: #999; clear: both; } /* Collapsed comments */ .collapsed-comment { margin: 5px; } .collapsed-comment .title { font: normal 1.2em Verdana, sans-serif; letter-spacing: -0.1em; text-decoration: none; color: #3c0; } .collapsed-comment .poster { font-size: 0.8em; } .comment-pagination { clear: both; padding: 10px; } .entry-comments-bar { background: #eee; clear: both; padding: 10px; } .entry-comments-bar .comments-title { font: normal 1.5em Georgia, serif; color: #333; padding: 5px; letter-spacing: 0; display: block; } #multiform { font-size: 0.8em; margin: 10px; padding: 10px; border: 1px solid #cde; background: #def; } /* YearPage calendar */ #calendar { margin: 10px; padding: 5px; } #calendar .month { margin: 10px; float: left; } #calendar .header a { color: #3c0; text-decoration: none; } .month th.weekday { color: #333; } .month .cell { height: 3em; width: 3em; } .month .cell.full { background: #def; border: 1px solid #cde; } .month .cell.empty { border: 1px solid #eee; } .month .day { text-align: left; color: #999; font-size: 0.8em; } .month .cell.empty .day { color: #ddd; } .month .count { text-align: center; } .extra-box .month { font-size: 0.5em; } /* Comment quickreply */ .quickreply { padding: 5px; } .quickreply table { border: 0px !important; } .quickreply span.de { display: block; float: left; font-size: 0.7em; background: #def; padding: 5px; margin: 5px; border: 1px solid #cde; } .quickreply td[align="right"] { font-size: 0.8em; } /* TagsPage tag cloud */ #tag-cloud { margin: 10px; padding: 5px; } #tag-cloud a { color: #3c0; text-decoration: none; } .module-tags_cloud li, .tags_cloud li { display: inline; } /* IconsPage */ .icons-container { margin: 10px; padding: 10px; } .sorting-options ul { padding-left: 0; } .sorting-options ul li { display: inline; } .icons-container .icon { margin: 1em 0; } .icon-image { float: left; clear: left; margin-bottom: .25em; min-width: 100px; padding-right: 1em; } .icon-info { min-height: 100px; } .icon-info span { font-weight: bold; } .icon-info .default { text-decoration: underline; } .icon-keywords ul { display: inline; padding-left: 0; } .icon-keywords ul li { display: inline; } /* ReplyPage reply box */ #reply { margin: 10px 10px 10px 165px; padding: 5px; } #postform { background: #def; border: 1px solid #cde; padding: 5px; margin-top: 10px; font-size: 0.8em; } """; } ################################################### # # # # ~4. # EntryLite # # # # ################################################### # Shared methods used on/for both entries and comments. # # # Gets the entry or comment's link icons (freeze, add to memories etc.), # with the exception of the 'nav_prev' and 'nav_next' which are handled by # Page::lay_back_forward(). # function EntryLite::lay_get_linkbar() : string { var string o; var Link link; foreach var string k ($.link_keyseq) { if ( $k != "nav_prev" and $k != "nav_next" ) { $link = $this->get_link($k); if ( defined $link ) { $o = $o + $link->as_string(); } } } return $o; } # Returns a comma-separated string of tags. # function EntryLite::lay_get_tags() : string { var string tags = ""; if ($.tags) { foreach var int i (0 .. (size $.tags - 1)) { var Tag t = $.tags[$i]; $tags = $tags + """<a rel="tag" href="$t.url">$t.name</a>"""; if ( $i < size $.tags - 1 ) { $tags = $tags + ", "; } } } return $tags; } # Return a string representing the poster of this entry or comment. # function Page::lay_get_poster(EntryLite e) : string { if ( not defined $e.poster ) { return $*text_poster_anonymous; } elseif ( not $e.poster->equals($e.journal) and $.view == "read" ) { # default: "%1 in %2" return lay_string_placeholders( $*poster_in_journal, [$e.poster->as_string(), $e.journal->as_string()] ); } else { return $e.poster->as_string(); } } # Print a string containing any or all of the poster, date, time, tags # and ip address of this entry or comment. # function Page::lay_print_posted_by(EntryLite e) : void { var string format; var string[] args; var string tags; var string timeformat; if ($this.timeformat24) { $timeformat = $*posted_time_format_24; } else { $timeformat = $*posted_time_format; } # default: "posted by %1 at %2 on %3"; $format = $*posted_by_at_on; $args = [ $this->lay_get_poster($e), $e.time->date_format( $timeformat ), $e.time->date_format( $*posted_date_format ) ]; $tags = $e->lay_get_tags(); if ( $tags != "" ) { # default: "posted by %1 at %2 on %3 under %4"; $format = $*posted_by_at_on_in; lay_array_push($args, $tags); } elseif ($e.metadata{"poster_ip"}) { # default: "posted by %1 at %2 on %3 from %4"; $format = $*posted_by_at_on_from; lay_array_push($args, $e.metadata{"poster_ip"}); } print lay_string_placeholders( $format, $args ); } # Prints an entry or comment's text. # function Page::lay_print_text(EntryLite e) : void "Prints an entry or comment's text." { println """<div class="text">"""; $e->print_text(); println "</div>"; } ################################################### # # # # ~5. # CommentInfo # # # # ################################################### # Get details of an entry's comments. # show_read_link is included so EntryPage needn't show a link to itself. # function CommentInfo::lay_get_details(int pages, bool show_read_link) : string { var string link; if ($.count > 0) { if ( lang_map_plural($.count) ) { # "%1 comments" $link = lay_string_placeholders($*text_some_comments_link, [string($.count)]); if ( $show_read_link ) { $link = """<a href="$.read_url">$link</a>"""; } if ( $pages > 1 ) { $link = lay_string_placeholders($*text_some_comments_over_pages, [$link, string($pages)]); } else { $link = lay_string_placeholders($*text_some_comments, [$link]); } } else { # "%1 comment" $link = lay_string_placeholders($*text_a_comment_link, [string($.count)]); if ( $show_read_link ) { $link = """<a href="$.read_url">$link</a>"""; } $link = lay_string_placeholders($*text_a_comment, [$link]); } } else { # default: "There are no comments on this entry." $link = $*text_no_comments; } if (not $.enabled) { # default: "Comments are disabled." $link = $link + " " + $*text_comments_disabled; } return $link; } function Page::lay_print_comment_details(CommentInfo c) : void { print $c->lay_get_details(0, true); } # Prints a link to an entry's ReplyPage. # function Page::lay_print_entry_reply_link(CommentInfo c) : void { if ($c.show_postlink) { var string link = """<a href="$c.post_url">$*reply_link_link_text</a>"""; print " " + lay_string_placeholders( $*reply_link_text, [$link] ); } } ################################################### # # # # ~6. # Entry # # # # ################################################### function Page::lay_print_entry_linkbar(Entry e) { var string bar = $e->lay_get_linkbar(); if ( $bar == "" ) { return; } println """<div class="tools">$bar</div>"""; } function Page::lay_print_entry_meta(Entry e) : void { var string o = ""; var string caption; var string val; var Image i; if (size $e.metadata == 0) { return; } """ <div class="meta"> """; foreach var string k ($e.metadata) { $caption = $k; $val = $e.metadata{$k}; if ($k == "music") { $caption = $*text_meta_music; } elseif ($k == "mood") { $caption = $*text_meta_mood; if (defined $e.mood_icon) { $i = $e.mood_icon; $val = $i->as_string("'$e.metadata{$k}'")+" "+$val; } } """ <div class="meta-item"><span class="meta-label">$caption:</span> $val</div> """; } """ </div> """; } function Page::lay_print_entry_header(Entry e) { var string subject = ($e.subject != "" ? $e.subject : $*text_nosubject); """ <div class="header"> <div class="title"> <h2 id="entry-$e.itemid"><a href="$e.permalink_url">$subject</a></h2> """; if ($e.security != "") { print """<span class="security"><img src="$e.security_icon.url" alt="[$e.security]" /></span>"""; } """ </div> <div class="posted">"""; $this->lay_print_posted_by($e); """</div>"""; """</div>"""; } function Page::lay_print_entry_left(Entry e) : void { """ <div class="userpic"> """; if ($e.userpic) { print $e.userpic->as_string(); } """ </div> """; } function Page::lay_print_entry_footer(Entry e) { """ <div class="links"> """; $this->lay_print_comment_details( $e.comments ); $this->lay_print_entry_reply_link( $e.comments ); """ </div> """; } function Page::lay_print_entry(Entry e) { """ <div class="entry"> <div class="left"> """; $this->lay_print_entry_left($e); """ </div> <div class="right"> """; $this->lay_print_entry_header($e); $this->lay_print_text($e); $this->lay_print_entry_meta($e); $this->lay_print_entry_footer($e); """ </div> </div> """; } function Page::print_entry(Entry e) { $this->lay_print_entry($e); } function RecentPage::print_sticky_entry(StickyEntry s) { $this->lay_print_entry($s); } ################################################### # # # # ~6b. # Collapsed Entry # # # # ################################################### # MonthPage and FriendsPage by default show only a shortened version of an # entry. I'm considering the same thing for RecentPage past a threshold -- # one or two full entries followed by a longer list of previously-posted # titles. # # On FriendsPage these entries can be expanded in-place; on MonthPage # the entry text isn't populated so they can only link to the full entry. # # Print "expand" link. # function Page::lay_print_collapsed_entry_expand(Entry e) : void { var string expand_url = $this->lay_build_url({".id" => string($e.itemid)}) + "#entry-$e.itemid"; print """<span class="expand"><a href="$expand_url" title="Expand this entry.">+</a></span>"""; } # Entry title. # function Page::lay_print_collapsed_entry_title(Entry e) : void { var string subject = ($e.subject != "" ? $e.subject : $*text_nosubject); print """<span class="title"><a href="$e.permalink_url" title="View this entry.">$subject</a></span>"""; } # Entry security unless public. # function Page::lay_print_collapsed_entry_security(Entry e) : void { if ($e.security != "") { print """ <span class="security">""" + $e.security_icon->as_string() + "</span>"; } } # Entry comment count / "comments disabled" message. # function Page::lay_print_collapsed_entry_comments(Entry e) : void { var string count = ""; var string max = ""; var string screened = ""; if ($e.comments.count > 0) { if ($e.comments.maxcomments) { # default: "!" $max = $*collapsed_entry_comments_max_flag; } if ($e.comments.screened) { # default "*" $screened = $*collapsed_entry_comments_screened_flag; } # Assuming max and screened comments, default output is "(5000!*)" # Just screened comments is "(343*)" etc. $count = lay_string_placeholders( $*collapsed_entry_comments_count, [string($e.comments.count), $max, $screened] ); } elseif (not $e.comments.enabled) { $count = $*collapsed_entry_comments_disabled; } print """ <span class="comments">$count</span>"""; } # Entry poster. # function Page::lay_print_collapsed_entry_poster(Entry e) : void { if ( $.view == "read" or not $e.poster->equals($.journal as UserLite) ) { print """ — <span class="poster">""" + $this->lay_get_poster($e) + "</span>"; } } # Print the actual entry. # function Page::lay_print_collapsed_entry(Entry e) { println """<div class="collapsed-entry">"""; $this->lay_print_collapsed_entry_expand($e); $this->lay_print_collapsed_entry_title($e); $this->lay_print_collapsed_entry_security($e); $this->lay_print_collapsed_entry_comments($e); $this->lay_print_collapsed_entry_poster($e); println """</div>"""; } ################################################### # # # # ~7. # Page # # # # # # These methods do nothing, but are # # # overridden in child layers. # # # # ################################################### # Show the full version of an entry, or the collapsed # version? function Page::lay_entry_is_expanded(Entry e) : bool { return true; } # Some pages have extra content to print in the footer. # function Page::lay_print_extra_box() { return; } # Lay back-and-foward navigation. Between pages of # entries on RecentPage, between months on MonthPage, etc. # function Page::lay_back_forward() : void {} # Returns a combination of page title and view title; # only used in the `<title>` element of the HTML output. function Page::title() : string { var string title = $this.global_title; var string view = $this->view_title(); return lay_string_placeholders( $*text_html_title, [$title, $view] ); } # Prints an error page. # function Page::lay_print_errorpage(string message) { """ <div class="error"> <h2 class="error-header">$*text_errorpage_title</h2> <p>$message</p> </div> """; } # Print the current tab. Made separate so it can be overridden # in FriendsPage. # function Page::lay_print_navigation_current_tab() : void { println """<li id="tab-current"><span>""" + lang_viewname($.view) + "</span></li>"; } function Page::lay_navigation() { var string nav; var string alt; # Time to play "making up for S2's deficiencies"! # No link is supplied to the TagsPage yet. var string{} vu = $.view_url; var string[] vo = $.views_order; if ( $vu{"tags"} == "" ) { $vu{"tags"} = $.base_url + "/tag/"; $vo[size $vo] = "tags"; } """ <div id="navi"> <ul> """; foreach var string v ($vo) { if ($.view == $v) { $this->lay_print_navigation_current_tab(); } else { println """<li><a href="$vu{$v}"><span>""" + lang_viewname($v) + "</span></a></li>"; } } """ </ul> </div> """; } function Page::lay_print_extra_box_open(string title) : void { var string alt = alternate("odd", "even"); """ <div class="extra-box $alt"> <h2 class="title">$title</h3> """; } function Page::lay_print_extra_box_close() : void { print "</div>"; } # Prints a linklist. More complicated than it'd normally be because the # style splits a list with headings into multiple lists. Sub-lists are # not supported because they're not implemented in the core yet and show # no signs of ever being so. # function Page::print_linklist() { if ( size $.linklist == 0 ) { return; } var bool open = false; var UserLink l = $.linklist[0]; if (not $l.is_heading) { $this->lay_print_extra_box_open( $*linklist_default_title ); """ <ul class="linklist"> """; $open = true; } foreach var UserLink l ($.linklist) { if ($l.is_heading) { if ($open) { """ </ul> """; $this->lay_print_extra_box_close(); $open = false; } $this->lay_print_extra_box_open($l.title); """ <ul class="linklist"> """; $open = true; } else { """ <li><a href="$l.url">$l.title</a></li> """; } } if ($open) { """ </ul> """; $this->lay_print_extra_box_close(); } } # Print one week in a calendar month. # function Page::lay_print_week(YearWeek w) : void { """ <tr> """; if ($w.pre_empty > 0) { foreach var int i (1..$w.pre_empty) { """ <td class="blank cell"> </td> """; } } foreach var YearDay d ($w.days) { if ($d.num_entries > 0) { """ <td class="full cell"> <span class="day">$d.day</span> <div class="count"><a href="$d.url">$d.num_entries</a></div> </td> """; } else { """ <td class="empty cell"> <span class="day">$d.day</span> <div class="count"> </div> </td> """; } } if ($w.post_empty > 0) { foreach var int i (1..$w.post_empty) { """ <td class="blank-cell"> </td> """; } } """ </tr> """; } # Print a calendar month. # function Page::lay_print_month(YearMonth m) { """ <table summary="Monthly calendar with links to each day's entries" class="month"> <tr> """; foreach var int d (weekdays()) { """<th class="weekday">$*lang_dayname_short[$d]</th>"""; } """ </tr> """; foreach var YearWeek w ($m.weeks) { $this->lay_print_week($w); } """ </table> """; } function Page::lay_header() { """ <div id="header"> """; var string subtitle; if ($.global_subtitle != "") { $subtitle = $this.global_subtitle + ". " + $this->view_title() + "."; } else { $subtitle = $this->view_title() + "."; } """ <h1>$.global_title</h1> <p>$subtitle</p> </div> """; } function Page::lay_print_mini_calendar_box() { var YearMonth m = $this->get_latest_month(); if ( defined $m and $m.has_entries ) { $this->lay_print_extra_box_open( $m->month_format("%%month%%") ); $this->lay_print_month($m); $this->lay_print_extra_box_close(); } } function Page::lay_footer() { """ <div id="extra"> """; $this->lay_print_extra_box(); $this->print_linklist(); $this->lay_print_mini_calendar_box(); lay_print_extra_boxes(); """ </div> <div id="footer"> <p><span class="top-link"><a href="#header">$*top_link_text</a></span> <a href="https://www.dreamwidth.org/customize/advanced/layerbrowse.bml?id=zesty/layout">Zesty</a>. """; server_sig(); """</p> </div> """; } function Page::print() { """ <!DOCTYPE html> <html lang="en"> <head> <title>"""+$this->title()+""" """; $this->print_head(); """ """; if ($*custom_favicon != "") { """"""; } """ """; $this->print_control_strip(); $this->lay_header(); $this->lay_navigation(); $this->print_body(); $this->lay_footer(); """ """; } ################################################### # # # # ~8. # RecentPage # # # # ################################################### function RecentPage::lay_build_url(string{} items) : string { if ($.nav.skip != 0) { $items{"skip"} = string($.nav.skip); } return $.base_url + lay_array_to_args($items); } function RecentPage::lay_back_forward() : void { if ($.nav.backward_url == "" and $.nav.forward_url == "") { return; } """
"""; if ($.nav.backward_url != "") { var string previous = get_plural_phrase($.nav.backward_count, "text_skiplinks_back"); """
"""; } if ($.nav.forward_url != "") { var string next = get_plural_phrase($.nav.forward_count, "text_skiplinks_forward"); """
"""; } """
"""; } function RecentPage::print_body() { $this->lay_back_forward(); """
"""; if (size $.entries == 0) { $this->lay_print_errorpage($*text_noentries_recent); } else { foreach var Entry e ($.entries) { $this->print_entry($e); } } """
"""; $this->lay_back_forward(); } ################################################### # # # # ~9. # FriendsPage # # # # ################################################### function FriendsPage::lay_build_url(string{} items) : string { var string url = $.base_url; # Page might be "friendsfriends". # if ($.friends_mode != "") { $url = $url + "/$.friends_mode"; } else { $url = $url + "/read"; } # Page might be a friends group. # if ($.filter_active) { $url = $url + "/$.filter_name"; } if ($.nav.skip != 0) { $items{"skip"} = string($.nav.skip); } return $url + lay_array_to_args($items); } # Is the default view mode for entries "expanded" or "collapsed"? # Currently only actually used on the FriendsPage. # function FriendsPage::lay_get_current_view_mode() : string { var string mode = $*default_view_mode; if ($.args{"mode"} != "" and $mode != $.args{"mode"}) { $mode = $.args{"mode"}; } return $mode; } # Returns the opposite of the current view mode for entries. # function FriendsPage::lay_get_alternate_view_mode() : string { var string current = $this->lay_get_current_view_mode(); if ( $current == "expanded" ) { return "collapsed"; } else { return "expanded"; } } function FriendsPage::lay_entry_is_expanded(Entry e) : bool { var bool expanded = false; # We expand entries if they match the `.id=$entry_id` argument, but # there's a problem in that the item id isn't necessarily unique. # Because LJ uses (internally) a composite key of user id and item id, # two different users' posts on the same page could have the same item id. # # I mention it mostly out of interest, since I'm not going to do anything # to stop it happening. # # It's vanishingly unlikely, S2 doesn't expose the user ID (and the # username isn't necessarily unique *or* safe), and even if there's a # clash, what's the damage? But it might still happen at some point. # # It's not quite as simple as the birthday paradox, because low item ids # are exponentially more likely to appear than high ones, and people who # joined the site at the same time and have similar posting habits are # quite likely to stay in the same range of item ids. # # This comment was much too long. Sorry! # if ($.args{"id"} != "" and int($.args{"id"}) == $e.itemid) { $expanded = true; } elseif ($this->lay_get_current_view_mode() == "expanded") { $expanded = true; } return $expanded; } # The FriendsPage tab has an extra mode-switching button on it. # function FriendsPage::lay_print_navigation_current_tab() : void { var string alt = $this->lay_build_url( {".mode" => $this->lay_get_alternate_view_mode()} ); println """
  • """ + lang_viewname($.view) + """ +
  • """; } function FriendsPage::print_entry(Entry e) { if ($e.new_day) { print """
    """ + $e.time->date_format($*lang_fmt_date_long) + "
    "; } if ( $this->lay_entry_is_expanded($e) ) { $this->lay_print_entry($e); } else { $this->lay_print_collapsed_entry($e); } } ################################################### # # # # ~10. # DayPage # # # # ################################################### function DayPage::lay_back_forward() : void { if ($.prev_url == "" and $.next_url == "") { return; } """
    """; if ($.prev_url != "") { """
    """; } if ($.next_url != "") { """
    """; } """
    """; } function DayPage::print_body() { if (not $.has_entries) { $this->lay_print_errorpage($*text_noentries_day); } else { foreach var Entry e ($.entries) { $this->print_entry($e); } } } ################################################### # # # # ~11. # MonthPage # # # # ################################################### # Can't expand MonthPage entries. Bah humbug. # function MonthPage::lay_print_collapsed_expand(Entry e) : void { return; } function MonthPage::lay_back_forward() : void { if ($.prev_url == "" and $.next_url == "") { return; } """
    """; if ($.prev_url != "") { """
    """; } if ($.next_url != "") { """
    """; } """
    """; } # Can't get entry text in this view, so no full entries possible. # function MonthPage::print_entry(Entry e) : void { return $this->lay_print_collapsed_entry($e); } # Print a box containing information about other linkable months. # function MonthPage::lay_print_extra_box() : void { if (size $.months == 0) { return; } """

    $.date.year

    """; } function MonthPage::print_body { var bool any = false; $this->lay_back_forward(); """
    """; foreach var MonthDay d ($.days) { if ($d.has_entries) { print """
    """ + $d.date->date_format($*lang_fmt_date_long) + "
    "; foreach var Entry e ($d.entries) { $this->print_entry($e); } $any = true; } } """
    """; if ( not $any ) { # default: "No entries were posted on the selected month." return $this->lay_print_errorpage( $*error_monthpage_no_entries ); } $this->lay_back_forward(); } ################################################### # # # # ~12. # EntryPage # # # # ################################################### # TODO: this is broken in the core. # Waiting on http://rt.livejournal.org/Ticket/Display.html?id=1266 . # function EntryPage::lay_comment_poster_is_suspended(Comment c) : bool { return $.viewing_thread and not $c.full and $c.depth == 1; } function EntryPage::lay_print_comment_details(CommentInfo c) : void { print $c->lay_get_details($.comment_pages.total, false); } # "Ideally layouts should never override this"... well how about you # actually make it work on all views, then? function EntryPage::view_title() : string { var string subject = ($.entry.subject != "" ? $.entry.subject : $*text_nosubject); if ( $.viewing_thread ) { $subject = lay_string_placeholders( "%1 : comments", [$subject] ); } return $subject; } function EntryPage::lay_back_forward() : void { var Link prev = $.entry->get_link("nav_prev"); var Link next = $.entry->get_link("nav_next"); if ( isnull $prev and isnull $next ) { return; } """
    """; if ( defined $prev ) { """
    """; } if ( defined $next ) { """
    """; } """
    """; } function EntryPage::lay_print_comment_linkbar(Comment c) "Same as Page::lay_print_entry_linkbar except that it also prints the multiform checkbox if the multiform is on. " { var string bar = $c->lay_get_linkbar(); if ( $bar == "" and not $.multiform_on ) { return; } print """
    $bar"""; if ($.multiform_on) { $c->print_multiform_check(); } print "
    "; } function EntryPage::lay_print_comment_links(Comment c) : void { println """"""; } function EntryPage::print_comment(Comment c) : void { var string class = "comment dwexpcomment " + alternate("odd", "even"); var string subject = ($c.subject == "") ? $*text_nosubject : $c.subject; var string state = "state"; if ($c.frozen) { $state = "frozen"; } elseif ($c.screened) { $state = "screened"; } println """
    """; # This "state" div is a dodgy hack for the JavaScript set_handler stuff. # It'll be changed to "frozen", "screened" etc. if the quick-change buttons # are used. # No apologies. :P """
    """; if ($c.userpic) { """
    """; print $c.userpic->as_string(); """
    """; } else { """
     
    """; } $this->lay_print_comment_linkbar($c); """

    $subject

    """; if (defined $c.subject_icon) { print """""" + $c.subject_icon->as_string() + ""; } """
    """; $this->lay_print_posted_by($c); """
    """; $this->lay_print_text($c); $this->lay_print_comment_links($c); """
    """; $c->print_reply_container({"class" => "quickreply"}); """
    """; $this->print_comments($c.replies); """
    """; } function EntryPage::print_comment_partial(Comment c) { var string poster = defined $c.poster ? $c.poster->as_string() : $*text_poster_anonymous; var string subject = $c.subject != "" ? $c.subject : $*text_nosubject; """
    """; if ( $c.deleted ) { """(deleted comment)"""; } elseif ( $c.screened ) { """ $subject$poster [screened] """; } else { """ $subject$poster """; } $this->print_comments($c.replies); """
    """; } function EntryPage::print_comments(Comment[] comments) { if (size $comments == 0) { return; } foreach var Comment c ($comments) { if ($c.full) { $this->print_comment($c); } # special case for suspended comments. elseif ( $this->lay_comment_poster_is_suspended($c) ) { $this->print_comment($c); } else { $this->print_comment_partial($c); } } } function EntryPage::lay_print_entry_left(Entry e) : void { """
    """; if ($e.userpic) { print $e.userpic->as_string(); } """
    """; $this->lay_print_entry_linkbar($.entry); } function EntryPage::lay_print_entry_footer(Entry e) { return; } function EntryPage::print_entry(Entry e) { $this->lay_print_entry($e); } function EntryPage::lay_comment_pagination() : void { # no comments if ($.entry.comments.count == 0) { return; } var ItemRange range = $.comment_pages; # only one page of comments if ($range.all_subitems_displayed) { return; } """
    """; if ( $range.url_last != "" ) { """"""; } foreach var int page (1 .. $range.total) { if ($range.current != $page) { """ $page """; } else { """ $page """; } } if ( $range.url_next != "" ) { """"""; } """
    """; } function EntryPage::lay_print_comments() : void { if ( $.entry.comments.enabled and size $.comments > 0 ) { # JavaScript voodoo. # # set_handler("screen_comment_#", [ [ "set_class", "state#", "screened" ] ]); set_handler("freeze_comment_#", [ [ "set_class", "state#", "frozen" ] ]); set_handler("unscreen_comment_#", [ [ "set_class", "state#", "state" ] ]); set_handler("unfreeze_comment_#", [ [ "set_class", "state#", "state" ] ]); """
    """; if ($.multiform_on) { $this->print_multiform_start(); } $this->print_comments($.comments); if ($.multiform_on) { """
    """; $this->print_multiform_actionline(); $this->print_multiform_end(); """
    """; } """
    """; } } function EntryPage::lay_print_entry_comments_bar() : void { """
    """; $this->lay_print_comment_details( $.entry.comments ); $this->lay_print_entry_reply_link( $.entry.comments ); """ """; $this->lay_comment_pagination(); """
    """; } function EntryPage::print_body() : void { if ( $.viewing_thread ) { return $this->lay_print_comments(); } $this->lay_back_forward(); """
    """; $this->print_entry($.entry); """
    """; $this->lay_back_forward(); $this->lay_print_entry_comments_bar(); $this->lay_print_comments(); # Show comment pagination again if necessary. if ( $.comment_pages.total > 1 ) { $this->lay_print_entry_comments_bar(); } } ################################################### # # # # ~13. # ReplyPage # # # # ################################################### function ReplyPage::lay_print_comment(EntryLite e) { var string subject = ($e.subject != "" ? $e.subject : $*text_nosubject); """
    """; if ($e.userpic) { print $e.userpic->as_string(); } """
    """; $this->lay_print_posted_by($e); """
    """; $this->lay_print_text($e); """
    """; } function ReplyPage::print_body() : void { # replying to a comment, not an entry. # """
    """; # replying to an entry? if ( $this.replyto.depth == 0 ) { $this->print_entry($.entry); } # no, it's a comment else { $this->lay_print_comment($.replyto); } """

    Reply

    """; $.form->print(); """
    """; } ################################################### # # # # ~14. # YearPage # # # # ################################################### # Prints a list of other linkable years. # function YearPage::lay_print_extra_box() { var string year; if (size $.years < 2) { return; } """

    Years

    """; } function YearPage::lay_back_forward() : void { if (size $.years < 2) { return; } var YearYear next; var YearYear last; foreach var YearYear y ($.years) { if ( $y.year == $.year - 1 ) { $last = $y; } elseif ( $y.year == $.year + 1 ) { $next = $y; } } """
    """; if ( defined $last ) { """
    """; } if ( defined $next ) { """
    """; } """
    """; } function YearPage::print_month(YearMonth m) { """

    """+$m->month_format("%%month%%")+"""

    """; $this->lay_print_month($m); """
    """; } function YearPage::print_body { if ( size $.months == 0 ) { return $this->lay_print_errorpage( $*error_yearpage_no_entries ); } $this->lay_back_forward(); """
    """; foreach var YearMonth m ($.months) { if ($m.has_entries) { $this->print_month($m); } } """
    """; $this->lay_back_forward(); } ################################################### # # # # ~15. # MessagePage # # # # # # Just a stub. AFAICT it's not used in the # # # core yet, so I can't test it. # # # # ################################################### function MessagePage::print_body() { """

    """; $this->print_message(); """

    """; } ################################################### # # # # ~16. # TagsPage # # # # ################################################### # Weighted tag cloud / heatmap. # function TagsPage::print_body() { # since there is no heading, make invisible one here for # screenreaders """
    """; }