mourningdove/styles/zesty/layout.s2

2797 lines
68 KiB
Text
Raw Normal View History

2026-05-24 01:03:05 +00:00
# -*-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;
property use medium_breakpoint_width;
property use large_breakpoint_width;
}
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 <a href="...">2 comments</a> 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 <a href="%1">%2 comment</a> 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 <title> 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 + "&amp;";
}
$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() {
var string medium_media_query = generate_medium_media_query();
var string large_media_query = generate_large_media_query();
"""
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-weight: normal;
font-family: Georgia, serif;
font-size: 2.5em;
color: #333;
margin: 0px;
padding: 20px 0 0 0;
}
#header p {
color: #999;
font: 1.2em normal Verdana, sans-serif;
margin-top: 5px;
}
@media $medium_media_query {
font-size: 4em;
padding: 40px 0 0 0;
}
/* 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 {
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;
}
@media $medium_media_query {
.extra-box {
float:left;
width: 25%;
}
}
/* entries */
#entries {
clear: both;
margin: 10px;
margin-left: 10px;
padding: 10px;
}
.entry .left {
text-align: center;
float: left;
width: 80px;
padding-top: 10px;
}
.entry .right {
margin-left: 80px;
}
@media $medium_media_query {
.entry .left {
width: 120px;
}
.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;
width: 80%;
}
.userpic img {
width: 100%;
height: 100%;
}
.userpic.empty {
width: calc(100% - 20px);
padding-top: calc(100% - 20px);
margin-bottom: 10px;
border: 1px solid #eee;
}
@media $medium_media_query {
.userpic {
width: auto;
}
}
/* 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 {
var string userpic_class = ($e.userpic ? "userpic" : "userpic empty");
"""
<div class="$userpic_class">
""";
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 """ &mdash; <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">&nbsp;</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">&nbsp;</div>
</td>
""";
}
}
if ($w.post_empty > 0) {
foreach var int i (1..$w.post_empty) {
"""
<td class="blank-cell">&nbsp;</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()+"""</title>
""";
$this->print_meta_tags();
$this->print_head();
"""
<link rel="stylesheet" type="text/css" href="$.stylesheet_url" />
""";
if ($*custom_favicon != "") {
"""<link rel="shortcut icon" href="$*custom_favicon" />""";
}
"""
</head>
<body class="$.journal.username-$.view">
""";
$this->print_control_strip();
$this->lay_header();
$this->lay_navigation();
$this->print_body();
$this->lay_footer();
"""
</body>
</html>
""";
}
###################################################
# # #
# ~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;
}
"""
<div class="back-forward">
""";
if ($.nav.backward_url != "") {
var string previous = get_plural_phrase($.nav.backward_count, "text_skiplinks_back");
"""
<div class="back">
<a href="$.nav.backward_url" title="$previous">&larr;</a>
</div>
""";
}
if ($.nav.forward_url != "") {
var string next = get_plural_phrase($.nav.forward_count, "text_skiplinks_forward");
"""
<div class="forward">
<a href="$.nav.forward_url" title="$next">&rarr;</a>
</div>
""";
}
"""
</div>
""";
}
function RecentPage::print_body() {
$this->lay_back_forward();
"""
<div id="entries">
""";
if (size $.entries == 0) {
$this->lay_print_errorpage($*text_noentries_recent);
} else {
foreach var Entry e ($.entries) {
$this->print_entry($e);
}
}
"""
</div>
""";
$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 """<li id="tab-current"><span>""" + lang_viewname($.view) + """ <a href="$alt">+</a></span></li>""";
}
function FriendsPage::print_entry(Entry e) {
if ($e.new_day) {
print """<div class="new-day">""" + $e.time->date_format($*lang_fmt_date_long) + "</div>";
}
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;
}
"""
<div class="back-forward">
""";
if ($.prev_url != "") {
"""
<div class="back">
<a href="$.prev_url" title="$*text_day_prev">&larr;</a>
</div>
""";
}
if ($.next_url != "") {
"""
<div class="forward">
<a href="$.next_url" title="$*text_day_next">&rarr;</a>
</div>
""";
}
"""
</div>
""";
}
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;
}
"""
<div class="back-forward">
""";
if ($.prev_url != "") {
"""
<div class="back">
<a href="$.prev_url" title="Previous day.">&larr;</a>
</div>
""";
}
if ($.next_url != "") {
"""
<div class="forward">
<a href="$.next_url" title="Next day.">&rarr;</a>
</div>
""";
}
"""
</div>
""";
}
# 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;
}
"""
<div class="extra-box">
<h3 class="title">$.date.year</h3>
<ul>
""";
foreach var MonthEntryInfo m ($.months) {
if ($.date.year == $m.date.year) {
println """<li><a href="$m.url">"""+$m.date->date_format("%%month%%")+"</a></li>";
}
}
"""
</ul>
</div>
""";
}
function MonthPage::print_body {
var bool any = false;
$this->lay_back_forward();
"""
<div id="entries">
""";
foreach var MonthDay d ($.days) {
if ($d.has_entries) {
print """<div class="new-day">""" + $d.date->date_format($*lang_fmt_date_long) + "</div>";
foreach var Entry e ($d.entries) {
$this->print_entry($e);
}
$any = true;
}
}
"""
</div>
""";
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;
}
"""
<div class="back-forward">
""";
if ( defined $prev ) {
"""
<div class="back">
<a href="$prev.url" title="$prev.caption">&larr;</a>
</div>
""";
}
if ( defined $next ) {
"""
<div class="forward">
<a href="$next.url" title="$next.caption">&rarr;</a>
</div>
""";
}
"""
</div>
""";
}
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 """<div class="tools" id="tools$c.talkid">$bar""";
if ($.multiform_on) {
$c->print_multiform_check();
}
print "</div>";
}
function EntryPage::lay_print_comment_links(Comment c) : void {
println """<div class="links">""";
if ( $.viewing_thread and $c.depth == 1 ) {
"""[<a href="$.entry.permalink_url">$*text_comment_parent_entry</a>] """;
}
"""[<a href="$c.permalink_url">$*text_comment_permalink</a>] """;
if ( $c.parent_url != "" ) {
"""[<a href="$c.parent_url">$*text_comment_parent</a>] """;
}
if ( $c.frozen ) {
"""[$*text_comment_frozen]""";
} elseif ( $this->lay_comment_poster_is_suspended($c) ) {
"""[$*text_comment_poster_is_suspended]""";
} else {
"""["""; $c->print_reply_link({"linktext" => $*text_comment_reply}); """]""";
}
println """</div>""";
}
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 """<div class="nest">""";
# 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
"""
<div class="$state" id="state$c.talkid">
<div class="$class" id="$c.dom_id">
<div class="left">
""";
if ($c.userpic) {
"""<div class="userpic">""";
print $c.userpic->as_string();
"""</div>""";
} else {
"""<div class="userpic empty">&nbsp;</div>""";
}
$this->lay_print_comment_linkbar($c);
"""
</div>
<div class="right">
<div class="header">
<div class="title">
<h3 id="t$c.talkid"><a href="$c.permalink_url">$subject</a></h2>
""";
if (defined $c.subject_icon) {
print """<span class="icon">""" + $c.subject_icon->as_string() + "</span>";
}
"""
</div>
<div class="posted">"""; $this->lay_print_posted_by($c); """</div>
</div>
""";
$this->lay_print_text($c);
$this->lay_print_comment_links($c);
"""
</div>
</div>
</div>
""";
$c->print_reply_container({"class" => "quickreply"});
"""
<div id="c-reply-$c.talkid"></div>
""";
$this->print_comments($c.replies);
"""</div>""";
}
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;
"""
<div class="nest">
<div class="collapsed-comment">
""";
if ( $c.deleted ) {
"""<span class="title">(deleted comment)</span>""";
} elseif ( $c.screened ) {
"""
<a class="title" href="$c.permalink_url">$subject</a> &mdash; <span class="poster">$poster</span> [screened]
""";
} else {
"""
<a class="title" href="$c.permalink_url">$subject</a> &mdash; <span class="poster">$poster</span>
""";
}
$this->print_comments($c.replies);
"""
</div>
</div>
""";
}
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 {
"""
<div class="userpic
""";
if ($e.userpic) {
print " empty";
}
"""
">
""";
if ($e.userpic) {
print $e.userpic->as_string();
}
"""
</div>
""";
$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;
}
"""
<div class="comment-pagination">
""";
if ( $range.url_last != "" ) {
"""<a href="$range.url_last">&larr;</a>""";
}
foreach var int page (1 .. $range.total) {
if ($range.current != $page) {
""" <a href='""" + $range->url_of($page) + """'>$page</a> """;
} else {
""" $page """;
}
}
if ( $range.url_next != "" ) {
"""<a href="$range.url_next">&rarr;</a>""";
}
"""
</div>
""";
}
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" ]
]);
"""
<div id="comments">
""";
if ($.multiform_on) {
$this->print_multiform_start();
}
$this->print_comments($.comments);
if ($.multiform_on) {
"""
<div id="multiform">
""";
$this->print_multiform_actionline();
$this->print_multiform_end();
"""
</div>
""";
}
"""
</div>
""";
}
}
function EntryPage::lay_print_entry_comments_bar() : void {
"""
<div class="entry-comments-bar">
<span class="comments-title">""";
$this->lay_print_comment_details( $.entry.comments );
$this->lay_print_entry_reply_link( $.entry.comments );
"""</span>
"""; $this->lay_comment_pagination(); """
</div>
""";
}
function EntryPage::print_body() : void {
if ( $.viewing_thread ) {
return $this->lay_print_comments();
}
$this->lay_back_forward();
"""
<div id="entries">
""";
$this->print_entry($.entry);
"""
</div>
""";
$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);
"""
<div class="comment">
<div class="left">
<div class="userpic">
""";
if ($e.userpic) {
print $e.userpic->as_string();
}
"""
</div>
</div>
<div class="right">
<div class="header">
<div class="title">
<h2><a href="$e.permalink_url">$subject</a></h2>
</div>
<div class="posted">"""; $this->lay_print_posted_by($e); """</div>
</div>
""";
$this->lay_print_text($e);
"""
<div class="links">[<a href="$.entry.permalink_url">parent entry</a>] [<a href="$e.permalink_url">$*text_permalink</a>]</div>
</div>
</div>
""";
}
function ReplyPage::print_body() : void {
# replying to a comment, not an entry.
#
"""
<div id="entries">
""";
# replying to an entry?
if ( $this.replyto.depth == 0 ) {
$this->print_entry($.entry);
}
# no, it's a comment
else {
$this->lay_print_comment($.replyto);
}
"""
</div>
<div id="reply">
<h2>Reply</h2>
""";
$.form->print();
"""
</div>
""";
}
###################################################
# # #
# ~14. # YearPage #
# # #
###################################################
# Prints a list of other linkable years.
#
function YearPage::lay_print_extra_box() {
var string year;
if (size $.years < 2) {
return;
}
"""
<div class="extra-box">
<h3 class="title">Years</h3>
<ul>
""";
foreach var YearYear y ($.years) {
if ($y.displayed) {
$year = string($y.year);
} else {
$year = """<a href="$y.url">$y.year</a>""";
}
println """<li>$year</li>""";
}
"""
</ul>
</div>
""";
}
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;
}
}
"""
<div class="back-forward">
""";
if ( defined $last ) {
"""
<div class="back">
<a href="$last.url" title="Previous year.">&larr;</a>
</div>
""";
}
if ( defined $next ) {
"""
<div class="forward">
<a href="$next.url" title="Next year.">&rarr;</a>
</div>
""";
}
"""
</div>
""";
}
function YearPage::print_month(YearMonth m) {
"""
<div class="calendar-month">
<h2 class="title"><a href="$m.url">"""+$m->month_format("%%month%%")+"""</a></h2>
""";
$this->lay_print_month($m);
"""
</div>
""";
}
function YearPage::print_body {
if ( size $.months == 0 ) {
return $this->lay_print_errorpage( $*error_yearpage_no_entries );
}
$this->lay_back_forward();
"""
<div id="calendar">
""";
foreach var YearMonth m ($.months) {
if ($m.has_entries) {
$this->print_month($m);
}
}
"""
</div>
""";
$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() {
"""
<div id="message">
<p>"""; $this->print_message(); """</p>
</div>
""";
}
###################################################
# # #
# ~16. # TagsPage #
# # #
###################################################
# Weighted tag cloud / heatmap.
#
function TagsPage::print_body() {
# since there is no heading, make invisible one here for
# screenreaders
"""
<div id="tag-cloud" class="tags_cloud">
<h2 class="invisible">Visible tags</h2>
<ul>
""";
# 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) + "%";
}
"""
<li>
<a rel="tag" class="used-$tag.use_count visible-to-$tag.visibility" href="$tag.url" style="font-size: $font;">$tag.name</a>
<span class ="invisible"> used $tag.use_count times</span></li>
""";
}
"""
</ul></div>
""";
}