# -*-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 + """$t.name""";
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 """
""";
$e->print_text();
println "
";
}
###################################################
# # #
# ~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 = """$link""";
}
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 = """$link""";
}
$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 = """$*reply_link_link_text""";
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 """
$bar
""";
}
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;
}
"""
""";
}
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 """+""";
}
# Entry title.
#
function Page::lay_print_collapsed_entry_title(Entry e) : void {
var string subject = ($e.subject != "" ? $e.subject : $*text_nosubject);
print """$subject""";
}
# Entry security unless public.
#
function Page::lay_print_collapsed_entry_security(Entry e) : void {
if ($e.security != "") {
print """ """ + $e.security_icon->as_string() + "";
}
}
# 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 """ $count""";
}
# Entry poster.
#
function Page::lay_print_collapsed_entry_poster(Entry e) : void {
if ( $.view == "read" or not $e.poster->equals($.journal as UserLite) ) {
print """ — """ + $this->lay_get_poster($e) + "";
}
}
# Print the actual entry.
#
function Page::lay_print_collapsed_entry(Entry e) {
println """
""";
}
###################################################
# # #
# ~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 `` 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) {
"""
$*text_errorpage_title
$message
""";
}
# Print the current tab. Made separate so it can be overridden
# in FriendsPage.
#
function Page::lay_print_navigation_current_tab() : void {
println """
""" + lang_viewname($.view) + "
";
}
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";
}
"""
""";
foreach var string v ($vo) {
if ($.view == $v) {
$this->lay_print_navigation_current_tab();
} else {
println """
""";
}
function Page::lay_print_extra_box_open(string title) : void {
var string alt = alternate("odd", "even");
"""
$title
""";
}
function Page::lay_print_extra_box_close() : void {
print "
";
}
# 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 );
"""
""";
$open = true;
}
foreach var UserLink l ($.linklist) {
if ($l.is_heading) {
if ($open) {
"""
""";
}
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() {
"""
""";
}
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 """
""";
}
# 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
""";
foreach var MonthEntryInfo m ($.months) {
if ($.date.year == $m.date.year) {
println """
";
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;
}
"""
""";
}
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
"""
""";
}
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 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
""";
foreach var YearYear y ($.years) {
if ($y.displayed) {
$year = string($y.year);
} else {
$year = """$y.year""";
}
println """
$year
""";
}
"""
""";
}
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;
}
}
"""
""";
}
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
"""
Visible tags
""";
# font min and max as % values
var int fontmin = 80;
var int fontmax = 400;
var int fontspread = $fontmax - $fontmin;
var int fontstep = 0;
# set later
var int countspread;
var int fontsize;
var string font;
var int highest = 0;
var int lowest = 999999;
var int count;
foreach var TagDetail tag ($.tags) {
if ($tag.use_count > $highest) {
$highest = $tag.use_count;
}
if ($tag.use_count < $lowest) {
$lowest = $tag.use_count;
}
}
$countspread = $highest - $lowest;
if ($countspread > 0) {
$fontstep = $fontspread/$countspread;
}
foreach var TagDetail tag ($.tags) {
if ($highest == $lowest) {
$font = string($fontmin) + "%";
} else {
$fontsize = $fontmin + (($tag.use_count - $lowest) * $fontstep);
$font = string($fontsize) + "%";
}
"""