S2 Syntax Test

From Dreamwidth Notes
Jump to: navigation, search
## -*-s2-*-

##[ layerinfo ]

layerinfo "type" = "core";
layerinfo "name" = "Dreamwidth S2 Core, v2";
layerinfo "redist_uniq" = "core2";
layerinfo "majorversion" = "2";
layerinfo "author_name" = "Dreamwidth Webmaster";
layerinfo "author_email" = "webmaster@dreamwidth.org";
 
##[ S2 core classes ]

class int
"An integer number.  This isn't really a class, as suggested by its lower-case name.  Parameters of type int pass by value, unlike all variables of real object types, which pass by reference.  Instead, this is just a pseudo-class which provides convenience methods on instances of integers.  The other pseudo-class is [class[string]]."
{
    function builtin zeropad(int digits) : string
    "Return the integer as a string formatted at least \$digits characters long, left-padded with zeroes.";
 
    function builtin compare(int n) : int
    "Compare one integer with another. Returns a negative number if n is less than the subject, positive if greater or zero if the two are numerically equal.";
}
 
class string
"A series of characters.  This isn't really a class, as suggested by its lower-case name.  Parameters of type string pass by value, unlike all variables of real object types, which pass by reference.  Instead, this is just a pseudo-class which provides convenience methods on instances of strings.  The other pseudo-class is [class[int]]."
{
    function builtin index(string sub) : int
    "Returns the index of the first instance of \$sub in string.";
 
    function builtin index(string sub, int position) : int
    "Returns the index of the first instance of \$sub in string, starting from \$position";
 
    function builtin substr(int start, int length) : string
    "Returns up to \$length characters from string, skipping \$start characters from the beginning.";
 
    function builtin ends_with (string sub) : bool
    "Returns true if string ends in \$sub";
 
    function builtin starts_with (string sub) : bool
    "Returns true if string begins with \$sub";
 
    function builtin contains (string sub) : bool
    "Return true if string contains \$sub";
 
    function builtin replace(string find, string replace) : string
    "Replace all instances of \$find with \$replace. Case sensitive."; 
 
    function builtin split (string sub) : string[]
    "Return an array of strings split by \$sub";
 
    function builtin lower : string
    "Returns string in lower case.";
 
    function builtin upper : string
    "Returns string in upper case";
 
    function builtin upperfirst : string
    "Return string with the first character capitalized.";
 
    function builtin length() : int
    "Return the number of characters in the string.";
 
    function builtin repeat(int n) : string
    "Returns the string repeated n times";
 
    function builtin compare(string s) : int
    "Compare one string with another. Returns a negative number if n is alphabetically before the subject, positive if greater or zero if the two are equal. Note that this function currently does a simple ASCII compare, not a proper unicode-aware sort.";
 
    function builtin css_string() : string
    "Returns the string escaped and quoted as a CSS string literal, safe for insertion into a stylesheet.";
 
    function builtin css_keyword() : string
    "If the string is syntactically valid as a CSS keyword (only letters and spaces) returns it, else returns an empty string.";
 
    function builtin css_keyword_list() : string
    "Analyses the string as a space-separated list of CSS keywords and returns a string containing the items that are syntactically acceptable.";
 
    function builtin css_keyword(string[] allowed) : string
    "Same as [method[string.css_keyword()]] except also imposes a whitelist of valid keywords given in \$allowed.";
 
    function builtin css_keyword_list(string[] allowed) : string
    "Same as [method[string.css_keyword_list()]] except also imposes a whitelist of valid keywords given in \$allowed.";
 
    function builtin css_length_value() : string
    "If the string contains a valid CSS length value, returns a canonical version. Else returns an empty string.";
 
    function builtin css_url_value : string
    "If the string contains a valid HTTP or HTTPS URL it is returned. Otherwise, an empty string is returned.";
 
}
 
class Color
"Represents a color."
{
  var readonly int r "Red value, 0-255.";
  var readonly int g "Green value, 0-255.";
  var readonly int b "Blue value, 0-255.";
  var  string as_string "HTML hex encoded: #rrggbb";
  function builtin Color(string s) : Color "Constructor for color class.  Lets you make a Color object from a string of form #rrggbb";
  function builtin set_hsl (int h, int s, int v) "Set the HSL value for a color class.";
 
  function builtin red(int r) "Set the red value. (0-255)";
  function builtin green(int g) "Set the green value. (0-255)";
  function builtin blue(int b) "Set the blue value. (0-255)";
  function builtin red() : int "Get the red value.";
  function builtin green() : int "Get the green value.";
  function builtin blue() : int "Get the blue value.";
 
  function builtin hue(int h) "Set the hue value. (0-255)";
  function builtin saturation(int s) "Set the saturation value. (0-255)";
  function builtin lightness(int v) "Set the lightness value. (0-255)";
  function builtin hue() : int "Get the hue value. (0-255)";
  function builtin saturation() : int "Get the saturation value. (0-255)";
  function builtin lightness() : int "Get the lightness value. (0-255)";
 
  function builtin clone() : Color "Returns identical color.";
  function builtin lighter() : Color "Returns a new color with lightness increased by 30.";
  function builtin lighter(int amt) : Color "Returns a new color with lightness increased by amount given.";
  function builtin darker() : Color "Returns a new color with lightness decreased by 30.";
  function builtin darker(int amt) : Color "Returns a new color with lightness decreased by amount given.";
  function builtin inverse() : Color "Returns inverse of color.";
  function builtin average(Color other) : Color "Returns color averaged with \$other color.";
  function builtin blend(Color other, int value) : Color "Returns color blended with \$other color by percentage value (int between 0 and 100).";
}
 
 
##[ site-core classes ]

class Date
"Represents a date."
{
    var int year "Year; 4 digits.";
    var int month "Month; 1-12.";
    var int day "Day; 1-31.";
 
    function builtin day_of_week() : int
    "Returns the day of the week this date falls on, from Sunday=1 to Saturday=7";
 
    function builtin date_format () : string
    "Returns date formatted as normal.  /// SeeAlso: siteapi.core1.dateformats";
 
    function builtin date_format (string fmt) : string
    "Returns date formatted as indicated by \$fmt.  One of: short, med, long, med_day, long_day.  Or a custom format.  Default is 'short'. /// SeeAlso: siteapi.core1.dateformats";
 
    function builtin date_format (string fmt, bool linkify) : string
    "Returns date formatted in the same way as calling date_format(string fmt), but with day, month, and year as links to the corresponding archive pages.";
 
    function builtin compare(Date d) : int
    "Compare two dates. Returns a negative number if d is before the subject in time, positive if it is after, or zero if the two dates are equal. When comparing a Date with a DateTime, the time on the bare Date value is assumed to be midnight.";
 
    function builtin compare(DateTime d) : int
    "Compare two dates. Returns a negative number if d is before the subject in time, positive if it is after, or zero if the two dates are equal. When comparing a Date with a DateTime, the time on the bare Date value is assumed to be midnight.";
}
 
class DateTime extends Date
"Represents both a date and time."
{
    var int hour "Hour; 0-23.";
    var int min "Minute; 0-59.";
    var int sec "Second; 0-59.";
 
    function builtin time_format () : string
    "Returns time formatted as normal.  /// SeeAlso: siteapi.core1.dateformats";
 
    function builtin time_format (string fmt) : string
    "Returns time formatted as indicated by \$fmt, or normal if blank.  /// SeeAlso: siteapi.core1.dateformats";
}
 
class Image
"Represents an image."
{
    var readonly string url "URL of the image";
    var int width "Width in pixels";
    var int height "Height in pixels";
    var string alttext "Default alternative text for image";
    var readonly string{} extra "Extra params for img tag";
 
    function builtin set_url (string url)
    "Sets the URL, doing any necessary escaping.";
 
    function print ()
    "Print an HTML tag for this Image";
 
    function print (string alttext)
    "Print an HTML tag for this Image with given alttext";
 
    function print (string{} opts)
    "Print the HTML for an image, Supported keys are 'href' to create a link to the image source
     and 'a_attr' which adds attributes to the anchor tag if a link is to be printed.";
 
    function as_string () : string
    "Return the HTML tag for this image";
 
    function as_string (string alttext) : string
    "Return an HTML tag for this Image with given alttext";
 
    function as_string (string{} opts) : string
    "Return the HTML for an image, Supported keys are 'href' to create a link to the image source
     and 'a_attr' which adds attributes to the anchor tag if a link is to be printed.";
}
 
class Link
"A link or button"
{
    var readonly string url "URL which the link points to";
    var readonly string caption "The caption for the link";
    var Image icon
    "A suggestion from the server as to which icon to use. layouts/users can override this of course.
    alt text works similarly to [member[Link.caption]].";
    var readonly string{} extra "Extra attributes passed to the link";
 
    function print_button
    "Output this Link as a clickable button using [member[Link.icon]]";
 
    function as_string() : string
    "Return the button HTML link.";
}
 
class ItemRange
"Represents a range of items which optionally contain items."
{
  var bool all_subitems_displayed "True if the subitems in this range represent the entire set. In this case, all of the URL members are blank.";
  var int num_subitems_displayed "The number of subitems in this range.";
  var int total "The total number of items that are navigable to.";
  var int current "The currently-active item.";
  var int from_subitem "The index of the first subitem in this range.";
  var int to_subitem "The index of the last subitem in this range.";
  var int total_subitems "The number of subitems.";
  var readonly string url_next "URL for the 'next' link.  Blank if there isn't a next URL.";
  var readonly string url_prev "URL for the 'previous' link.  Blank if there isn't a previous URL.";
  var readonly string url_first "URL for the 'first' link.  Blank if already on the first page.";
  var readonly string url_last "URL for the 'last' link.  Blank if already on the last page.";
  function builtin url_of(int n) : string "Returns the URL to use to link to the nth item";
 
  function print () "Prints the item range links with a default anchor and CSS class.";
  function print(string{} extra) "Prints the item range links with the given attributes. Accepts 'class' and 'anchor'";
}
 
### LJ Specific Classes

class CommentInfo
"Information about comments attached to something."
{
    var readonly string read_url "URL pointer to the 'Read Comments' view.";
    var readonly string post_url "URL pointer to the 'Post Comments' view.";
    var readonly string permalink_url "Permanent URL for the entry.";
    var int count "Current number of comments available to be read by the viewer.";
    var bool screened "Set to true if there are screened comments and remote user can unscreen them.";
    var bool enabled "Set to false if comments disabled journal-wide or just on this item.";
    var bool comments_disabled_maintainer "Set to true if the comments on this entry were disabled by a community maintainer";
    var bool maxcomments "Set to true if entry has reached a comment maximum.";
    var bool show_postlink "Indicates whether the Post Comment link for this entry should be shown.";
    var bool show_readlink "Indicates whether the Read Comments link for this entry should be shown.";
 
    function print
    "Print all comment related links";
    function print_readlink
    "Print the formatted link to the 'Read Comments' view";
    function print_postlink
    "Print the formatted link to the 'Post Comments' view";
}
 
class UserLink
"A user-defined link to an outside resource."
{
    var readonly bool is_heading "Is this link a heading or category name?  If so, it has no url and a list of children.";
    var readonly string title "The title or label for the link";
    var readonly string url "The url to which the link points";
    var readonly UserLink[] children "Not Implemented: An array of child UserLink objects.";
}
 
class UserLite
"A 'lite' version of a [class[User]] which the system often has more readily-available than a full version."
{
    var readonly string username "Canonical Username, ex: johnqpub.  Note that if journal_type is an external identity, there will be no username, so this field will be a display version of their URL, longer than 25 characters, and with characters other than a-z, 0-9 and underscore.";
    var readonly string name "User's formatted name, ex: John Q. Public";
    var readonly string journal_type "Type of account: P (personal), C (community), Y (syndicated), I (external identity) etc";
    var readonly string userpic_listing_url "URL of a page listing this user's userpics";
 
    var Link{} data_link "Links to various machine-readable data sources relating to this user";
    var string[] data_links_order "An array of data views which can be used to order the data_link hash";
 
    var string[] link_keyseq "Array of keys which can be passed into the get_link";
 
    function builtin equals(UserLite u) : bool "Returns true if the two user objects refer to the same user. Use this rather than comparing usernames, since usernames aren't globally unique.";
    function builtin ljuser() : string "Returns an LJ user tag for the user.";
    function builtin ljuser(Color link_color) : string "Returns an LJ user tag for the user.  The color of the link will be link_color.";
    function builtin get_link(string key) : Link "Returns a link based on the given key, or null if the link is unavailable";
    function base_url () : string "Returns URL of user's journal.";
    function tag_manage_url () : string "Returns URL to user's tag management page.";
    function print_interaction_links() "Print the interaction links for this user/journal.";
    function as_string() : string;
    function print ();
}
 
class Tag
"Represents a tag in its most basic form."
{
    var readonly string name "Textual representation of this tag.";
    var readonly string url "URL to view entries with this tag.";
}
 
class TagDetail extends Tag
"A rich structure with lots of information about a Tag."
{
    var readonly int use_count "Count of how many times this tag has been used.";
    var readonly string visibility "The visibility level for this tag.  Based on the entries it's used on.  Can be one of: public, private, friends, group.";
    var readonly int{} security_counts "How many times this tag has used this security.  The keys are which security, one of: public, private, friends, group.  The value is the count of times the tag is used on entries with that security level.";
}
 
class EntryLite
"Base class for both journal entries and comments."
{
    var readonly string subject "Subject.  May contain HTML.  Don't do substring chops on this.";
 
    var readonly string text
        "Entry Text; Use [method[EntryLite.print_text()]] to print this so that the entry's trust level is not affected by your layer's trust level.";
 
 
    var readonly bool text_must_print_trusted
        "Indicates that this entry's text contains some content that must be printed trusted, with [method[EntryLite.print_text()]], rather than printed directly from an untrusted context. Use this to fall back to a plain trusted print if you are doing something unusual with [member[EntryLite.text]]. Most layers can just ignore this and always use [method[EntryLite.print_text()]].";
 
    var DateTime time "The user-specified time of the post, or the GMT time if it's a comment.";
    var DateTime system_time "The system time (in GMT) this entry or comment was posted.";
    var readonly bool timeformat24 "Indicates whether the time should be displayed in 24-hour format";
 
    var UserLite poster "Author of the entry, or null if an anonymous comment";
    var UserLite journal "Journal the entry has been posted to";
 
    var readonly Tag[] tags "Array of tags applied to this entry.";
 
    var Image userpic "The userpic selected to relate to this entry.";
 
    var readonly string{} metadata "Post metadata. Keys: 'music', 'mood', 'location', 'groups', 'xpost'.  Comment metadata.  Key: 'poster_ip'";
 
    var readonly string dom_id "The DOM 'id' attribute you should put on your outer-most element";
 
    var readonly string permalink_url "A URL at which this specific entry can be viewed, for linking purposes.";
    var int depth "Visual depth of entry.  Top-level journal entries are always depth zero.  Comments have a depth greater than or equal to one, depending on where the thread is rooted at.";
 
    var string[] link_keyseq "An array of keys which you should pass to [method[EntryLite.get_link(string key)]] to produce an entry 'toolbar'. Does not contain nav_next and nav_prev for entries; you should retrieve those separately and put them somewhere appropriate for your layout.";
 
    function print_wrapper_start() "Start the wrapper for this entry or comment; includes anchor and classes";
    function print_wrapper_end() "End the wrapper for this entry or comment.";
    function print_metatypes() "Print the metatype icons for this entry (security, age restriction) or comment (subject icon)";
    function print_metadata () "Print the metadata associated with this entry or comment";
    function print_text() [fixed]
        "Print the entry text. Doesn't print anything in some contexts, such as on a month view or in a collapsed comment.";
    function builtin get_link (string key) : Link "Get a link to some action related to this entry or comment. You can iterate over [member[EntryLite.link_keyseq]] to get keys to pass in here to produce a 'toolbar' of links.";
    function builtin get_plain_subject () : string "For Entries that can contain HTML subjects, this returns the subject without HTML.  Comments can't have HTML in subjects, so this is equivalent to just using \$.subject.  The returned 'plain' subject may still contain HTML entities, so don't do substring chops on it either.";
    function builtin get_tags_text () : string "Returns a string containing a div of class 'ljtags' with the tags for the entry.  If there are no tags on the entry, returns a blank string.  The string is formatted according to the 'text_tags' property.";
    function print_userpic() "Print the userpic for this entry or comment";
    function print_management_links() "Print the links to manage this entry or comment. (Screen, freeze, track, etc)";
    function print_interaction_links() "Print the links to interact with this entry or comment. (Post a comment etc)";
    function print_time () [fixed] "Print the time of this post, with most useful information for user, and with tooltip for more.";
    function print_time (string datefmt, string timefmt) [fixed] "Print the time of the post, with customized date/time formats.";
    function time_display () : string "Show the time of this post, with most useful information for user, and with tooltip for more.";
    function time_display (string datefmt, string timefmt) : string "time_display, with customized date/time formats.";
    function builtin formatted_subject (string{} opts) : string "formats subject - outputs subject as html-link, gets hash of attributes - class and(or) style ";
    function print_subject () [fixed] "Print the formatted subject for this entry or comment";
    function print_subject (string{} opts) [fixed] "Print the formatted subject for this entry or comment, gets hash of attributes - class and(or) style ";
    function print_poster () "Print the poster of the entry or comment (user or user in journal, etc)";
}
 
class Entry extends EntryLite
"A journal entry"
{
    var readonly string security "The security level of the entry ('private', 'protected'), or blank if public.";
    var Image security_icon "A little icon which should be displayed somewhere on an entry to represent the security setting";
 
    var readonly string adult_content_level "The adult content level ('NSFW', '18'), or blank if unrestricted.";
    var Image adult_content_icon "A little icon which should be displayed somewhere on an entry to represent the adult content level";
 
    var Image mood_icon "Mood icon, or null.";
    var CommentInfo comments "Comment information on this entry";
 
    var bool new_day "Is this entry on a different day to the previous one?";
    var bool end_day "Is this the last entry of a day?";
 
    var int itemid "Server stored ID number for this entry";
 
    function print_metatypes(bool icon, bool info) "Print the metatype information for this entry";
 
    function print_interaction_links(string target) "Print the links to interact with this entry or comment, with target.";
 
    function print_tags() "Print the tags for this entry.";
    function builtin plain_subject () : string
        "Return entry's subject as plain text, with all HTML removed.";
 
    function print_link_next() "Print the link to the next entry in this journal.";
    function print_link_prev() "Print the link to the previous entry in this journal.";
    function builtin viewer_sees_ebox() [fixed] : bool
    "True if opaque horizontal site-specific content boxes between entries should be displayed to the user.";
    function builtin print_ebox() [fixed]
    "Prints a small horizontal bar of site-specific content between entries in a journal.";
}
 
class StickyEntry extends Entry
"An entry that is shown on top of the journal's recent entries page"
{
    var Image sticky_entry_icon "A little icon displayed next to the subject of a sticky entry post";
 
    function print_sticky_icon () "prints the icon into the subject line";
}
 
 
 
class Comment extends EntryLite
"A comment to a journal entry, or to another comment."
{
    var Image subject_icon "Subject icon, or null.";
    var int talkid "Server stored ID number for this comment.";
    var Comment[] replies "Comments replying to this comment.";
    var bool full "True if all information is available for this comment.  False if only the subject, poster, and date are available.  (collapsed threads)";
    var readonly string parent_url "URL to parent comment, or blank if a top-level comment.";
    var readonly string reply_url "URL to reply to this comment.";
    var readonly string thread_url "URL to view threaded rooted at this comment, or blank if comment has no children.";
    var readonly string threadroot_url "URL to view the entire thread this comment is part of, or blank if a top-level comment.";
    var readonly bool screened "True if comment is in screened state.";
    var readonly bool frozen "True if comment is in frozen state.";
    var readonly bool deleted "True if comment has been deleted. Deleted comments still show up if they are the parent of a thread.";
    var readonly bool fromsuspended "True if comment's author has been suspended.";
    var readonly string anchor "Direct link to comment, via HTML name anchors";
    var readonly bool comment_posted "True if comment was just posted by the current user.";
    var readonly bool edited "True if the comment has been edited.";
 
    var readonly DateTime time_remote "The local time the comment appeared, in the remote user's (reader's) timezone.  Or undef if no remote user, or remote user hasn't set their timezone.";
    var readonly DateTime time_poster "The local time the comment appeared, in the commenter's timezone.  Or undef if anonymous comment, or commenter's timezone is unknown.";
    var readonly int seconds_since_entry "The number of elapsed seconds from the time of the journal entry until the comment was initially posted.";
    var readonly string editreason "The reason this comment was last edited";
 
    var readonly DateTime edittime "The GMT time the comment was edited.  Or undef if the comment hasn't been edited.";
    var readonly DateTime edittime_remote "The local time the comment was edited, in the remote user's (reader's) timezone.  Or undef if no remote user, or remote user hasn't set their timezone, or the comment hasn't been edited.";
    var readonly DateTime edittime_poster "The local time the comment was edited, in the commenter's timezone.  Or undef if anonymous comment, or commenter's timezone is unknown, or the comment hasn't been edited.";
 
    function builtin print_multiform_check "Prints the select checkbox in CSS class 'ljcomsel' with DOM id 'ljcomsel_\$talkid' for a multi-action form started with [method[EntryPage.print_multiform_start()]].";
 
    function builtin print_reply_link(string{} opts) "Prints a link to reply to the comment. You may specify the link text in the 'linktext' option, and the link CSS class in 'class'. You may also specify the url of an image to use as a button in 'img_url'.";
 
    function builtin print_reply_container() "Prints the area in which the quickreply box will go. If no container is available, quickreply will not work.";
 
    function builtin print_reply_container(string{} opts) "Prints the area in which the quickreply box will go. You may 'class' which will be the CSS class used by the container. If no container is available, quickreply will not work.";
 
    function builtin expand_link () : string "Returns a link to expand a collapsed comment. Uses the value of the 'text_comment_expand' property as the text. Will not work in untrusted layers.";
    function builtin expand_link (string{} opts) : string "Returns a link to expand a collapsed comment. Can pass options 'text', 'title', 'class', and 'img_url' (and other 'img_*' options). Will not work in untrusted layers.";
 
    function builtin print_expand_link () : string "Prints a link to expand a collapsed comment. Uses the value of the 'text_comment_expand' property as the text.";
    function builtin print_expand_link (string{} opts) : string "Prints a link to expand a collapsed comment. Can pass options 'text', 'title', 'class', and 'img_url' (and other 'img_*' options).";
    function print_time (string datefmt, string timefmt, bool edittime) "Same as EntryLite::print_time, except can pass in if we want the edit time or not.";
    function time_display (string datefmt, string timefmt, bool edittime) : string "Same as EntryLite::time_display, except can pass in if we want the edit time or not.";
    function print_edittime () "Print the time that this comment was edited, with most useful information for user.  Empty string if the comment hasn't been edited.";
    function print_edittime (string datefmt, string timefmt) "print_edittime, with customized date/time formats.";
    function edittime_display () : string "Show the time that this comment was edited, with most useful information for user.  Empty string if the comment hasn't been edited.";
    function edittime_display (string datefmt, string timefmt) : string "edittime_display, with customized date/time formats.";
 
    function print_edit_text () "Print the text that says when this comment was edited.";
 
}
 
### Userinfo

class Friend extends UserLite
"Represents a friends or friendof list"
{
    var Color bgcolor "Background color selected for friend";
    var Color fgcolor "Foreground color selected for friend";
}
 
class User extends UserLite
"A more information-rich userinfo structure"
{
    var Image default_pic "Information about default userpic";
    var readonly string website_url "URL pointer to user's website";
    var readonly string website_name "'pretty' name of user's website";
    function print_userpic() "Print the userpic for this user";
 
}
 
### Other

class Redirector
"A redirector makes either a GET URL which redirects to a pretty URL or an HTML form which posts to a URL that redirects to a pretty URL.  This class exists because it's often desirable to use a form to end up at a URL, instead of doing a GET request.  It's also used in cases where finding the previous or next URL would incur database overhead which would be wasteful, considering most people don't click previous/next links.  Instead, the system will give you a Redirector object which has a URL that'll do the lookup for you later, followed by a redirect."
 
{
    var readonly string user;
    var readonly string vhost;
    var readonly string type;
    var readonly string url;
 
    function start_form() "Starts an inline HTML form, then calls [method[Redirector.print_hiddens()]].  You can also make it yourself, using [member[Redirector.url]], if you need special form attributes.";
    function print_hiddens() "Prints the necessary hidden elements for a form.  Called automatically by [method[Redirector.start_form()]].";
    function end_form() "Prints a form close tag.";
 
    function builtin get_url(string redir_key) "Returns a GET URL, given a redir_key";
}
 
### Pages

class Page
"Base template for all views"
{
    var readonly string view "The view type (recent, friends, archive, month, day, entry)";
    var readonly string{} args
    "Arguments from the URL's query string (after the question mark). S2 code can only access arguments starting with a period, and this period is not included in the hash key.";
 
    var User journal "User whose journal is being viewed";
    var readonly string journal_type "Journal type, ex: 'P' (personal), 'C' (community), etc.";
    var readonly string base_url "The base URL of the journal being viewed.";
    var readonly string{} view_url
    "Links to top-level views where id equals the name of the view being linked to.
    (if one of views == \$.view, already looking at that view)";
 
    var readonly string[] views_order "An array of view identifiers which can be used to order the views hash.";
    var readonly string head_content
    "Extra tags supplied by the server to go in the <head> section of the output HTML document. Layouts
    should include this in the head section if they are writing HTML.";
 
    var readonly string stylesheet_url
    "The URL to use in a link element for the server-supported external stylesheet to put stuff in it)";
 
    var readonly string global_title
    "A title selected by the user for their whole journal.";
 
    var readonly string global_subtitle
    "A sub-title selected by the user for their whole journal.";
 
    var readonly UserLink[] linklist
    "An array of UserLink objects defined by the user to be displayed on their journal.";
 
    var bool has_activeentries
    "So we don't try to load the activeentries array when there are no entries to display";
 
    var readonly Entry[] activeentries
    "Array of recently active entries available to be seen by the viewer of the page.";
 
    var readonly bool timeformat24 
    "Indicates whether the time should be displayed in 24-hour format";
 
    var readonly DateTime local_time
    "A DateTime object filled with the time (in the viewer's timezone) when the page was created.";
 
    var readonly DateTime time
    "A DateTime object filled with the time (GMT) when the page was created.";
 
    var Link{} data_link "Links to various machine-readable data sources relating to this page";
    var string[] data_links_order "An array of data views which can be used to order the data_link hash";
 
    function print
    "The main entry point that Dreamwidth calls. Layouts should override this to create HTML that's the
    same for all view types, and use \$this->title, \$this->head and \$this->body to include view-specific
    content into the template.";
 
    function print_title "Print the title for this particular page, such as \"Friends' Recent Entries\" for the friends view,
    or a date for the day view. Should be overridden in i18n layers. Ideally, layout layers should never override
    this.  See [method[Page.title()]].";
 
    function print_head_title "Print the title for this particular page, as in print_title, formatted with <title> and the journal username";
 
    function print_global_title;
    function print_global_subtitle;
 
    function view_title : string
    "Return a title for this particular page, such as \"Friends' Recent Entries\" for the friends view,
    or a date for the day view. Should be overridden in i18n layers. Ideally, layout layers should never override
    this.  See [method[Page.title()]].";
 
    function title : string
    "Return a relevant combination of [member[Page.global_title]] and [method[Page.view_title()]].  May be
    overridden in layout layers or left untouched for the core layer to handle.";
 
    function print_wrapper_start() "Start the body wrapper for this page; includes default classes";
    function print_wrapper_start(string{} opts) "Start the body wrapper for this page; includes default classes and takes a hash using the key 'class'";
    function print_wrapper_end() "End the body wrapper for this entry or comment.";
 
    function print_time
    "Print the time when the page was created.";
 
    function print_time (string datefmt, string timefmt)
    "Print the time when the page was created, with customized date/time formats.";
 
    function print_body
    "Call from [method[Page.print()]] to render parts of the view that are specific to the view, eg print
    the recent set of journal entries, recent friends entries, or rows of user information";
 
    function print_head [fixed]
    "Print server side supplied head content. This is a fixed function, so you can't override it. See
    [method[Page.print_custom_head()]] if you want to supply custom head content.";
 
    function builtin print_control_strip [fixed]
    "Prints a control strip for the user's convenience";
 
    function print_custom_head
    "Layers can override this to add extra HTML to the head section of the HTML document.
    Note that layouts are not intended to override this method.";
 
    function print_linklist
    "Print the list of UserLink objects specified by the user.";
 
    function print_entry(Entry e)
    "Output a journal entry. Layouts should override this and the inherited versions in RecentPage, FriendsPage
    and DayPage to change how entries display.";
 
    function builtin get_latest_month() : YearMonth
    "Returns information about the latest month the user posted (or the current month, if no entries), so that the page may include a mini-calendar or similar features.";
 
    function builtin visible_tag_list() : TagDetail[]
    "Returns an array of tags that the logged in user can see for the journal being viewed.";
 
    function builtin visible_tag_list(int limit) : TagDetail[]
    "Returns an array of tags that the logged in user can see for the journal being viewed, limited to the top \$limit tags by number of uses.";
 
    function builtin print_reply_link(string{} opts) "Prints a link to reply to the comment. You may specify the link text in the 'linktext' option, the link CSS class in 'class', and the target container in the 'target' option. You may also specify the url of an image to use as a button in 'img_url'.";
 
    function builtin print_reply_container(string{} opts) "Prints the area in which the quickreply box will go. Options you may specify are 'target' which will be the target id, and 'class' which will be the CSS class used by the container. If no container is available, quickreply will not work.";
 
    function print_navigation() [fixed] "Print out the page navigation links.";
    function print_navigation(string{} opts) "Print out the page navigation links. Accepts 'class', an additional CSS class for this container.";
 
    function print_module_section( string section_name )
    "Prints a module section, given a group name";
 
    function print_stylesheets
    "Prints all defined stylesheets, including default and user-defined ones.";
 
    function print_default_stylesheets
    "Prints stylesheets defined by the layout, including any additional CSS defined by the theme.";
 
    function print_default_stylesheet
    "Prints the layout's base default stylesheet.";
 
    function print_theme_stylesheet
    "Prints theme-specific CSS. Should be overwritten by themes that include CSS not part of the layout's default stylesheet.";
 
    function builtin print_trusted(string key)
    "Prints a trusted string by key.";
}
 
class TagsPage extends Page
"A detail page listing a user's tags."
{
    var TagDetail[] tags "List of tags visible to the user viewing the page.";
}
 
class MessagePage extends Page
"A page showing an error or confirmation message."
{
    var readonly string title "The title of the message.";
    var readonly string message "The body of the message. Do not print this directly; use [method[MessagePage.print_body()]] instead.";
    var Link{} links "An associative array of links to be displayed alongside this message. Iterate over [member[MessagePage.link_keyseq]] to find the keys.";
    var string[] link_keyseq "A list of links, indicated by key, that should be displayed alongside this error. They should ideally be displayed in a similar way to the entry links displayed on the entry page.";
 
    function print_message() [fixed] "Print the message. Call this rather than printing [member[MessagePage.message]] directly.";
    function print_links() "Print the links from the [member[MessagePage.links]] and [member[MessagePage.link_keyseq]] members. Layouts will probably want to override this.";
}
 
class RecentNav
"Navigation position within a [class[RecentPage]] or [class[FriendsPage]] and URLs to move about."
{
    var int version        "Currently version 1.  A new method of navigation has been frequently discussed, so this is planning for the future";
 
    # version 1 attributes:
    var int skip            "Indicates how many entries are being skipped back.";
    var int count           "Indicates how many entries we're currently seeing";
    var readonly string forward_url  "URL to go forward in time, or blank if furthest forward.";
    var int forward_skip    "Number of items we'd be skipping going forward.";
    var int forward_count   "Number of items we'd be potentially seeing going forward.";
    var readonly string backward_url "URL to go backward in time, or blank if furthest back server will allow.";
    var int backward_skip   "Number of items we'd be skipping going back more.";
    var int backward_count  "Number of items we'd be potentially seeing going backward.";    
}
 
class RecentPage extends Page
"Most recent entries page, formerly known as the LASTN view in the previous style system"
{
    var Entry[] entries
    "Array of entries available to be seen by the viewer of the page.";
 
    var RecentNav nav;
 
    var StickyEntry stickyentry
    "Entry shown on top of the Recent Entries page";
 
    function print_sticky_entry(StickyEntry s)
    "function to print the sticky entry";
 
    var bool filter_active
    "If true, some kind of filter is in effect. If this filter has a name, it will be included in [member[RecentPage.filter_name]]";
 
    var string filter_name
    "The name of the filter in effect, if it has a name. This is only used when [member[RecentPage.filter_active]] is true.";
 
}
 
class FriendsPage extends RecentPage
"Friends most recent entries"
{
    var Friend{} friends
    "A mapping from friend username to color association information.  There will only be keys for friends whose entries are in the entries array.";
 
    var readonly string friends_title
    "A user-selected title for their friends page.";
 
    var string friends_mode
    "The 'mode' of this view. An empty string indicates a normal friends view, while 'network' indicates the network view.";
}
 
class DayPage extends Page
"View entries by specific day"
{
    var Date prev_date "Previous day";
    var Date next_date "Next day";
    var readonly string prev_url "URL to previous day";
    var readonly string next_url "URL to next day";
    var bool has_entries "True if there are entries on the specified day";
    var Entry[] entries "Array of entries available to be seen by the viewer of the page";
    var Date date "Date of the current day";
}
 
### Archive classes

class YearYear
"Information on how to link to a year in the year archive"
{
    var int year "Number of the year, eg 2001.";
    var readonly string url "URL to link to for this year.";
    var bool displayed "If this is the year currently being displayed, this will be true.";
}
 
class YearDay
"Information on how to link to a day in the year archive"
{
    var int day "Day of month number";
    var Date date "Date of day";
 
    # http://zilla.livejournal.org/show_bug.cgi?id=504
    # var bool is_today "True if the day represents the current day.";

    var int num_entries "Number of entries made on this day";
    var readonly string url "A URL to view the day, if there are entries, else blank.";
}
 
class YearWeek
"Represents a week on the [class[YearMonth]] on the [class[YearPage]]."
{
    var int pre_empty "How many days at the start of the week are blank? (From previous month)";
    var int post_empty "How many days at the end of the week are blank? (From next month)";
    var YearDay[] days "An array of the days of the week (0=sunday)";
 
    function print()
    "Print formatted week";
}
 
class YearMonth
"A month on the [class[YearPage]]."
{
    var bool has_entries "If this is false, you probably don't want to display this month.";
    var int month "The number of the month";
    var int year "The number of the year";
    var YearWeek[] weeks "An array of the weeks of the month (for ease of building a row-per-week calendar)";
    var readonly string url "A url to link to in order to view this month.";
    var readonly string prev_url "A url to link to in order to view the previous month.";
    var readonly string next_url "A url to link to in order to view the next month.";
    var Date prev_date "Date of previous month, with day of zero, or null if none.";
    var Date next_date "Date of next month, with day of zero, or null if none.";
 
    function builtin month_format () : string
    "Returns month formatted long (February 1980)  /// SeeAlso: siteapi.core1.dateformats";
    function builtin month_format (string fmt) : string
    "Returns time formatted as indicated by \$fmt, or 'long' if blank.  /// SeeAlso: siteapi.core1.dateformats";
    function builtin month_format (string fmt, bool link) : string
    "Returns time formatted in the same way as calling month_format(string fmt), but with month and year as links to the corresponding archive pages.";
}
 
class YearPage extends Page
"Entire calendar page for a single year."
{
    var int year "The year being viewed";
    var YearYear[] years "Information for linking to other years";
    var YearMonth[] months "12 months objects, even if no entries are in that month.";
 
    function print_month(YearMonth m)
    "Print the calendar cell for the given month";
 
    function print_year_links()
    "Print the navigation links to move between years";
}
 
class MonthDay extends YearDay
"Summaries of entries on a given day on the [class[MonthPage]]."
{
    var bool has_entries "True if there are entries on this day.";
    var Entry[] entries "Only populated on the month view.  Entry text not present.";
 
    function print_subjectlist
    "Print a list of entry summaries including subjects";
}
 
class MonthEntryInfo
"A month the user has journal entries, along with information to link to it."
{
    var Date date "Date of month, with day of zero.";
    var readonly string url "URL for the [class[MonthPage]] month view.";
    var readonly string redir_key "The 'redir_key' parameter for a [class[Redirector]] instance.";
}
 
class MonthPage extends Page
"A page which contains a list of entries made in that month"
{
    var Date date "Date of this month, with day of zero.";
    var MonthDay[] days "One entry for each day of the month.";
    var MonthEntryInfo[] months "Other months this journal has entries.";
    var Date prev_date "Date of previous month, with day of zero, or null if none.";
    var Date next_date "Date of next month, with day of zero, or null if none.";
    var readonly string prev_url "URL of previous month, or empty string if none.";
    var readonly string next_url "URL of next month, or empty string if none.";
    var Redirector redir "Necessary to make a form which POSTs to a redirector";
}
 
class EntryPage extends Page
"A page with a single journal entry and associated comments."
{
    var Entry entry "Journal entry being viewed";
    var ItemRange comment_pages "Represents what comment page is being displayed.";
    var Comment[] comments "Comments to journal entry, or at least some of them.";
    var bool viewing_thread "True if viewing a specific sub-thread of the comments.  Style may which to hide the journal entry at this point, since the focus is the comments.";
 
    function print_comment_section(Entry entry) "Prints comment section";
    function print_comments(Comment[] comments) "Prints comments";
    function print_comment(Comment comment) "Prints a full comment";
    function print_comment_partial(Comment comment) "Prints a collapsed comment";
 
    var bool multiform_on "Set to true if the multi-action is to be printed, which requires both comments and applicable permissions for the remote user.";
    function builtin print_multiform_start "Prints start of form tag and hidden elements to do a multi-comment action (multiple delete, screen, unscreen, etc...)";
    function builtin print_multiform_end "Prints end of form tag to do a multi-comment action.";
    function builtin print_multiform_actionline "Prints the line of the multiform giving instructions, options, and the submit button, using the text of the different \$*text_multiform_ properties.";
}
 
class ReplyForm
"This class will be used more in the future to set options on the reply form before
it's printed out by the system.  The system has to print it since it contains
sensitive information which can't be made available to S2."
{
    var readonly bool subj_icons "Whether user has enabled subject icons or not.  Currently read-only until policy is decided on whether layers should be able to change it (rather than changing it in the user preferences)";
    function builtin print() "Prints the reply form";
}
 
class ReplyPage extends Page
"A page to reply to a journal entry or comment"
{
    var Entry entry "The journal entry for this talk page";
    var EntryLite replyto "The object which is being replied to, either the entry or a comment";
    var ReplyForm form "The reply form.";
 
    function print_comment(Comment comment) "Prints a full comment";
}
 
class PalItem
"A specification for a numbered palette index in a GIF or PNG to be changed to a certain color"
{
    var int index "Integer palette index.";
    var Color color "Color to put at specified index.";
}
 
##[ Built-in Functions ]

function builtin eurl (string s) : string
"URL escape";
 
function builtin ehtml (string s) : string
"Escapes all HTML tags and entities from the text";
 
function builtin etags (string s) : string
"Escapes all HTML tags (but not entities) from text";
 
function builtin clean_url (string s) : string
"Returns the given URL back if it's a valid URL.";
 
function builtin rand (int high) : int
"Returns a random integer between 1 and \$high, inclusive.";
 
function builtin rand (int low, int high) : int
"Returns a random integer between \$low and \$high, inclusive.";
 
function builtin pageview_unique_string () : string
"Returns a unique string for the remote user.";
 
function builtin alternate (string a, string b) : string
"With each call, this function will alternate between the two values and return one of them.
Useful for making tables whose rows alternate in background color.";
 
function builtin zeropad (int n, int digits) : string
"Returns the number padded with zeroes so it has the amount of digits indicated.";
 
function builtin zeropad (string n, int digits) : string
"Returns the number padded with zeroes so it has the amount of digits indicated.";
 
function builtin striphtml (string s) : string
"Similar to ehtml, but the HTML tags are stripped rather than escaped.";
 
function builtin viewer_logged_in() : bool
"Returns true if the user viewing the page is logged in. It's recommended that your page links to the site
login page if the user isn't logged in.";
 
function builtin viewer_is_owner() : bool
"Returns true if the user viewing the page is both logged in, and is the owner of the content in question.
Useful for returning links to manage information, or edit entries.";
 
function builtin viewer_is_friend() : bool
"Returns true if the user viewing the page is logged in and has access to the journal being viewed.  Returns true if the user is a member of a community.";
 
function builtin viewer_is_subscribed() : bool
"Returns true if the user viewing the page is logged in and subscribes to the journal being viewed.";
 
function builtin viewer_has_access() : bool
"Returns true if the user viewing the page is logged in and has access to the journal being viewed.  In communities, returns true if the viewer is a member of the community.";
 
function builtin viewer_is_admin() : bool
"Returns true if the user viewing a community is logged in and is an admin of the community.";
 
function builtin viewer_is_moderator() : bool
"Returns true if the user viewing a community is logged in and a moderator of the community.";
 
function builtin viewer_is_member() : bool
"Returns true if the user viewing the page is both logged in, and is a member of the community being viewed. Always returns false for personal journals, since they cannot have members.";
 
function builtin viewer_can_search() : bool
"Returns true if the user viewing the page is logged in and can search that journal.";
 
function builtin viewer_can_manage_tags() : bool
"Returns true if the user viewing the page can add, edit, and delete tags on the journal being viewed.";
 
function builtin viewer_sees_control_strip [fixed] : bool
"Returns true if reader will see the built in control strip.";
 
function builtin control_strip_logged_out_userpic_css [fixed] : string
"Returns CSS for the userpic div in the logged out version of the control strip.";
 
function builtin control_strip_logged_out_full_userpic_css [fixed] : string
"Returns CSS for the loggedout_userpic div in the logged out version of the control strip.";
 
function builtin get_page () : Page
"Gets the top-level [class[Page]] instance that Dreamwidth ran the [method[Page.print()]] method on.";
 
function builtin get_url(string user, string view) : string
"Returns a URL to the specified view for the specified user. Views use the same names as elsewhere. (recent, friends, archive, month, userinfo)";
 
function builtin get_url(UserLite user, string view) : string
"Returns a URL to the specified view for the specified user. Views use the same names as elsewhere. (recent, friends, archive, month, userinfo)";
 
function builtin string(int i) : string
"Return the given integer as a string";
 
function builtin int(string s) : int
"Convert the string to an integer and return";
 
function builtin set_content_type(string text)
"Set the HTTP Content-type response header (for example, if outputting XML). Must be called before printing any data.";
 
function builtin get_plural_phrase(int n, string prop) : string
"Picks the phrase with the proper plural form from those in the property \$prop, passing \$n to [function[lang_map_plural(int)]] to get the proper form for the current language, and then substituting the # character with \$n.  Also, returned string is HTML-escaped.";

function builtin weekdays() : int[]
"Integers representing the days of the week. This will start on Monday (2) or Sunday (1) depending on the property setting for start-of-week and go to Sunday (1) or Saturday (7)";
 
function builtin PalItem(int index, Color c) : PalItem
"Convenience constructor to make populating an array of PalItems (like in [function[palimg_modify(string,PalItem[])]]) easy.";
 
 
function builtin UserLite(string username) : UserLite
"Constructor for making a UserLite object from a username";
 
function builtin palimg_modify(string filename, PalItem[] items) : string
"Return a URL to the specified filename (relative to the palimg root) with its palette table altered, once for each provided [class[PalItem]].  Restrictions:  only 7 palette entries may be modified, and the PalItem indexes must be 0-15.";
 
function builtin palimg_tint(string filename, Color bright) : string
    "Return a URL to the specified filename (relative to the palimg root) with its palette table tinted.  The given 'bright' color will be the new white, and darkest color remains black.";
 
function builtin palimg_tint(string filename, Color bright, Color dark) : string
    "Return a URL to the specified filename (relative to the palimg root) with its palette table tinted.  The given 'bright' color will be the new white, and the given 'dark' color is the new black.";
 
function builtin palimg_gradient(string filename, PalItem start, PalItem end) : string
    "Return a URL to the specified filename (relative to the palimg root) with its palette table made into a gradient.  All palette entries between the inclusive indexes of \$start and \$end will fade from the colors in \$start and \$end.  The palette indexes for the start and end can be between 0 and 255.";
 
function builtin set_handler(string eventname, string[][] commands);
 
function builtin userlite_base_url(UserLite ul) : string;
 
function builtin start_css () "Declare that you're about to start printing out CSS that should be buffered, then later cleaned when you call end_css().  WARNING: this is not re-entrant.  You can't call start_css recursively.";
function builtin end_css () "Declare that you're done printing CSS and the output thus buffered should be cleaned and printed.";
 
function builtin journal_current_datetime() : DateTime
"Returns the current datetime in the timezone of the journal being viewed.";
 
function builtin style_is_active() : bool
"Returns if the style (layout and theme) calling it is active based on a hook.  If hook isn't defined, returns true always.";
 
function builtin htmlattr(string name, string value) : string
"If the value isn't blank, return in HTML attribute format with a leading space.  HTML of name is not escaped.";
 
function builtin htmlattr(string name, int value) : string
"If the value isn't blank, return in HTML attribute format with a leading space.  HTML of name is not escaped.";
 
function builtin print_search_form(string button_text) 
"Prints a search form, with the button text as the label to the submit button.";
 
##[ properties ]

propgroup colors = "Colors";
propgroup fonts = "Fonts";
propgroup presentation = "Presentation";
propgroup other = "Other";
propgroup text = "Text";
propgroup background = "Background";
propgroup sidebar = "Sidebar";
propgroup modules = "Modules";
propgroup appearance = "Appearance";
propgroup options = "Options";
propgroup images = "Images";
propgroup customcss = "Custom CSS";
 
##===============================
## Constant system properties
##===============================
property builtin string IMGDIR {
    noui = 1;
    doc_flags = "[sys]";
    des = "The base URL of the current Dreamwidth site's image directory, without a trailing slash.  Example: \"http://www.dreamwidth.com/img\".";
}
property builtin string STATDIR {
    noui = 1;
    doc_flags = "[sys]";
    des = "The base URL of the current Dreamwidth site's static files directory, without a trailing slash.  Example: \"http://www.dreamwidth.com/stc\".";
}
property builtin string SITENAME {
    noui = 1;
    doc_flags = "[sys]";
    des = "Name of the current Dreamwidth site.  Example: \"Dreamwidth.com\".";
}
 
property builtin string SITENAMESHORT {
    noui = 1;
    doc_flags = "[sys]";
    des = "Shorter name of the current Dreamwidth site.  Example: \"Dreamwidth\".";
}
 
property builtin string SITENAMEABBREV {
    noui = 1;
    doc_flags = "[sys]";
    des = "Abbreviation of the current Dreamwidth site.  Example: \"DW\".";
}
 
property builtin string SITEROOT {
    noui = 1;
    doc_flags = "[sys]";
    des = "The base URL of the current Dreamwidth site, without a trailing slash.  Example: \"http://www.dreamwidth.com\".";
}
 
property builtin string PALIMGROOT {
    noui = 1;
    doc_flags = "[sys]";
    des = "The base URL of palimg files, without a trailing slash.  Example: \"http://www.dreamwidth.com/palimg\".";
}
 
 
##===============================
## Display settings - general
##===============================

# FIXME: Not all of these properties are hooked into the appropriate functions, so setting them does not yet work.

property int num_items_recent {
    des = "Number of journal entries to show on recent entries page";
    doc_flags = "[construct]";
    min = 1;
    max = 50;
}
property int num_items_reading {
    des = "Number of journal entries to show on reading page";
    doc_flags = "[construct]";
    min = 5;
    max = 50;
}
set num_items_recent = 20;
set num_items_reading = 20;
 
property string tags_page_type { 
    des = "Type of tags display on tags page"; 
    values="list|List|cloud|Cloud|multi|Multilevel"; 
}
set tags_page_type = "multi";
 
property string[] reverse_sortorder_group {
    des = "Display entries in reverse-chronological order on my:";
    grouptype = "viewslist";
}
set reverse_sortorder_group = [ "reverse_sortorder_day", "reverse_sortorder_year" ];
property bool reverse_sortorder_recent    { des = "Display entries in reverse-chronological order on my recent entries page"; }
property bool reverse_sortorder_day { 
    des = "Display entries in reverse-chronological order on my day page";  
    label = "Day Page";    
    grouped = 1;
}
property bool reverse_sortorder_year { 
    des = "Display entries in reverse-chronological order on my year archive"; 
    label = "Year Archive";
    grouped = 1;
}
property bool reverse_sortorder_reading   { des = "Display entries in reverse-chronological order on my reading page"; }
set reverse_sortorder_recent = true;
set reverse_sortorder_day = false;
set reverse_sortorder_year = false;
set reverse_sortorder_reading = true;
 
property string[] show_userpics_group {
    des = "Display userpics on my:";
    grouptype = "viewslist";
}
set show_userpics_group = [ "show_userpics_recent", "show_userpics_reading", "show_userpics_entry", "show_userpics_comments" ];
property bool show_userpics_recent {
    des = "Display userpics on my recent entries";
    label = "Recent Entries";
    grouped = 1;
}
property bool show_userpics_reading {
    des = "Display userpics on my reading page"; 
    label = "Reading Page";
    grouped = 1;
}
property bool show_userpics_entry {
    des = "Display userpics on my entry pages";
    label = "Entry Pages";
    grouped = 1;
}
property bool show_userpics_comments {
    des = "Display userpics on my comments";
    label = "Comments";
    grouped = 1;
}
set show_userpics_recent = true;
set show_userpics_reading = true;
set show_userpics_entry = true;
set show_userpics_comments = true;
 
# was !view_entry_disabled
property bool use_journalstyle_entry_page  { des = "Show entry pages in my journal style rather than the site layout"; }
set use_journalstyle_entry_page = true;
 
# Defaults to false, but layouts can set it to true to switch on custom colors for different posters on reading list.
property bool use_custom_friend_colors  { des = "Use my custom reading list colors"; }
set use_custom_friend_colors = false;
 
 
property string[] entry_datetime_format_group {
    des = "Entry date/time format";
    grouptype = "datetime";
}
set entry_datetime_format_group = ["entry_date_format", "entry_time_format"];
property string entry_date_format { 
    des = "Entry date format";
    values = "short|2/5/80|med|Feb. 5th, 1980|med_day|Tue, Feb. 5th, 1980|long|February 5th, 1980|long_day|Tuesday, February 5th, 1980";
    grouped = 1;
}
property string entry_time_format {
    des = "Entry time format.";
    values = "short|12:34am";
    grouped = 1;
}
 
 
property string[] comment_datetime_format_group {
    des = "Comment date/time format";
    grouptype = "datetime";
}
set comment_datetime_format_group = ["comment_date_format", "comment_time_format"];
property string comment_date_format { 
    des = "Comment date format";
    values = "short|2/5/80|med|Feb. 5th, 1980|med_day|Tue, Feb. 5th, 1980|long|February 5th, 1980|long_day|Tuesday, February 5th, 1980";
    grouped = 1;
}
property string comment_time_format {
    des = "Comment time format.";
    values = "short|12:34am";
    grouped = 1;
}
set entry_date_format = "short";
set entry_time_format = "short";
set comment_date_format = "short";
set comment_time_format = "short";
 
# FIXME: this is not currently implemented, so not showing it in the UI yet
property string reg_firstdayofweek {
    noui = 1;
    des = "The day of the week the calendar weeks starts on.";
    doc = "The day of the week the calendar weeks starts on.  Either 'sunday' or 'monday'. Not currently implemented";
    doc_flags = "[construct]";
    values = "sunday|Sunday|monday|Monday";
}
set reg_firstdayofweek = "sunday";
 
##===============================
## Display settings - misc behavior
##===============================

property bool use_shared_pic {
    des = "Use community icons instead of poster's icon";
}
 
property bool tags_aware {
    des = "When enabled, style is responsible for handling tags.  When disabled, tags are automatically inserted into entry bodies.";
    noui = 1;
}
 
property string comment_userpic_style {
   des = "User icon display style for comments";
   doc = "User icon display style for comments.  Either '' for full, 'small' for small, or 'off' for none.";
   doc_flags = "[construct]";
   values = "|Full|small|Small|off|Off";
}
 
property string userpics_position {
   des = "Place icons on left or right of entry";
   values = "left|Left|right|Right";
   }
 
property string entry_metadata_position {
    des = "Place metadata after or before entry text";
    values = "top|Before text|bottom|After text";
    }
 
property bool linklist_support {
    des = "Display link list";
}
 
property string userlite_interaction_links {
    des = "Select whether user interaction links are printed as text or using the available icons";
    values = "icons|icons|text|text-only|";
}
property string entry_management_links {
    des = "Select whether entry management links are printed as text or using the available icons";
    values = "icons|icons|text|text-only|";
}
property string comment_management_links {
    des = "Select whether comment management links are printed as text or using the available icons";
    values = "icons|icons|text|text-only|";
}
property string tags_page_count_type {
    des = "Determines how the tag count is displayed on the tags page";
    values = "|(default for this tag display)|title|Title|number|Number|text|Text";
}
property string module_tags_opts_count_type {
    des = "Determines how the tag count is displayed in the module";
    values = "|(default for this tag display)|title|Title|number|Number|text|Text";
}
 
set use_shared_pic = false;
set tags_aware = true;
set comment_userpic_style = "";
set userpics_position = "left";
set entry_metadata_position = "bottom";
set linklist_support = true;
set userlite_interaction_links = "icons";
set entry_management_links = "icons";
set comment_management_links = "icons";
set tags_page_count_type = "";
set module_tags_opts_count_type = "";
##===============================
## Display settings - sidebar
##===============================

# FIXME: Not sure if none is a sensible value, but need something appropriate as a default for designers who are not using this option.
# In order to use layout_type on /customize/ in official layouts, the associated S2Theme file needs to be edited, and then changing the layout type will select a different wizard-layer.  Non-official layouts can use this property on the /customize/options.bml page.  Both methods will add the layout_type to <body> in the Page::print-wrapper to allow CSS to be written accordingly.

property string layout_type {
        des = "Layout Type";
        values = "|none|one-column|One column|two-columns-left|Two columns (Sidebar on Left)|two-columns-right|Two columns (Sidebar on Right)|three-columns-sides|Three columns (Sidebars on either side)|three-columns-left|Three column (Sidebars on Left)|three-columns-right|Three column (Sidebars on Right)";
    }
 
property string sidebar_width {
    des = "Set the width of any sidebars.  Ensure that the value is given in em or px.";
    example = "15em";
}
 
property string sidebar_width_doubled {
    des = "Enter in a value twice that of any sidebars.  Also ensure that the value is given in em or px.";
    example = "30em";
}
 
set layout_type = "";
set sidebar_width = "";
set sidebar_width = "";
 
property string module_layout_sections {
    des = "Map module sections to user-friendly names for the wizard. Layouts may want to override this";
    example = "none|(none)|one|Left Sidebar|two|Right Sidebar";
    noui = 1;
}
set module_layout_sections = "none|(none)|one|Group One|two|Group Two";
 
property string[][]{} module_sections {
    des = "Build up a list of module sections and the ordering within them";
    noui = 1;
}
 
property string{} grouped_property_override {
    des="Use to override a specific property in a group of properties (for example, which sections a module should show up in), by mapping the default property to a new property you create";
    example="""{ "module_something_section" => "module_something_section_override" } (module_something_section_override is a new property created in your layout layer) """;
    noui = 1;
}
 
##===============================
## Display settings - modules
##===============================
property string[] module_userprofile_group {
    des = "Basic Journal Info";
    grouptype = "module";
}
set module_userprofile_group = ["module_userprofile_show", "module_userprofile_order", "module_userprofile_section", "module_userprofile_opts_group"];
property bool module_userprofile_show   { grouped = 1; }
property int  module_userprofile_order  { grouped = 1; }
property string module_userprofile_section { grouped = 1; }
 
property string[] module_userprofile_opts_group {
    grouptype = "moduleopts";
    grouped = 1;
}
set module_userprofile_opts_group = ["module_userprofile_opts_userpic", "module_userprofile_opts_name", "module_userprofile_opts_website"];
property bool module_userprofile_opts_userpic   { grouped = 1; label = "Default Userpic"; }
property bool module_userprofile_opts_name      { grouped = 1; label = "Display Name"; }
property bool module_userprofile_opts_website   { grouped = 1; label = "Website"; }
 
set module_userprofile_show = true;
set module_userprofile_order = 1;
set module_userprofile_section = "one";
set module_userprofile_opts_userpic = true;
set module_userprofile_opts_name = true;
set module_userprofile_opts_website = true;
 
property string[] module_navlinks_group {
    des = "Navigation";
    grouptype = "module";
}
set module_navlinks_group = ["module_navlinks_show", "module_navlinks_order", "module_navlinks_section"];
property bool module_navlinks_show    { grouped = 1; }
property int  module_navlinks_order   { grouped = 1; }
property string module_navlinks_section { grouped = 1; }
 
set module_navlinks_show = true;
set module_navlinks_order = 2;
set module_navlinks_section = "one";
 
property string[] module_calendar_group {
    des = "Calendar";
    grouptype = "module";
}
set module_calendar_group = ["module_calendar_show", "module_calendar_order", "module_calendar_section", "module_calendar_opts_group"];
property bool module_calendar_show    { grouped = 1; }
property int  module_calendar_order   { grouped = 1; }
property string module_calendar_section { grouped = 1; }
property string[] module_calendar_opts_group {
    grouptype = "moduleopts";
    grouped = 1;
}
set module_calendar_opts_group = ["module_calendar_opts_type"];
property string module_calendar_opts_type         { grouped = 1; values = "|Table|horizontal|Horizontal"; des = "Type of calendar display"; }
 
set module_calendar_show = true;
set module_calendar_order = 3;
set module_calendar_section = "one";
 
property string[] module_pagesummary_group {
    des = "Page Summary";
    grouptype = "module";
}
set module_pagesummary_group = ["module_pagesummary_show", "module_pagesummary_order", "module_pagesummary_section", "module_pagesummary_opts_group"];
property bool module_pagesummary_show   { grouped = 1; }
property int  module_pagesummary_order  { grouped = 1; }
property string module_pagesummary_section { grouped = 1; }
property string[] module_pagesummary_opts_group { grouptype="moduleopts"; grouped = 1; }
set module_pagesummary_opts_group = ["module_pagesummary_opts_comments_tooltip"];
property bool module_pagesummary_opts_comments_tooltip {
    grouped = 1; 
    des = "Display comment counts in page summary as tooltip (otherwise the number will appear next to the entry title)";
    label = "Comment count as tooltip";
}
 
set module_pagesummary_opts_comments_tooltip = true;
 
set module_pagesummary_show = true;
set module_pagesummary_order = 7;
set module_pagesummary_section = "one";
 
property string[] module_tags_group {
    des = "Tags";
    grouptype = "module";
}
set module_tags_group = ["module_tags_show", "module_tags_order", "module_tags_section", "module_tags_opts_group"];
property bool module_tags_show  { grouped = 1; }
property int  module_tags_order { grouped = 1; }
property string module_tags_section { grouped = 1; }
 
property string[] module_tags_opts_group {
    grouptype = "moduleopts";
    grouped = 1;
}
set module_tags_opts_group = ["module_tags_opts_limit","module_tags_opts_type"];
property string module_tags_opts_type       { grouped = 1; des = "Type of tags display"; values="|List|cloud|Cloud|multi|Multilevel"; }
property int module_tags_opts_limit         { grouped = 1; des = "Limit display to these popular tags. '0' for unlimited"; }
 
set module_tags_show = true;
set module_tags_order = 6;
set module_tags_section = "one";
set module_tags_opts_type = "";
set module_tags_opts_limit = 50;
 
property string[] module_links_group {
    des = "Link List";
    grouptype = "module";
}
set module_links_group = ["module_links_show", "module_links_order", "module_links_section"];
property bool module_links_show     { grouped = 1; }
property int  module_links_order    { grouped = 1; }
property string module_links_section { grouped = 1; }
 
set module_links_show = true;
set module_links_order = 4;
set module_links_section = "one";
 
property string[] module_active_group {
    des = "Recent active entries";
    grouptype = "module";
    requires_cap = "activeentries";
}
set module_active_group = ["module_active_show", "module_active_order", "module_active_section"];
property bool module_active_show     { grouped = 1; }
property int  module_active_order    { grouped = 1; }
property string module_active_section { grouped = 1; }
 
set module_active_show = true;
set module_active_order = 8;
set module_active_section = "one";
 
property string[] module_syndicate_group {
    des = "Syndication";
    grouptype = "module";
}
set module_syndicate_group = ["module_syndicate_show", "module_syndicate_order", "module_syndicate_section"];
property bool module_syndicate_show    { grouped = 1; }
property int  module_syndicate_order   { grouped = 1; }
property string module_syndicate_section { grouped = 1; }
 
set module_syndicate_show = true;
set module_syndicate_order = 5;
set module_syndicate_section = "one";
 
property string[] module_time_group {
    des = "Time Loaded";
    grouptype = "module";
}
set module_time_group = ["module_time_show", "module_time_order", "module_time_section"];
property bool module_time_show      { grouped = 1; }
property int  module_time_order     { grouped = 1; }
property string module_time_section { grouped = 1; }
 
set module_time_show = true;
set module_time_order = 11;
set module_time_section = "two";
 
property string[] module_poweredby_group {
    des = "Powered By";
    grouptype = "module";
}
set module_poweredby_group = ["module_poweredby_show", "module_poweredby_order", "module_poweredby_section"];
property bool module_poweredby_show    { grouped = 1; }
property int  module_poweredby_order   { grouped = 1; }
property string module_poweredby_section { grouped = 1; }
 
set module_poweredby_show = true;
set module_poweredby_order = 12;
set module_poweredby_section = "two";
 
property string[] module_customtext_group {
    des = "Custom Text";
    grouptype = "module";
}
set module_customtext_group = ["module_customtext_show", "module_customtext_order", "module_customtext_section"];
property bool module_customtext_show    { grouped = 1; }
property int  module_customtext_order   { grouped = 1; }
property string module_customtext_section { grouped = 1; }
 
set module_customtext_show = false;
set module_customtext_order = 13;
set module_customtext_section = "one";
 
property string[] module_credit_group {
    des = "Layout Credit";
    grouptype = "module";
}
set module_credit_group = ["module_credit_show", "module_credit_order", "module_credit_section"];
property bool module_credit_show    { grouped = 1; }
property int module_credit_order    { grouped = 1; }
property string module_credit_section { grouped = 1; }
 
set module_credit_show = true;
set module_credit_order = 14;
set module_credit_section = "one";
 
property string{}[] layout_authors { noui = 1;  des = "The layout author (or authors). Accepts 'name', 'url', 'type'"; }
property string{}[] layout_resources { noui = 1; des = "Resources used in the layout. Accepts 'name', 'url'"; }
 
set layout_authors = [];
set layout_resources = [];
 
property string[] module_search_group {
    des = "Search";
    grouptype = "module";
}
set module_search_group = ["module_search_show", "module_search_order", "module_search_section"];
property bool module_search_show { grouped = 1; }
property int module_search_order { grouped = 1; }
property string module_search_section { grouped = 1; }
 
set module_search_show = true;
set module_search_order = 15;
set module_search_section = "one";
 
##===============================
## Journal style - fonts
##===============================

property string font_base {
    des = "Preferred journal font";
    maxlength = 25;
    size = 15;
    note = "For example: Arial or \"Times New Roman\". Leave blank to use the default.";
}
set font_base = "";   # In core, default is not to care. Layouts will probably specify fonts the author likes instead.

property string font_fallback {
    des = "Alternative journal font";
    values = "sans-serif|Sans-serif|serif|Serif|cursive|Cursive|monospace|Monospaced||Use browser's default";
    note = "This general style will serve as a fallback if your preferred font is unavailable.";
}
set font_fallback = ""; # Default in core is to let the browser handle it.

property string font_base_size {
    des = "Size of base font";
    size = 3;
}
 
property string font_base_units {
    des = "Units for base font size";
    values = "em|em|ex|ex|%|%|pt|pt|px|px";
}
 
property string font_journal_title {
    des = "Preferred font for journal titles";
    maxlength = 25;
    size = 15;
    note = "For example: Arial or \"Times New Roman\". Leave blank to use the default.";
}
 
property string font_journal_title_size {
    des = "Size of title font";
    size = 3;
}
 
property string font_journal_title_units {
    des = "Units for title size";
    values = "em|em|ex|ex|%|%|pt|pt|px|px";
}
 
property string font_journal_subtitle {
    des = "Preferred font for journal subtitles";
    maxlength = 25;
    size = 15;
    note = "For example: Arial or \"Times New Roman\". Leave blank to use the default.";
}
 
property string font_journal_subtitle_size {
    des = "Size of subtitle font";
    size = 3;
}
 
property string font_journal_subtitle_units {
    des = "Units for subtitle size";
    values = "em|em|ex|ex|%|%|pt|pt|px|px";
}
 
property string font_entry_title {
    des = "Preferred font for entry titles";
    maxlength = 25;
    size = 15;
    note = "For example: Arial or \"Times New Roman\". Leave blank to use the default.";
}
 
property string font_entry_title_size {
    des = "Size of entry title font";
    size = 3;
}
 
property string font_entry_title_units {
    des = "Units for entry title size";
    values = "em|em|ex|ex|%|%|pt|pt|px|px";
}
 
property string font_module_heading {
    des = "Preferred font for module headings";
    maxlength = 25;
    size = 15;
    note = "For example: Arial or \"Times New Roman\". Leave blank to use the default.";
}
 
property string font_module_heading_size {
    des = "Size of module heading font";
    size = 3;
}
 
property string font_module_heading_units {
    des = "Units for module heading size";
    values = "em|em|ex|ex|%|%|pt|pt|px|px";
}
 
property string font_module_text {
    des = "Preferred font for module text";
    maxlength = 25;
    size = 15;
    note = "For example: Arial or \"Times New Roman\". Leave blank to use the default.";
}
 
property string font_module_text_size {
    des = "Size of module text font";
    size = 3;
}
 
property string font_module_text_units {
    des = "Units for module text size";
    values = "em|em|ex|ex|%|%|pt|pt|px|px";
}
 
##===============================
## Journal style - images
##===============================

property string[] image_background_page_group {
    des = "Background image";
    grouptype = "image";
}
set image_background_page_group = [ "image_background_page_url", "image_background_page_repeat", "image_background_page_position" ];
property string image_background_page_url {
    grouped = 1;
}
property string image_background_page_repeat {
    values = "repeat|tile image|no-repeat|don't tile|repeat-x|tile horizontally|repeat-y|tile vertically";
    grouped = 1;
}
property string image_background_page_position {
    values = "top left|top left|top center|top center|top right|top right|center left|center left|center center|center|center right|center right|bottom left|bottom left|bottom center|bottom center|bottom right|bottom right";
    grouped = 1;
}
 
property string[] image_background_header_group {
    des = "Header image";
    grouptype = "image";
}
set image_background_header_group = [ "image_background_header_url", "image_background_header_repeat", "image_background_header_position" ];
property string image_background_header_url {
    grouped = 1;
}
property string image_background_header_repeat {
    values = "repeat|tile image|no-repeat|don't tile|repeat-x|tile horizontally|repeat-y|tile vertically";
    grouped = 1;
}
property string image_background_header_position {
    values = "top left|top left|top center|top center|top right|top right|center left|center left|center center|center|center right|center right|bottom left|bottom left|bottom center|bottom center|bottom right|bottom right";
    grouped = 1;
}
# FIXME: This should be grouped, but then it doesn't get a label.
property int image_background_header_height {
    des = "The height of your header, in pixels.  Use 0 for default.";
    example = "50";
    size = 6;    
}
property string[] image_background_entry_group {
    des = "Entry background image";
    grouptype = "image";
}
set image_background_entry_group = [ "image_background_entry_url", "image_background_entry_repeat", "image_background_entry_position" ];
property string image_background_entry_url {
    grouped = 1;
}
property string image_background_entry_repeat {
    values = "repeat|tile image|no-repeat|don't tile|repeat-x|tile horizontally|repeat-y|tile vertically";
    grouped = 1;
}
property string image_background_entry_position {
    values = "top left|top left|top center|top center|top right|top right|center left|center left|center center|center|center right|center right|bottom left|bottom left|bottom center|bottom center|bottom right|bottom right";
    grouped = 1;
}
 
property string[] image_background_module_group {
    des = "Module background image";
    grouptype = "image";
}
set image_background_module_group = [ "image_background_module_url", "image_background_module_repeat", "image_background_module_position" ];
property string image_background_module_url {
    grouped = 1;
}
property string image_background_module_repeat {
    values = "repeat|tile image|no-repeat|don't tile|repeat-x|tile horizontally|repeat-y|tile vertically";
    grouped = 1;
}
property string image_background_module_position {
    values = "top left|top left|top center|top center|top right|top right|center left|center left|center center|center|center right|center right|bottom left|bottom left|bottom center|bottom center|bottom right|bottom right";
    grouped = 1;
}
 
property string[] separator_image_group {
    des = "Separator";
    grouptype = "image";
}
set separator_image_group = [ "separator_image_url", "separator_image_repeat", "separator_image_position" ];
property string separator_image_url {
    grouped = 1;
}
property string separator_image_repeat {
    values = "repeat|tile image|no-repeat|don't tile|repeat-x|tile horizontally|repeat-y|tile vertically";
    grouped = 1;
}
property string separator_image_position {
    values = "top left|top left|top center|top center|top right|top right|center left|center left|center center|center|center right|center right|bottom left|bottom left|bottom center|bottom center|bottom right|bottom right";
    grouped = 1;
}
 
##===============================
## Journal style - basic colors
##===============================

property Color color_comment_bar {
   des = "Color of comment bar header";
}
 
set color_comment_bar = "#d0d0ff";

property string custom_control_strip_colors {
    des = "Navigation strip colors";
    values = "off|Do not use custom colors|on_gradient|Use custom colors with a background gradient|on_no_gradient|Use custom colors without a background gradient";
    note = "Custom colors can be set in the \"Colors\" section.";
}
set custom_control_strip_colors = "off";
 
property Color control_strip_bgcolor {
    des = "Background color of navigation strip";
    note = "If you don't enable custom navigation strip colors in the \"Presentation\" section, this won't have any effect.";
}
set control_strip_bgcolor = "";
 
property Color control_strip_fgcolor {
    des = "Text color of navigation strip";
    note = "If you don't enable custom navigation strip colors in the \"Presentation\" section, this won't have any effect.";
}
set control_strip_fgcolor = "";
 
property Color control_strip_bordercolor {
    des = "Border color of navigation strip";
    note = "If you don't enable custom navigation strip colors in the \"Presentation\" section, this won't have any effect.";
}
set control_strip_bordercolor = "";
 
property Color control_strip_linkcolor {
    des = "Link color of navigation strip";
    note = "If you don't enable custom navigation strip colors in the \"Presentation\" section, this won't have any effect.";
}
set control_strip_linkcolor = "";
 
 
##===============================
## Journal style - element colors
##===============================

property Color color_entry_background   { des = "Entry background color"; }
property Color color_entry_text         { des = "Entry text color"; }
property Color color_entry_link         { des = "Entry link color"; }
property Color color_entry_link_active  { des = "Entry active link color"; }
property Color color_entry_link_hover   { des = "Entry hover link color"; }
property Color color_entry_link_visited { des = "Entry visited link color"; }
property Color color_entry_border       { des = "Entry border color"; }
property Color color_entry_title        { des = "Entry title color"; }
property Color color_entry_title_background { des = "Entry title background color"; }
property Color color_entry_interaction_links { des = "Color of interaction links and text"; }
property Color color_entry_interaction_links_background { des = "Entry interaction links background color"; }
property Color color_entry_interaction_links_hover { des = "Color of interaction links and text - hover"; }
property Color color_entry_interaction_links_active { des = "Color of interaction links and text - active"; }
property Color color_entry_interaction_links_visited { des = "Color of interaction links and text - visited"; }
 
 
set color_entry_background = "";
set color_entry_text = "";
set color_entry_link = "";
set color_entry_link_active = "";
set color_entry_link_hover = "";
set color_entry_link_visited = "";
set color_entry_border = "";
set color_entry_title = "";
set color_entry_title_background = "";
set color_entry_interaction_links = "";
set color_entry_interaction_links_background = "";
set color_entry_interaction_links_hover = "";
set color_entry_interaction_links_active = "";
set color_entry_interaction_links_visited = "";
 
##===============================
## Journal style - element colors
##===============================

property Color color_comment_title_background { des = "Comment title background color"; }
 
set color_comment_title_background = "";
 
##===============================
## Journal style - element colors
##===============================

property Color color_page_background { des = "Page background color"; }
property Color color_page_text { des = "Page text color"; }
property Color color_page_link { des = "Page link color"; }
property Color color_page_link_active { des = "Page active link color"; }
property Color color_page_link_hover { des = "Page hover link color"; }
property Color color_page_link_visited { des = "Page visited link color"; }
property Color color_page_border { des = "Page border color"; }
property Color color_page_title { des = "Page title color"; }
 
set color_page_background = "";
set color_page_text = "";
set color_page_link = "";
set color_page_link_active = "";
set color_page_link_hover = "";
set color_page_link_visited = "";
set color_page_border = "";
set color_page_title = "";
 
property Color color_header_background { des = "Page header background color";}
property Color color_header_link { des = "Page header link color"; }
property Color color_header_link_active { des = "Page header active link color"; }
property Color color_header_link_hover { des = "Page header hover link color"; }
property Color color_header_link_visited { des = "Page header visited link color"; }
property Color color_footer_background { des = "Page footer background color"; }
property Color color_footer_link { des = "Page footer link color"; }
property Color color_footer_link_active { des = "Page footer active link color"; }
property Color color_footer_link_hover { des = "Page footer hover link color"; }
property Color color_footer_link_visited { des = "Page footer visited link color"; }
 
set color_header_background = "";
set color_header_link = "";
set color_header_link_active = "";
set color_header_link_hover = "";
set color_header_link_visited = "";
set color_footer_background = "";
set color_footer_link = "";
set color_footer_link_active = "";
set color_footer_link_hover = "";
set color_footer_link_visited = "";
 
property Color color_module_background { des = "Module background color"; }
property Color color_module_text { des = "Module text color"; }
property Color color_module_link { des = "Module link color"; }
property Color color_module_link_active { des = "Module active link color"; }
property Color color_module_link_hover { des = "Module hover link color"; }
property Color color_module_link_visited { des = "Module visited link color"; }
property Color color_module_border { des = "Module border color"; }
property Color color_module_title { des = "Module title color"; }
property Color color_module_title_background { des = "Module title background color"; }
 
set color_module_background = "";
set color_module_text = "";
set color_module_link = "";
set color_module_link_active = "";
set color_module_link_hover = "";
set color_module_link_visited = "";
set color_module_title = "";
set color_module_title_background = "";
set color_module_border = "";
 
# Initialize custom colors, these properties can be selected or overwritten by layout layers, but should not normally be exposed in the wizard.

property string custom_foreground_element {
    des = "Part of entry that will display foreground color, if you have custom colors enabled";
    values = "subject|Entry title|userpic_border|Icon border|postername|Posted by|entry|Entry text|metadata|Mood/music/location|bottom_links|Comment links|custom|Custom template";
    noui = 1;
    }
property string custom_background_element {
    des = "Part of entry that will display background color, if you have custom colors enabled";
    values = "subject|Entry header background|userpic_background|Icon background|entry_border|Entry border|metadata_background|Mood/music/location section|footer_border|Entry footer|custom|Custom template";
    noui = 1;
    }
property string custom_colors_template {
    des = "Your own CSS to use for custom color";
    doc = "Enter a CSS statement with the class or id of CSS elements you want to use for foreground and background, and any CSS instructions.
           Use %%foreground%% for the custom foreground color, and %%background%% for the custom background color
           Each CSS rule should begin with %%new%%";
    example = "%%new%% div.special {border: 1px dotted %%background%%; font-size: 120%; color: %%foreground%%}";
    noui = 1;
    }
 
set custom_foreground_element = "";
set custom_background_element = "";
set custom_colors_template = "";
 
##===============================
## Custom CSS
##===============================

property bool external_stylesheet {
    des = "Use links for external stylesheets";
    note = "If true, a stylesheet link element will point to a file containing the layout's CSS data.";
    noui = 1;
}
 
property bool include_default_stylesheet {
    des = "Use layout's stylesheet(s)";
    note = "Disable this only if you want to re-style this layout completely from scratch using a custom stylesheet.";
}
 
property string linked_stylesheet {
    des = "Custom stylesheet URL";
    note = "If you have a custom external stylesheet that you'd like to use, enter its URL here.";
}
 
property string custom_css {
    des = "Use embedded CSS";
    note = "If you'd like to add custom CSS to this style, enter it here.";
    cols = 50;
    rows = 20;
    string_mode = "css";
}
 
set external_stylesheet = false;
set include_default_stylesheet = true;
set linked_stylesheet = "";
set custom_css = "";
 
##===============================
## Text - main navigation
##===============================

property string text_view_recent {
    des = "Text used to link to the 'Recent Entries' view";
    maxlength = 40;
    "size" = 15;
    example = "Recent Entries";
}
property string text_view_recent_tagged {
    des = "Text used to link to a tag-filtered version of the 'Recent Entries' view";
    maxlength = 40;
    "size" = 15;
    example = "Entries tagged with ";
}
property string text_view_friends {
    des = "Text used to link to the 'Reading' view";
    maxlength = 40;
    "size" = 15;
    example = "My Reading Page";
}
property string text_view_friends_comm {
    des = "Text used to link to the 'Reading' view for a community";
    maxlength = 40;
    "size" = 15;
    example = "Members' Journals";
}
property string text_view_network {
    des = "Title of the 'Network' view";
    maxlength = 40;
    "size" = 15;
    example = "Network";
}
property string text_view_friends_filter {
    des = "Title of a Friends page with an unnamed filter in effect";
    maxlength = 40;
    "size" = 15;
    example = "Subscriptions (Custom filter)";
}
property string text_view_network_filter {
    des = "Title of a Network page with an unnamed filter in effect";
    maxlength = 40;
    "size" = 15;
    example = "Network (Custom filter)";
}
property string text_view_archive {
    des = "Text used to link to the 'Archive' view";
    maxlength = 40;
    "size" = 15;
    example = "Journal Archive";
}
property string text_view_userinfo {
    des = "Text used to link to the 'User Information' view";
    maxlength = 40;
    "size" = 15;
    example = "My Profile";
}
 
property string text_view_memories {
    des = "Text used to link to the 'Memories' view";
    maxlength = 40;
    "size" = 15;
    example = "My Memories";
}
 
property string text_view_tags {
    des = "Text used to link to the 'Tags' page";
    maxlength = 40;
    "size" = 15;
    example = "My Tags";
}
 
set text_view_recent = "Recent Entries";
set text_view_recent_tagged = "Entries tagged with ";
set text_view_friends = "Reading";
set text_view_friends_comm = "Reading";
set text_view_friends_filter = "Reading (Custom filter)";
set text_view_network = "Network";
set text_view_network_filter = "Network (Custom filter)";
set text_view_archive = "Archive";
set text_view_userinfo = "Profile";
set text_view_memories = "Memories";
set text_view_tags = "Tags";
 
property string text_skiplinks_back {
    des = "Text to show in a link to skip back through entries";
    maxlength = 20;
    "size" = 15;
    example = "Go back #";
    note = "Include a # character to insert the number of entries that will be viewable when skipping back.";
}
property string text_skiplinks_forward {
    des = "Text to show in a link to skip forward through entries";
    maxlength = 20;
    "size" = 15;
    example = "Go forward #";
    note = "Include a # character to insert the number of entries that will be viewable when skipping forward.";
}
property string text_page_top {
    des = "Text to show in a link which takes you back to the top of the page";
    maxlength = 20;
    "size" = 15;
    example = "Top of page";
}
 
set text_skiplinks_back="Previous #";
set text_skiplinks_forward="Next #";
set text_page_top = "Top of page";
##===============================
## Text - metadata
##===============================
property string text_meta_music {
    des = "Text for 'Current Music'";
}
set text_meta_music = "Current Music";
 
property string text_meta_mood {
    des = "Text for 'Current Mood'";
}
set text_meta_mood = "Current Mood";
 
property string text_meta_location {
    des = "Text for 'Current Location'";
}
set text_meta_location = "Current Location";
 
property string text_meta_groups {
    des = "Text for 'Custom Access Groups'";
}
set text_meta_groups = "Custom Access Groups";
 
property string text_meta_xpost {
    des = "Text for 'Crossposts'";
}
set text_meta_xpost = "Crossposts";
 
property string text_tags {
    des = "Text for 'Tags' header for an entry";
    example = "Tags:";
}
set text_tags = "Tags:";
 
##===============================
## Text - module headings
##===============================

property string text_module_links {
    des = "Text used  for the heading of the links list";
    maxlength = 50;
    size = 20;
    example = "Links";
}
 
property string text_module_pagesummary {
    des = "Text used for the heading for the Page Summary navigation section";
    maxlength = 100;
    size = 30;
    example = "Entries on This Page";
}
 
property string text_module_syndicate {
    des = "Text used for the heading for the Syndication navigation section";
    maxlength = 50;
    size = 20;
    example = "Subscribe to my feeds";
}
 
property string text_module_tags {
    des = "Text for the 'Tags' heading in the navigation area";
    maxlength = 50;
    size = 20;
    example = "My tags";
}
 
property string text_module_popular_tags {
    des = "Text for the 'Most Popular Tags' heading in the navigation area (used when there are more tags than shown)";
    maxlength = 50;
    size = 20;
    example = "Most Popular Tags";
}
 
property string text_module_active_entries {
    des = "Text for the 'Active Entries' heading";
    maxlength = 50;
    size = 20;
    example = "Active Entries";
}
 
property string text_module_customtext {
    des = "Text for the 'Custom text' heading";
    maxlength = 50;
    size = 20;
    example = "Custom text";
}
 
property string text_module_customtext_content {
    des = "Text for the 'Custom text' box";
    cols = 50;
    rows = 10;
    string_mode = "html";
}
 
property string text_module_customtext_url {
    des = "URL for 'Custom text' header link";
    maxlength = 100;
    size = 20;
    example = "http://www.dreamwidth.org";
}
 
property string text_module_credit {
    des = "Text for the 'Layout Credit' module heading";
    maxlength = 50;
    size = 20;
    example = "Layout Credit";
}
 
set text_module_links = "Links";
set text_module_pagesummary = "Page Summary";
set text_module_syndicate = "Syndicate";
set text_module_tags = "Tags";
set text_module_popular_tags = "Most Popular Tags";
set text_module_active_entries = "Active Entries";
set text_module_credit = "Layout Credit";
 
set text_module_customtext = "Custom text";
set text_module_customtext_content = "";
set text_module_customtext_url = "";
 
##===============================
## Text - user linkbar
##===============================
property string text_user_manage_membership {
    des = "Link text to manage your membership to a community journal";
    example = "Membership";
}
 
property string text_user_trust {
    des = "Link text to manage your access settings for this journal";
    example = "Manage Access";
}
 
property string text_user_watch {
    des = "Link text to manage your subscription settings for this journal";
    example = "Manage Subscription";
}
 
property string text_user_post_entry {
    des = "Link text to post an entry to this journal";
    example = "Post Entry";
}
 
property string text_user_message {
    des = "Link text to send a message to this user";
    example = "Send Message";
}
 
property string text_user_track {
    des = "Link text to track new entries made to this journal";
    example = "Track Entries";
}
 
property string text_user_memories {
    des = "Link text to memories saved by this journal";
    example = "Memories";
}
 
property string text_user_tell_friend {
    des = "Link text to send other people a link to this journal";
    example = "Share Journal Link";
}
 
set text_user_manage_membership = "Membership";
set text_user_trust = "Manage Access";
set text_user_watch = "Manage Subscription";
set text_user_post_entry = "Post";
set text_user_message = "Send Message";
set text_user_track = "Track Entries";
set text_user_memories = "Memories";
set text_user_tell_friend = "Share Journal Link";
 
 
##===============================
## Text - entry comment-related
##===============================

property string text_post_comment {
    des = "Link text to leave a comment";
    example = "Reply";
}
property string text_max_comments {
    des = "Text when entry has reached a comment maximum";
    example = "Maximum comments reached";
}
property string text_read_comments {
    des = "Link text to read comments";
    format = "plurals";
    example = "1 comment // # comments";
}
set text_post_comment = "Reply";
set text_max_comments = "Maximum comments reached";
set text_read_comments = "1 comment // # comments";

property string text_post_comment_friends {
    des = "Link text to leave a comment from your reading page";
    example = "Reply";
}
property string text_read_comments_friends {
    des = "Link text to read comments from your reading page";
    format = "plurals";
    example = "1 comment // # comments";
}
set text_post_comment_friends = "Reply";
set text_read_comments_friends = "1 comment // # comments";

property string text_read_comments_threads {
    des = "Link text to read comment threads from an entry";
    format = "plurals";
    example = "1 response // # responses";
}
set text_read_comments_threads = "1 response // # responses";

property string text_comments_disabled_maintainer {
    des = "Text shown instead of comments if a community maintainer has disabled comments";
    example = "Comments disabled by a maintainer of this community";
}
set text_comments_disabled_maintainer = "Comments disabled by a maintainer of this community";
 
##===============================
## Text - entry actions
##===============================

property string text_entry_prev {
    des = "Text to link to the previous entry";
    example = "\"Previous Entry\" or \"Previous\"";
}
 
property string text_entry_next {
    des = "Text to link to the next entry";
    example = "\"Next Entry\" or \"Next\"";
}
 
property string text_edit_entry { des = "Text to edit an entry"; }
 
property string text_edit_tags { des = "Text to edit tags for an entry"; }
 
property string text_tell_friend {
    des = "Text to tell someone about an entry";
    example = "\"Share This Entry\" or \"Share\"";
}
 
property string text_mem_add {
    des = "Text to add an entry into memories";
    example = "\"Add Memory\" or \"Memory\"";
}
 
property string text_watch_comments { des = "Text to track events on an entry"; }
 
property string text_unwatch_comments { des = "Text to stop tracking events on an entry"; }
 
property string text_permalink {
    des = "Text for an entry's permanent link";
    note = "This is displayed when the entry subject contains links";
    maxlength = 50;
    size = 20;
}
 
property string text_stickyentry_subject { des = "Text that appears before the subject of the sticky entry"; }
 
set text_entry_prev = "Previous Entry";
set text_entry_next = "Next Entry";
set text_edit_entry = "Edit Entry";
set text_edit_tags = "Edit Tags";
set text_tell_friend = "Share This Entry";
set text_mem_add = "Add Memory";
set text_watch_comments = "Track This";
set text_unwatch_comments = "Untrack This";
set text_permalink = "Link";
set text_stickyentry_subject = "Sticky: ";
 
##===============================
## Text - comment actions
##===============================

property string text_comment_reply {
    des = "Text to link to reply for comment";
    example = "Reply to this";
    maxlength = "50";
}
property string text_comment_frozen {
    des = "Text to replace reply link with if comment is frozen";
    example = "Replies frozen";
    maxlength = "50";
}
property string text_comment_parent {
    des = "Text to link to parent comment of current comment";
    example = "Parent";
    maxlength = "50";
}
property string text_comment_link {
    des = "Text to link to the comment";
    example = "Link";
    maxlength = "50";
}
property string text_comment_thread {
    des = "Text to link to the thread stemming from the comment";
    example = "Thread";
    maxlength = "50";
}
property string text_comment_threadroot {
    des = "Text to link to the entire thread this comment is part of";
    example = "Thread from start";
    maxlength = "50";
}
property string text_comment_expand {
    des = "Text to expand a collapsed comment thread";
    example = "Expand";
    maxlength = "50";
}
set text_comment_reply = "Reply";
set text_comment_frozen = "Frozen";
set text_comment_parent = "Parent";
set text_comment_link = "Link";
set text_comment_thread = "Thread";
set text_comment_threadroot = "Thread from start";
set text_comment_expand = "Expand";
 
##===============================
## Text - tags 
##===============================

property string text_tags_page_header {
    des = "Text for the header of the Tags page";
    example = "Visible Tags";
}
 
property string text_tag_uses {
    des = "Text to show how many times a tag has been used";
    format = "plurals";
    example = "1 use // # uses";
}
 
property string text_tagsmultilevel_delimiter {
    des = "Text to use as a delimiter for multi-level tags";
    example = ":";
}
 
property string text_tags_manage {
    des = "Text to use to link to this journal's tags management page.";
    example = "Manage Tags";
}
 
property string text_tags_item_sep {
    des = "Text to separate items in a list of tags";
    example = ",";
}
 
set text_tags_page_header = "Visible Tags";
set text_tag_uses = "1 use // # uses";
set text_tagsmultilevel_delimiter = ":";
set text_tags_manage = "Manage Tags";
set text_tags_item_sep = ",";
 
##================================================================
## Text - misc strings, not popular enough to expose in wizard
##================================================================

property string text_default_separator {
    des = "Text used to separate items";
    maxlength = 5;
    "size" = 5;
    example = " | ";
}
 
property string text_view_month {
    des = "Text used to link to a list of subjects for a month";
    maxlength = 20;
    "size" = 15;
    example = "View Subjects";
}
 
property string text_generated_on {
    noui = 1;
    des = "Text used to describe at what time the page was generated";
    example = "Generated on";
}
 
property string text_powered_by { noui = 1; des = "Text to describe what site hosts the journal"; }
 
property string text_layout_authors { noui = 1; des = "Text label for the layout author"; }
property string text_layout_resources { noui = 1; des = "Text label for the layout resources"; }
 
property string text_posting_in {
    des = "Text when user is posting in a community";
    example = "posting in ";
}
 
 
property string text_nosubject {
    des = "Text to replace a subject line when no subject is specified";
    maxlength = 20;
    size = 10;
    example = "No Subject";
    note = "This only appears in places where a subject line is required, such as on a month view.";
}
 
property string text_calendar_num_entries {
    des = "Text to show how many entries were made on a particular day";
    format = "plurals";
    example = "1 entry // # entries";
}
 
set text_default_separator = " | ";
set text_view_month = "View Subjects";
set text_generated_on = "Page generated";
set text_powered_by = "Powered by";
set text_layout_authors = "Layout:";
set text_layout_resources = "Resources:";
set text_posting_in = " posting in ";
set text_nosubject = "(no subject)";
set text_calendar_num_entries = "1 entry // # entries";

property bool all_entrysubjects {
    des = "For all entries, display the replacement text when no subject is specified";
    note = "If set, all entries will display the text_nosubject where no subject is specified.  Otherwise, it only appears in places where a subject line is required, such as on a month view.";
    noui = 1;
}
 
property bool all_commentsubjects {
    des = "For all comments, display the replacement text when no subject is specified";
    note = "If set, comments will display the text_nosubject where no subject is specified.";
    noui = 1;
}
 
property string text_noentries_recent {
    des = "Text to display when there are no entries on the recent or friends views";
    maxlength = 255;
    size = 50;
}
 
property string text_noentries_day {
    des = "Text to display when there are no entries on the day view";
    maxlength = 255;
    size = 50;
}
 
property string text_month_screened_comments { des = "Text to indicate there are screened comments"; }
 
property string text_month_form_btn { des = "Text used for Submit button in MonthPage selector"; }
 
property string text_replyform_header { des = "Text for the heading above the form on the reply page"; }
 
property string text_comment_posted { des = "Text to display when a comment has just been posted by the user"; }
 
set all_entrysubjects = true;
set all_commentsubjects = false;
set text_noentries_recent = "There are no entries to display.";
set text_noentries_day = "There were no entries on this day.";
set text_month_screened_comments = "w/ Screened";
set text_month_form_btn = "View";
set text_replyform_header = "Comment Form";
set text_comment_posted = "Comment successfully posted.";
 
property string comment_page_prev {
    des = "Text to link the previous comment page";
    example = "Previous Comment Page";
}
property string comment_page_next {
    des = "Text to link the previous comment page";
    example = "Previous Comment Page";
}
set comment_page_prev = "&lt;&lt";
set comment_page_next = "&gt;&gt";
 
 
property string text_day_prev {
    des = "Text to link to the previous day";
    example = "Previous Day";
    maxlength = 20;
}
property string text_day_next {
    des = "Text to link to the next day";
    example = "Next Day";
    maxlength = 20;
}
set text_day_prev = "Previous Day";
set text_day_next = "Next Day";
 
property string text_comment_from {
    des = "Text of the 'from' header in comments";
    example = "From:";
    maxlength = "20";
}
property string text_comment_date {
    des = "Text of the 'date' header in comments";
    example = "Date:";
    maxlength = "20";
}
property string text_comment_ipaddr {
    des = "Text of the 'IP Address' header in comments";
    example = "IP Address:";
    maxlength = "20";
}
property string text_comment_edittime {
    des = "Text of the 'Edited' string at the bottom of edited comments";
    example = "Edited";
    maxlength = "20";
}
set text_comment_from = "From:";
set text_comment_date = "Date:";
set text_comment_ipaddr = "IP Address:";
set text_comment_edittime = "Edited";
 
property string text_screened {
   des = "The text used when a comment is screened";
   example = "(screened) ";
}
 
property string text_deleted {
   des = "The text used when a comment has been deleted";
   example = "(deleted comment)";
}
 
property string text_fromsuspended {
   des = "The text used when the comment's author has been suspended";
   example = "(reply from suspended user)";
}
 
property string text_frozen {
   des = "The text used when a comment is frozen";
   example = "(frozen) ";
}
 
property string text_poster_anonymous {
    des = "The placeholder used when something is posted by an anonymous user";
    example = "(Anonymous)";
}
 
property string text_openid_from {
  des = "Text to indicate which site this OpenID account originally posted a comment on";
  example = "from";
  noui = 1;
}
 
property string text_reply_back {
    des = "Text to link back to the single entry view from the read comments page";
    example = "Read Comments";
    maxlength = "50";
}
 
property string text_reply_nocomments_header {
    des = "Heading text that explains that comments are disabled";
    example = "Comments Disabled:";
    maxlength = "50";
}
 
property string text_reply_nocomments {
    des = "Text that explains that comments are not allowed for this post.";
    example = "Comments have been disabled for this post.";
    maxlength = "100";
}
 
property string text_website_default_name { noui = 1; des = "If an account's website is specified, but there's no website name, use this text instead"; }
 
set text_screened = "(screened) ";
set text_deleted = "(deleted comment)";
set text_fromsuspended = "(reply from suspended user)";
set text_frozen = "(frozen) ";
set text_poster_anonymous = "(Anonymous)";
set text_openid_from = "from";
set text_reply_back = "Read Comments";
set text_reply_nocomments_header = "Comments Disabled:";
set text_reply_nocomments = "Comments have been disabled for this post.";
set text_website_default_name = "My Website";
 
property string text_icon_alt_protected { noui = 1; des = "Alternative text for icons of friends-only entries"; }
property string text_icon_alt_private   { noui = 1; des = "Alternative text for icons of private entries"; }
property string text_icon_alt_groups    { noui = 1; des = "Alternative text for icons of custom friends group entries"; }
property string text_icon_alt_nsfw      { noui = 1; des = "Alternative text for icons of NSFW entries"; }
property string text_icon_alt_18        { noui = 1; des = "Alternative text for icons of 18+ entries"; }
property string text_icon_alt_sticky_entry        { noui = 1; des = "Alternative text for icons of sticky entries"; }
set text_icon_alt_protected = "[protected post]";
set text_icon_alt_private = "[private post]";
set text_icon_alt_groups = "[custom friends groups post]";
set text_icon_alt_nsfw = "[NSFW]";
set text_icon_alt_18 = "[18+]";
set text_icon_alt_sticky_entry = "[sticky entry]";
 
property string text_multiform_check { des = "Text beside a comment multi-action checkbox"; }
 
property string text_multiform_des { des = "Text on the multiform action line."; }
 
property string text_multiform_btn { des = "Text on the multiform action button."; }
 
property string text_multiform_opt_unscreen { des = "Text for the comment unscreening action"; }
 
property string text_multiform_opt_unscreen_to_reply { des = "Text for the comment unscreening action it's showed instead of reply-link for screened comments"; }
 
property string text_multiform_opt_screen { des = "Text for the comment screening action"; }
 
property string text_multiform_opt_unscreen_to_reply { des = "Text for the comment unscreening action it's showed instead of reply-link for screened comments"; }
 
property string text_multiform_opt_unfreeze { des = "Text for the comment unfreezing action"; }
 
property string text_multiform_opt_freeze { des = "Text for the comment freezing action"; }
 
property string text_multiform_opt_delete { des = "Text for the comment delete action"; }
 
property string text_multiform_opt_deletespam { des = "Text for the comment delete and mark as spam action"; }
 
property string text_multiform_conf_delete { des = "Text for the confirming mass-delete action"; }
 
property string text_multiform_opt_track { des = "Text for the comment tracking action"; }
 
property string text_multiform_opt_untrack { des = "Text for the comment untracking action"; }
 
property string text_multiform_opt_edit { des = "Text for the comment editing action"; }
 
set text_multiform_conf_delete = "Delete selected comments?";
set text_multiform_opt_track = "Track This";
set text_multiform_opt_untrack = "Untrack This";
set text_multiform_opt_edit = "Edit";
set text_multiform_check = "Select:";
set text_multiform_des = "Mass action on selected comments:";
set text_multiform_btn = "Perform Action";
set text_multiform_opt_unscreen = "Unscreen";
set text_multiform_opt_unscreen_to_reply = "Unscreen to reply";
set text_multiform_opt_screen = "Screen";
set text_multiform_opt_unscreen_to_reply = "Unscreen to reply";
set text_multiform_opt_unfreeze = "Unfreeze";
set text_multiform_opt_freeze = "Freeze";
set text_multiform_opt_delete = "Delete";
set text_multiform_opt_deletespam = "Delete as Spam";
 
##===============================
## Language defaults (noui)
##===============================
property string lang_current { noui = 1; des = "Current language code.  So layouts can change date/time formats more safely if they want."; }
set lang_current = "en";  # core is English.

property string lang_fmt_date_short { noui = 1; des = "Short date format.  All numeric."; }
 
property string lang_fmt_date_med { noui = 1; des = "Medium date format.  Abbreviated month name, no day of the week."; }
 
property string lang_fmt_date_med_day { noui = 1; des = "Medium date format with day of week.  Abbreviated month name and abbreviated day of the week."; }
 
property string lang_fmt_date_long { noui = 1; des = "Long date format.  With full month name, but no day of the week."; }
 
property string lang_fmt_date_long_day { noui = 1; des = "Long date format.  With full month name and full day of the week."; }
 
property string lang_fmt_time_short { noui = 1; des = "Time format."; }
 
property string lang_fmt_time_short_24 { noui = 1; des = "Time format (24 hours)."; }
 
property string lang_fmt_month_short { noui = 1; des = "Short month format."; }
 
property string lang_fmt_month_med { noui = 1; des = "Medium month format."; }
 
property string lang_fmt_month_long { noui = 1; des = "Long month format."; }
 
set lang_fmt_date_short = "%%m%%/%%d%%/%%yy%%";
set lang_fmt_date_med = "%%mon%%. %%dayord%%, %%yyyy%%";
set lang_fmt_date_med_day = "%%da%%, %%mon%%. %%dayord%%, %%yyyy%%";
set lang_fmt_date_long = "%%month%% %%dayord%%, %%yyyy%%";
set lang_fmt_date_long_day = "%%day%%, %%month%% %%dayord%%, %%yyyy%%";
set lang_fmt_time_short = "%%hh%%:%%min%% %%a%%m";
set lang_fmt_time_short_24 = "%%HH%%:%%min%%";
set lang_fmt_month_short = "%%m%%/%%yy%%";
set lang_fmt_month_med = "%%mon%% %%yyyy%%";
set lang_fmt_month_long = "%%month%% %%yyyy%%";
 
property string[] lang_monthname_long { noui = 1; des = "Months of the year.  Indexed from 1 (January) to 12 (December)."; }
 
property string[] lang_monthname_short { noui = 1; des = "Months of the year, in their short forms.  Indexed from 1 (Jan) to 12 (Dec)."; }
 
property string[] lang_dayname_long { noui = 1; des = "Days of the week.  Indexed from 1 (Sunday) to 7 (Saturday)."; }
 
property string[] lang_dayname_short { noui = 1; des = "Days of the week, in their short forms.  Indexed from 1 (Sun) to 7 (Sat)."; }
 
property string[] lang_dayname_shorter { noui = 1; des = "Days of the week, in their one letter forms.  Indexed from 1 (S) to 7 (S)."; }
 
set lang_monthname_long = [ "", "January",  "February", "March",
                            "April", "May", "June",
                            "July", "August", "September",
                            "October", "November", "December" ];
set lang_monthname_short = [ "", "Jan",  "Feb", "Mar",
                             "Apr", "May", "Jun",
                             "Jul", "Aug", "Sep",
                             "Oct", "Nov", "Dec" ];
set lang_dayname_long = [ "", "Sunday", "Monday",  "Tuesday", "Wednesday",
                          "Thursday", "Friday", "Saturday" ];
set lang_dayname_short = [ "", "Sun", "Mon",  "Tue", "Wed",
                           "Thu", "Fri", "Sat" ];
set lang_dayname_shorter = [ "", "S", "M",  "T", "W",
                             "T", "F", "S" ];
 
###[ global function implementations ]

function prop_init ()
  "This function is the first thing called and is the place to set properties based on the values of other properties.  It's called before the style system looks at its builtin properties, so if you need to conditionally setup something based on your own custom properties, do it here.  You can't print from this function."
{
}
 
function modules_init()
"This function sets up the modules. It is called immediately after prop_init."
{
    if ( $*module_userprofile_show ) { $*module_sections{$*module_userprofile_section}[$*module_userprofile_order]=["userprofile"]; }
    if ( $*module_navlinks_show ) { $*module_sections{$*module_navlinks_section}[$*module_navlinks_order]=["navlinks"]; }
    if ( $*module_calendar_show ) { $*module_sections{$*module_calendar_section}[$*module_calendar_order]=["calendar"]; }
    if ( $*module_links_show ) { $*module_sections{$*module_links_section}[$*module_links_order]=["links"]; }
    if ( $*module_active_show ) { $*module_sections{$*module_active_section}[$*module_active_order]=["active"]; }
    if ( $*module_syndicate_show ) { $*module_sections{$*module_syndicate_section}[$*module_syndicate_order]=["syndicate"]; }
    if ( $*module_tags_show ) { $*module_sections{$*module_tags_section}[$*module_tags_order]=["tags"]; }
    if ( $*module_pagesummary_show ) { $*module_sections{$*module_pagesummary_section}[$*module_pagesummary_order]=["pagesummary"]; }
    if ( $*module_time_show ) { $*module_sections{$*module_time_section}[$*module_time_order]=["time"]; }
    if ( $*module_poweredby_show ) { $*module_sections{$*module_poweredby_section}[$*module_poweredby_order]=["poweredby"]; }
    if ( $*module_customtext_show ) { $*module_sections{$*module_customtext_section}[$*module_customtext_order]=["customtext"]; }
    if ( $*module_credit_show ) { $*module_sections{$*module_credit_section}[$*module_credit_order]=["credit"]; }
    if ( $*module_search_show ) { $*module_sections{$*module_search_section}[$*module_search_order]=["search"]; }
 
    #if ( $*module_%%mod%%_show ) { $*module_sections{$*module_%%mod%%_section }[$*module_%%mod%%_order]=["%%mod%%"]; }
}
 
function print_stylesheet ()
  "Prints a stylesheet, the URL of which can be referenced by [member[Page.stylesheet_url]].  This is another S2 entry point, in addition to [method[Page.print()]]. If you want to hardcode a stylesheet into your layout, override this function."
{
}
 
function print_custom_control_strip_css ()
  "Prints the CSS for custom control strip colors, if the option is enabled. This should be called by print_stylesheet().  If you override this function, you will be changing the design  of what is effectively site navigation, so proceed with caution."
{
    if ($*custom_control_strip_colors != "off") {
        var bool bgcolor = $*control_strip_bgcolor.as_string != "";
        var bool fgcolor = $*control_strip_fgcolor.as_string != "";
        var bool bordercolor = $*control_strip_bordercolor.as_string != "";
        var bool linkcolor = $*control_strip_linkcolor.as_string != "";
        var string bgimage = "";
 
        if ($*custom_control_strip_colors == "on_gradient" and $bgcolor and $fgcolor) {
            var Color blended_color = $*control_strip_bgcolor->blend($*control_strip_fgcolor, 25); # Make the background color 25% like the text color
            $bgimage = palimg_tint("controlstrip/bg.gif", $*control_strip_bgcolor, $blended_color);
        }
 
        if ($bgcolor) {
"""
 
#lj_controlstrip {
    background-color: $*control_strip_bgcolor;
""";
if ($bgimage == "") {
    """    background-image: none;""";
} else {
    """    background-image: url($bgimage);""";
}
"""
}""";
        }
 
        if ($fgcolor or $bordercolor) {
"""
 
#lj_controlstrip td {
""";
if ($fgcolor) {
    """    color: $*control_strip_fgcolor;""";
}
if ($fgcolor and $bordercolor) {
    "\n";
}
if ($bordercolor) {
    """    border-bottom: 1px solid $*control_strip_bordercolor;""";
}
"""
}""";
        }
 
        if ($fgcolor) {
"""
 
#lj_controlstrip_statustext {
    color: $*control_strip_fgcolor;
}""";
        }
 
        if ($linkcolor) {
"""
 
#lj_controlstrip a {
    color: $*control_strip_linkcolor;
}""";
        }
 
        if ($bordercolor) {
"""
 
#lj_controlstrip_user,
#lj_controlstrip_actionlinks,
#lj_controlstrip_search,
#lj_controlstrip_login,
#lj_controlstrip_loggedout_userpic {
    border-right: 1px solid $*control_strip_bordercolor;
}
 
#lj_controlstrip_login td {
    border-bottom: 0;
}
 
#lj_controlstrip td td {
    border-bottom: 0;
}""";
        }
 
        if ($bgcolor and $fgcolor) {
            print control_strip_logged_out_userpic_css();
            print control_strip_logged_out_full_userpic_css();
        }
    }
}
 
 
### Language

function lang_map_plural (int n) : int 
"This function defines plurals for different languages. Use this if you are writing a language layer that needs customization for its plurals."
{
    if ($n == 1) { return 0; } # singular
    return 1;             # plural
}
 
function lang_page_of_pages (int pg, int pgs) [notags] : string
"This function returns the current page number as well as number of available pages. Override for language layers."
{
    return "Page $pg of $pgs";
}
 
function lang_user_wrote(UserLite u) : string
"Returns text describing that the user wrote something. This function is deprecated in favor of print_poster. Override for # language layers, but even better, use print_poster."
{
    if (defined $u) {
        return $u->as_string()+" wrote";
    }
    else {
        return "An anonymous user wrote";
    }
}
 
function lang_at_datetime(DateTime d) : string
"Returns a string saying \"at {the data and time given}\". Used in the core implementation of EntryPage and ReplyPage. i18nc layers should override this."
{
    return "on " + $d->date_format("long") + " at " + $d->time_format();
}
 
function lang_ordinal(int num) [notags] : string
"Make an ordinal number from a cardinal number. Override only for language layers."
{
    if ($num % 100 >= 4 and $num % 100 <= 20) { return $num+"th"; }
    if ($num % 10 == 1) { return $num+"st"; }
    if ($num % 10 == 2) { return $num+"nd"; }
    if ($num % 10 == 3) { return $num+"rd"; }
    return $num+"th";
}
 
function lang_ordinal(string num) [notags, fixed] : string
"Make an ordinal number from a cardinal number.  Don't override this, since the core layer implementation just calls [function[lang_ordinal(int)]], which i18nc layers should override."
{
    return lang_ordinal(int($num));
}
 
 
function lang_viewname(string viewid) [notags] : string
"Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout. Sets strings according to layer or wizard supplied string properties."
{
    if ($viewid == "recent") { return $*text_view_recent; }
    if ($viewid == "archive") { return $*text_view_archive; }
    if ($viewid == "read") { return $*text_view_friends; }
    if ($viewid == "network") { return $*text_view_network; }
    if ($viewid == "day") { return "Day"; }
    if ($viewid == "month") { return "Month"; }
    if ($viewid == "userinfo") { return $*text_view_userinfo; }
    if ($viewid == "entry") { return "Read Comments"; }
    if ($viewid == "reply") { return "Post Comment"; }
    if ($viewid == "tags") { return "$*text_view_tags"; }
    if ($viewid == "memories") { return $*text_view_memories; }
    if ($viewid == "network") { return $*text_view_network; }
    return "Unknown View";
}
 
function lang_metadata_title(string which) [fixed] : string
"Get a human-readable caption for a metadata key. Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout. Sets strings according to layer or wizard supplied string properties."
{
    if ($which == "music") {
        return $*text_meta_music;
    }
    elseif ($which == "mood") {
        return $*text_meta_mood;
    }
    elseif ($which == "location") {
        return $*text_meta_location;
    }
    elseif ($which == "groups") {
        return $*text_meta_groups;
    }
    elseif ($which == "xpost") {
        return $*text_meta_xpost;
    }
    else {
        return $which;
    }
}
 
### Navigation

 
### Image Manipulation

function Image::as_string(string{} opts) [fixed] : string 
"You most likely want to use Image::print instead.  Prints HTML for the image. Using meaningful alttext is strongly recommended to improve the accessibility of your layout. Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    var string img = "";
    if ($opts{"href"} != "") { $img = $img + "<a href=\"" + eurl($opts{"href"}) + "\" $opts{"a_attr"}>"; }
    $img = $img + $this->as_string($opts{"alt"});
    if ($opts{"href"} != "") { $img = $img + "</a>"; }
 
    return $img;
}
 
function Image::as_string(string alttext) [fixed] : string
"You most likely want to use Image::print instead.  Prints HTML for the image with a given alttext. Using meaningful alttext is strongly recommended to improve the accessibility of your layout. Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    return "<img src=\"$.url\" title=\"$.extra{"title"}\" alt=\""
        + ehtml( $alttext )
        + "\" "
        + htmlattr("height", $.height)
        + htmlattr("width", $.width)
        + " />";
}
 
function Image::as_string() [fixed] : string
"You most likely want to use Image::print instead.  Prints HTML for the image. Using meaningful alttext is strongly recommended to improve the accessibility of your layout. Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    # If the image has an attribute "alttext", the alt text has already been escaped, eg. in S2.pm
     return "<img src=\"$.url\" title=\"$.extra{"title"}\" alt=\"$this.alttext\" "
        + htmlattr("height", $.height)
        + htmlattr("width", $.width)
        + " />";
}
 
function Image::print (string{} opts)
"Prints HTML for the image. Using meaningful alttext is strongly recommended to improve the accessibility of your layout. Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    # must do safe here because opts could have the 'a_attr' key set
    print safe $this->as_string($opts);
}
 
function Image::print (string alttext)
"Prints HTML for the image. Using meaningful alttext is strongly recommended to improve the accessibility of your layout. Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    print $this->as_string($alttext);
}
 
function Image::print
"Prints HTML for the image. Using meaningful alttext is strongly recommended to improve the accessibility of your layout. Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    print $this->as_string();
}
 
function userinfoicon(UserLite user) : Image
"Prints the user icon identifying an account link's type. Be aware that overriding this function will allow you to change an image which contains semantically meaningful information used for site navigation, so override with caution."
{
    var Image uimage;
    $uimage.width = 16;
    $uimage.height = 16;
 
    if ($user.journal_type == "C") {
        $uimage->set_url("$*IMGDIR/silk/identity/community.png");
    } elseif ($user.journal_type == "Y") {
        $uimage->set_url("$*IMGDIR/silk/identity/feed.png");
    } elseif ($user.journal_type == "I") {
        $uimage->set_url("$*IMGDIR/silk/identity/openid.png");
    } else {
        $uimage->set_url("$*IMGDIR/silk/identity/user.png");
        $uimage.width = 17;
        $uimage.height = 17;
    }
    return $uimage;
}
 
### UserLite functions

function UserLite::base_url() [fixed] : string
"Returns a link to the user's journal."
{
    return userlite_base_url($this);
}
 
function UserLite::tag_manage_url() [fixed] : string
"Returns a link to the user's tag management page. Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    return "$*SITEROOT/manage/tags.bml?authas=$.username";
}
 
function UserLite::as_string() [fixed] : string
"Use UserLite::print instead in most cases. Returns a DW user tag for the user. Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    return $this->ljuser();
}
 
function UserLite::print() 
"Print a DW user tag for the user. Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    print $this->as_string();
}
 
function UserLite::print_interaction_links()
"This function prints the \"subscribe to journal, add user to access list, post to community, track journal, etc, etc\" items, and wraps them in a standarized class."
{
    """<ul class="userlite-interaction-links">""";
    # FIXME: HTML is not valid if there are no items inside a list, so there should be a
    # check that there are user interaction links before opening the ul class.
    var Link link;
    var int count;
    $count = 0;
    foreach var string k ($.link_keyseq) {
        $link = $this->get_link($k);
        if ($link.url) {
            $count ++;
            if ($*userlite_interaction_links == "text") {
                """<li class="link $k""" + ( $count == 1 ? " first-item" : "" ) + """"><a href="$link.url">$link.caption</a></li>\n""";
            }
            else { ## if ($*userlite_interaction_links == "icon")
                """<li class="link $k""" + ( $count == 1 ? " first-item" : "" ) + """">$link</li>\n""";
            }
        }
    }
    """</ul>""";
}
 
function Friend::print() {
    print $this->as_string();
}
 
function User::print_userpic() {
  print """<a href="$this.userpic_listing_url">""";
  if ( defined $this.default_pic ) {
      $this.default_pic->print();
  }
  print "</a>";
}
 
 
### Generic Page Functions

function Page::print_default_stylesheet()
"Adds CSS to all themes on your layout. Very likely to be overridden for most layouts."
{
    """<style type="text/css">""";
    start_css();
    end_css();
    """</style>\n""";
}
 
function Page::print_theme_stylesheet()
"Prints theme-specific CSS. Should be overwritten by themes that include CSS not part of the layout's default stylesheet."
{
 
}
 
function Page::print_default_stylesheets() 
"Contains the layout and theme default stylesheets. Themes and child layouts may use this to disable the printing of the layout's base default stylesheet, or to add theme-specific CSS bits. May be overriden on a per-layout/theme basis"
{
    $this->print_default_stylesheet();
    if ($*external_stylesheet) {
        println safe """<link rel="stylesheet" href="$.stylesheet_url" type="text/css" />""";
    }
    else {
        println """<style type="text/css">""";
        start_css();
        print_stylesheet();
        end_css();
        println """</style>""";
    }   
 
    println """<style type="text/css">""";
    start_css();
    $this->print_theme_stylesheet();
    end_css();
    println """</style>\n""";
 
}
 
function Page::print_stylesheets()
"If you have specific CSS to code onto all themes using this layout, override print_default_stylesheet.  If you have specific CSS to code into this theme, set the external_stylesheet property or override the Page::print_theme_stylesheet function. An end user can choose to link to an off-site stylesheet using the linked_stylesheet property and/or use the custom_css property.  Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    if ($*include_default_stylesheet) {
        $this->print_default_stylesheets();
    }
 
    if ($*linked_stylesheet != "") {
        println safe """<link rel="stylesheet" href="$*linked_stylesheet" type="text/css" />""";
    }
 
    if ($*custom_css != "") {
        println """<style type="text/css">""";
        start_css();
        println safe $*custom_css;
        end_css();
        println """</style>""";
    }
}
 
function FriendsPage::print_stylesheets() {
#This creates entry-specific CSS to allow custom colors on reading page. Rather than override this, layouts should change custom_foreground_element, custom_background_element (and custom_colors_template for advanced options) instead
    $super->print_stylesheets();
 
    if ($*use_custom_friend_colors) {
        println """<style type="text/css">""";
        start_css();
 
        foreach var Entry e ($this.entries) {
            var Color bg;
            var Color fg;
 
            $bg = $.friends{$e.journal.username}.bgcolor;
            $fg = $.friends{$e.journal.username}.fgcolor;
 
            var string{} custom_foreground_location;
            var string{} custom_background_location;
 
            $custom_foreground_location = {
                "subject" => "h3.entry-title a {color:",
                "userpic_border" => "div.userpic a img {border: solid; border-color:",
                "postername" => "span.entry-poster a {color:",
                "entry" => "div.entry-content {color:",
                "metadata" => ".metadata {color:",
                "bottom_links" => ".footer a {color:"
                };
 
            $custom_background_location = {
                "subject" => "h3.entry-title {background-color:",
                "userpic_background" => "div.userpic a img {padding: 2px; background-color:",
                "entry_border" => "div.entry-content {border: solid; border-color:",
                "metadata_background" => ".metadata {background-color:",
                "footer_border" => "div.footer {border: solid; border-color:"
                };
 
            var string custom_foreground_css = "";
            var string custom_background_css = "";
            $custom_foreground_css = """.journal-$e.journal.username $custom_foreground_location{$*custom_foreground_element} $fg; } \n""";
            $custom_background_css = """.journal-$e.journal.username $custom_background_location{$*custom_background_element} $bg; } \n\n""";
 
            var string custom_colors_css;
            if ($*custom_colors_template != ""){
            var string foreground = $fg;
            var string background = $bg;
            $custom_colors_css = $*custom_colors_template->replace("%%foreground%%", $foreground);
            $custom_colors_css = $custom_colors_css->replace("%%background%%", $background);
            $custom_colors_css = $custom_colors_css->replace("%%new%%", ".journal-$e.journal.username");
            $custom_colors_css = $custom_colors_css + "\n\n";
            }
            else {$custom_colors_css = $custom_foreground_css + $custom_background_css;}
            print $custom_colors_css;
            }
 
        end_css();
        println """</style>""";
        }
}
 
function generate_image_url ( string image_path ) : string
"Take an image property and append static diretory path if not given absolute path"
{
    if ( $image_path != "" and not $image_path->starts_with("http://") ) {
        $image_path = "$*STATDIR/$image_path";
    }
 
    return $image_path;
}
 
function generate_background_css (
    string background_image, 
    string background_image_repeat, 
    string background_image_position,
    Color background_color
) : string 
"Take the values for the properties in a background image group and output the appropriate
line of CSS for inclusion in your stylesheet."
{
    var string color = ($background_color.as_string != "") ? $background_color.as_string : "transparent";
    var string background_css = "background: $color";
 
    $background_image = generate_image_url ($background_image);
    if ($background_image != "") {
        $background_css = $background_css + " url($background_image) $background_image_repeat $background_image_position";
    }
 
    $background_css = $background_css + ";";
 
    return $background_css;
}
 
function generate_color_css (Color text_color, Color background_color, Color border_color) : string 
"Take the color property values and return the appropriate lines of CSS for your style sheet."
{
    var string color_css = "";
    if ($text_color.as_string != "") {
        $color_css = $color_css + "color: $text_color;\n";
    }
    if ($background_color.as_string != "") {
        $color_css = $color_css + "background-color: $background_color;\n";
    }
    if ($border_color.as_string != "") {
        $color_css = $color_css + "border: solid 1px $border_color;\n";
    }
    return $color_css;
}
 
function generate_font_css(string font_base, string font_fallback, string font_size, string font_unit) : string
{
    var string font_css = "";
    if ($font_base != "" or $font_fallback != "") {
        $font_css = "font-family: ";
        if ($font_base != "") {
            $font_css = $font_css + "$font_base";
            if ($font_fallback != "") {
                $font_css = $font_css + ", $font_fallback";
            }
        } else {
            $font_css = $font_css + "$font_fallback";
        }
        $font_css = $font_css + "; ";
    }
    if ($font_size != "" and $font_unit != "") {
        $font_css = $font_css + "font-size: $font_size$font_unit;";
    }
    return $font_css;
}
 
function Page::print_title() 
"Using standard CSS, print the page title. If you want to change the way this looks, modify the CSS."
{
    print """<h2 id="pagetitle"><span>""" + $this->view_title() + """</span></h2>\n""";
}
 
function Page::print_head_title() 
"Using standard CSS, print the journal title. If you want to change the way this looks, modify the CSS."
{
    if ($this.journal.journal_type == "I") {
        print """<title>""" + $this.journal.name + $*text_default_separator + $this->view_title() + """</title>\n""";
    }
    else {
        print """<title>""" + $this.journal.username + $*text_default_separator + $this->view_title() + """</title>\n""";
    }
}
function Page::print_global_title() {
    if ($.global_title) {
        """<h1 id="title"><span>""" + $.global_title + """</span></h1>""";
    }
}
function FriendsPage::print_global_title() {
    if ($.friends_title) {
        """<h1 id="title"><span>""" + $.friends_title + """</span></h1>""";
    }
    else {
        """<h1 id="title"><span>""" + $.global_title + """</span></h1>""";
    }
}
function Page::print_global_subtitle() {
    if ($.global_subtitle) {
        """<h2 id="subtitle"><span>""" + $.global_subtitle + """</span></h2>""";
    }
}
function Page::view_title() [notags] : string {
    return lang_viewname($.view);
}
function RecentPage::view_title() : string {
    if ($.filter_active) {
        return $*text_view_recent_tagged + $.filter_name;
    }
    else {
        return $*text_view_recent;
    }
}
function FriendsPage::view_title() : string {
    if ($.friends_mode == "") {
        if ($.filter_active) {
            if ($.filter_name != "") {
                return $*text_view_friends+" ("+$.filter_name+")";
            } else {
                return $*text_view_friends_filter;
            }
        } else {
            if ($.journal.journal_type == "C") {
                return $*text_view_friends_comm;
            } else {
                return $*text_view_friends;
            }
        }
    }
    elseif ($.friends_mode == "network") {
        if ($.filter_active) {
            if ($.filter_name != "") {
                return $*text_view_network+" ("+$.filter_name+")";
            }
            else {
                return $*text_view_network_filter;
            }
        }
        else {
            return $*text_view_network;
        }
    }
    else {
        return "Unknown Friends View";
    }
}
function DayPage::view_title : string {
    return $.date->date_format("long");
}
function YearPage::view_title() : string {
    return string($.year);
}
function EntryPage::view_title() : string {
    return $.entry.subject ? $.entry->get_plain_subject() : $*text_nosubject;
}
function ReplyPage::view_title() : string {
    return $*text_comment_reply;
}
function Page::title() [notags] : string {
    return $this->view_title();
}
 
function Page::print_wrapper_start() 
"This function adds standard classes to the body of a page.  Pass extra options to it if necessary.  Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    $this->print_wrapper_start( { "" => "" } );
}
 
function Page::print_wrapper_start(string{} opts) 
"This function adds standard classes to the body of a page.  Pass extra options to it if necessary.  Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    var string class = $opts{"class"} ? """class="$opts{"class"}" """ : "";
    var string control_strip = viewer_sees_control_strip() ? "has-navstrip" : "no-navstrip";
    var string column_count = "";
    var string column_side = "";
 
    if ( $*layout_type->contains("two") ) { $column_count = "two-columns"; }
    if ( $*layout_type->contains("three") ) { $column_count = "three-columns"; }
 
    if ( $*layout_type->contains("right") ) { $column_side = " column-right"; }
    if ( $*layout_type->contains("left") ) { $column_side = " column-left"; }
    if ( $*layout_type->contains("sides") ) { $column_side = " column-left column-right"; }
 
    """<body class="page-$.view $column_count$column_side $*layout_type $class $control_strip">\n""";
}
 
function Page::print_wrapper_end() 
"This function concludes the wrapper to the page.  Overriding this function is NOT RECOMMENDED. Overriding this function could prevent sitewide improvements to styles, accessibility, or other functionality from operating in your layout."
{
    """</body>""";
}
 
function server_sig()
"Provides the site branding for each page."
{
    """<span id="site-branding">$*text_powered_by <a href="$*SITEROOT/">$*SITENAME</a></span>""";
}
 
function print_tag_manage_link() "Prints out a link to the tag management page, if the viewer can manage tags."
{
    var Page p = get_page();
    print viewer_can_manage_tags() ? "<div class=\"manage-tags-link\"><a href=\"" + $p.journal->tag_manage_url() + "\">$*text_tags_manage</a></div>" : "";
}
 
function print_list_tags(TagDetail[] tagslist, string{} opts) "Prints out a list of tags. Takes as arguments the taglist and a hash with optional arguments 'list-class' and 'item-class'. 'print_uses' option can be 'number', 'text' or 'title' to determine whether to display the uses as a number, as full text, or in the link title. It defaults to full text"
{
    var string list_class = $opts{"list-class"} ? """class="$opts{"list-class"}" """ : "";
    var string item_class = $opts{"item-class"} ? $opts{"item-class"} : "";
    var string print_uses = $opts{"print_uses"} ? $opts{"print_uses"} : "text";
    if (size $tagslist) {
        println """<ul $list_class>""";
 
        foreach var TagDetail t ($tagslist) {
            var string tag_class = """class="visibility-$t.visibility $item_class" """;
            if ($print_uses == "title") {
                var string uses = get_plural_phrase($t.use_count, "text_tag_uses");
                println """<li $tag_class><a href="$t.url" title="$uses">$t.name</a></li>\n""";
            } elseif ($print_uses == "number") {
                println """<li $tag_class><a href="$t.url">$t.name</a> [$t.use_count]</li>\n""";
            } else {
                var string uses = get_plural_phrase($t.use_count, "text_tag_uses");
                println """<li $tag_class><a href="$t.url">$t.name</a> - $uses</li>\n""";
            } 
        }
        println """</ul>""";
    }
}
 
function print_cloud_tags(TagDetail[] tagslist, string{} opts) "Prints out a list of tags in a cloud. Takes as arguments the taglist and a hash with optional arguments 'min_size' (minimum size in ems, times 10), 'maz_size' (maximum size in ems, times 10), 'list-class' and 'item-class'. 'print_uses' option can be 'number', 'text' or 'title' to determine whether to display the uses as a number, as full text, or in the link title. It defaults to the link title"
{
    var string list_class = $opts{"list-class"} ? """class="$opts{"list-class"}" """ : "";
    var string item_class = $opts{"item-class"} ? $opts{"item-class"}: "";
    var string print_uses = $opts{"print_uses"} ? $opts{"print_uses"} : "title";
 
    var int min_size = $opts{"min_size"} ? int($opts{"maz_size"}) : 9;
    var int maz_size = $opts{"maz_size"} ? int($opts{"maz_size"}) : 20;
    var int high_count = 1;
 
    println """<div $list_class>""";
    foreach var TagDetail t ($tagslist)
    {
        if ($t.use_count > $high_count) { $high_count = $t.use_count; }
    }
 
    foreach var TagDetail t ($tagslist)
    {
        var string tag_size = string($min_size);
        var string tag_class = """class="visibility-$t.visibility $item_class" """;
        if ($t.use_count > 1)
        {
            $tag_size = string((($maz_size-$min_size)*$t.use_count)/$high_count + $min_size);
        }   
        var string tag_size_em = $tag_size->substr(0,($tag_size->length())-1) + "." + $tag_size->substr(($tag_size->length())-1,1) + "em";
        if ($print_uses == "number") {
            println """<span $tag_class style="font-size: $tag_size_em;"><a href="$t.url">$t.name</a> [$t.use_count]</span>\n""";
        } elseif ($print_uses == "text") {
            var string uses = get_plural_phrase($t.use_count, "text_tag_uses");
            println """<span $tag_class style="font-size: $tag_size_em;"><a href="$t.url">$t.name</a> - $uses</span>\n""";
        } else {
            var string uses = get_plural_phrase($t.use_count, "text_tag_uses");
            println """<span $tag_class style="font-size: $tag_size_em;"><a href="$t.url" title="$uses">$t.name</a></span>\n""";
        }
    }
    println """</div>""";
}
 
function print_multilevel_tags(TagDetail[] tagslist, string{} opts) "Prints out a list of multilevel tags. Takes as arguments the taglist and a hash with optional arguments 'list-class' and 'item-class'. 'print_uses' option can be 'number', 'text' or 'title' to determine whether to display the uses as a number, as full text, or in the link title. It defaults to uses as a number"
{
    if (size($tagslist) < 1) { return; }
 
    var string list_class = $opts{"list-class"} ? """class="$opts{"list-class"}" """ : "";
    var string item_class = $opts{"item-class"} ? $opts{"item-class"} : "";
    var string print_uses = $opts{"print_uses"} ? $opts{"print_uses"} : "number";
 
    var string[] closing_html;
    var string[] prev_tags;
    var int tag_list_pos = 0;
    var string tier_code = "";
    $closing_html[0] = "";  # keeps track of html that is used to close off lists
    $prev_tags[0] = "";
 
    foreach var TagDetail t ($tagslist) {
        var string[] tags = $t.name->split($*text_tagsmultilevel_delimiter);
        var string tag_class = """class="visibility-$t.visibility $item_class" """;
 
        var int pos = 0;
        var bool show_lower_tiers = false;
        foreach var string tier($tags) {
            if (size $closing_html <= $pos) {
                # $closing_html length must be kept greater than or equal to that of the current tag.
                $closing_html[$pos] = "";
            }
 
            if (size $prev_tags <= $pos) {
                # The current tag has more tiers than the previous tag.
                $prev_tags[$pos] = "";
            }
 
            # If we're on a tag's last tier, we need to return a link to the tag,
            # otherwise plain text is returned.
            if (size $tags == ($pos + 1)) {
                if ($print_uses == "title") {
                    var string uses = get_plural_phrase($t.use_count, "text_tag_uses");
                    $tier_code = """<a rel="tag" href="$t.url" title="$uses">$tier</a>""";                    
                } elseif ($print_uses == "text") {
                    var string uses = get_plural_phrase($t.use_count, "text_tag_uses");
                    $tier_code = """<a rel="tag" href="$t.url">$tier</a> - $uses""";                    
                } else {
                    $tier_code = """<a rel="tag" href="$t.url">$tier</a> [${t.use_count}]""";
                }
            }
            else {
                $tier_code = """<span class="non-link-tag">$tier</span>""";
            }
 
            # $prev_tags has fewer tiers than current tag.
            if ($prev_tags[$pos] == "") {
                print """\n<ul $list_class><li $tag_class>$tier_code""";
                $closing_html[$pos] = "</li></ul>";
            }
            elseif (($tags[$pos] != $prev_tags[$pos]) or ($show_lower_tiers)) {
                if ($tags[$pos] != $prev_tags[$pos]) {
 
                    # The current tag's tier is not the same as the previous tag's tier of
                    # the same level.  This means we may need to close some lists.
                    var int i = size $closing_html;
                    foreach var string html ($closing_html) {
                        if ($i > $pos) {
                            print $closing_html[$i];
                            $closing_html[$i] = "";
                        }
                        $i--;
                    }
 
                    # If we just closed some lists, that means that any lower tiers in this tag need to
                    # be explicitly displayed, even if they match the same-level tier of the previous tag
                    $show_lower_tiers = true;
                }
 
                if ($closing_html[$pos] == "") {
                    # This is the first tier at this level, so open list.
                    print """\n<ul $list_class><li $tag_class>$tier_code""";
                    $closing_html[$pos] = "</li></ul>";
                }
                else {
                    # There have already been tiers added at this level
                    print """</li>\n<li $tag_class>$tier_code""";
                }
            }
            else {
                # The current tag's tier is exactly the same as the previous tag's tier at
                # this same level.  It has already been included in the list, so do nothing.
            }
 
            # Moving on to next tier in this tag!
            $pos++;
        }
        $prev_tags = $tags;
        $show_lower_tiers = false;
    }
    # Next tag in the list!
    $tag_list_pos++;
 
    # All the tags have been added so close all outstanding lists.
    var int i = 0;
    var string remaining_html = "";
    foreach var string html ($closing_html) {
        if ($html != "") {
            $remaining_html = $html + $remaining_html;
            $closing_html[$i] = "";
        }
        $i++;
    }
 
    println $remaining_html;
 
}
 
function print_multilevel_tags(TagDetail[] tagslist) "Prints out a list of multilevel tags. Takes as arguments the taglist."
{
    print_multilevel_tags($tagslist, { "" => "" });
}
 
 
function open_module(string intname, string title, string headlink_url, bool nocontent)
"Opens up a module for printing, using the appropriate HTML/CSS."
{
    print safe """<div class="module-$intname module">""";
    if ($title != "") {
        """<h2 class="module-header">""";
        if ($headlink_url != "") { print safe """<a href="$headlink_url">"""; }
        print safe $title;
        if ($headlink_url != "") { """</a>"""; }
        "</h2>\n";
    }
    if (not $nocontent) {
        println """<div class="module-content">""";
    }
}
 
function close_module(bool nocontent) {
    println "</div>";
    if (not $nocontent) { println "</div>"; }
}
 
function open_module(string intname, string title, string headlink_url) 
"Opens up a module for printing, using the appropriate HTML/CSS."
{
    open_module($intname, $title, $headlink_url, false);
}
function close_module() {
    close_module(false);
}
 
function print_module_list(string[] list) {
    if (size $list) {
        println """<ul class="module-list">""";
        foreach var string s ($list) {
            print safe """<li class="module-list-item">$s</li>\n""";
        }
        println """</ul>""";
    }
}
 
function print_module_search() {
    # only print this module if the viewer can search this journal
    if (viewer_can_search()) {
        var Page p = get_page();
        var string title = "Search";
 
        open_module("search", $title, "$*SITEROOT/search?user=" + $p.journal.username);
        print_search_form("Search");
        close_module();
    }
}
 
function print_module_userprofile() {
    var Page p = get_page();
    var string title = "Profile";
 
    open_module("userprofile", $title, $p.view_url{"userinfo"});
 
    if ($*module_userprofile_opts_userpic) {
        if (defined $p.journal.default_pic) {
            """<div class="userpic">""";
            $p.journal->print_userpic();
             """</div>""";
        }
    }
    if ($*module_userprofile_opts_name or $*module_userprofile_opts_website) {
        if ($*module_userprofile_opts_name) {
            println "<div class='journal-name'>"+$p.journal.name+"</div>";
        }
        if ($*module_userprofile_opts_website and $p.journal.website_url != "") {
            var string website_name = ( $p.journal.website_name != "" ) ? $p.journal.website_name : $*text_website_default_name;
            println "<div class='journal-website-name'><a href='$p.journal.website_url'>$website_name</a></div>";
        }
    }
    $p.journal->print_interaction_links();
    close_module(); 
}
 
function print_module_navlinks() {
    var Page p = get_page();
    open_module("navlinks", "", "");
    var string[] links = [];
    foreach var string k ($p.views_order) {
        var string css = """ class="$k" """;
        if ($p.view == $k) { $css = """ class="current $k" """; }
        $links[size $links] = """<a href="$p.view_url{$k}"$css>"""+lang_viewname($k)+"""</a>""";
    }
    print_module_list($links);
    close_module();
}
 
function print_module_time() {
    var Page p = get_page();
    open_module("time", "", "");
    $p->print_time();
    close_module();
}
 
function print_module_poweredby() {
    var Page p = get_page();
    open_module("powered", "", "");
    server_sig();
    close_module();
}
 
function print_module_pagesummary_comment_count(Comment comment) : int {
    var int count = 0;
    foreach var Comment c ( $comment.replies ) {
        $count = $count + 1 + print_module_pagesummary_comment_count($c);
    }
    return $count;
}
 
function print_module_pagesummary_comments(string esubject, int count, string prop, string read_url) : string {
    var string subject = ($esubject != "" ? striphtml($esubject) : "<em>$*text_nosubject</em>");
    var string module_comment_text = get_plural_phrase($count, $prop);
 
    if ($*module_pagesummary_opts_comments_tooltip) {
        return "title='$module_comment_text'>$subject</a></span>";
    }
    elseif ($count != 0) {
        if ($read_url == "") {
            return ">$subject</a></span> <span class='pagesummary-commentcount'>+$module_comment_text</span>";
        } else {
            return ">$subject</a></span> <span class='pagesummary-commentcount'><a href='$read_url'>+$module_comment_text</a></span>";
        }
    }
    else {
        return ">$subject</a></span>";
    }
}
 
function print_module_pagesummary() {
    var Page p = get_page();
    var string[] links = [];
 
    if ( $p isa EntryPage ) {
        var EntryPage cp = $p as EntryPage;
        foreach var Comment c ( $cp.comments ) {
            var int count = print_module_pagesummary_comment_count($c);
            var string comment_display =
                print_module_pagesummary_comments($c.subject, $count, "text_read_comments_threads", "");
            var string poster = isnull $c.poster ? $*text_poster_anonymous : $c.poster->ljuser();
 
            $links[size $links] = """<span class="pagesummary-poster">$poster</span> - <span class="pagesummary-subject"><a href="#$c.anchor" $comment_display""";
        }
    }
 
    elseif ( $p isa FriendsPage ) {
        var FriendsPage cp = $p as FriendsPage;
        foreach var Entry e ( $cp.entries ) {
            var string comment_display =
                print_module_pagesummary_comments($e.subject, $e.comments.count, "text_read_comments_friends", $e.comments.read_url + "#comments");
            var string poster = "<span class='pagesummary-poster'>" + $e.poster->ljuser() + "</span>";
 
            if ( not $e.poster->equals($e.journal) ) {
                $poster = "$poster in <span class='pagesummary-community'>$e.journal</span>";
            }
 
            $links[size $links] = """$poster - <span class="pagesummary-subject"><a href="#entry-$e.itemid" $comment_display""";
        }
    }
 
    elseif ( $p isa RecentPage ) {
        var RecentPage cp = $p as RecentPage;
        foreach var Entry e ( $cp.entries ) {
            var string comment_display =
                print_module_pagesummary_comments($e.subject, $e.comments.count, "text_read_comments", $e.comments.read_url + "#comments");
            var string poster = "<span class='pagesummary-poster'>" + $e.poster->ljuser() + "</span>";
 
            if ( not $e.poster->equals($e.journal) ) {
                $links[size $links] = """$poster - <span class="pagesummary-subject"><a href="#entry-$e.itemid" $comment_display""";
            }
            else {
                $links[size $links] = """<span class="pagesummary-subject"><a href="#entry-$e.itemid" $comment_display""";
            }
        }
    }
 
    elseif ( $p isa DayPage ) {
        var DayPage cp = $p as DayPage;
        foreach var Entry e ( $cp.entries ) {
            var string comment_display =
                print_module_pagesummary_comments($e.subject, $e.comments.count, "text_read_comments", $e.comments.read_url + "#comments");
            var string poster = "<span class='pagesummary-poster'>" + $e.poster->ljuser() + "</span>";
 
            if ( not $e.poster->equals($e.journal) ) {
                $links[size $links] = """$poster - <span class="pagesummary-subject"><a href="#entry-$e.itemid" $comment_display""";
            }
            else {
                $links[size $links] = """<span class="pagesummary-subject"><a href="#entry-$e.itemid" $comment_display""";
            }
        }
    }
 
    if ( size($links) < 1 ) { return; }
    open_module("pagesummary", $*text_module_pagesummary, "");
    print_module_list($links);
    close_module();
}
 
function print_module_tags() {
    var Page p = get_page();
    var string title = "";
    var TagDetail[] tags = $p->visible_tag_list($*module_tags_opts_limit);
    if ($*module_tags_opts_limit == 0) {
       $title = $*text_module_tags;
    }
    else {
       $title = $*text_module_popular_tags;
    }
    if (size($tags) < 1) {
        return;
    }
    elseif ($*module_tags_opts_type == "multi") {
        open_module("tags_multilevel", $title, $p.view_url{"tags"});
        print_multilevel_tags($tags, { "list-class" => "module-list", "item-class" => "module-list-item", "print_uses" => $*module_tags_opts_count_type });
        print_tag_manage_link();
        close_module();
    }
    elseif ($*module_tags_opts_type == "cloud") {
        open_module("tags_cloud", $title, $p.view_url{"tags"});
        print_cloud_tags($tags, { "list-class" => "module-list", "item-class" => "module-list-item", "print_uses" => $*module_tags_opts_count_type });
        print_tag_manage_link();
        close_module();
    }
    else {
        open_module("tags_list", $title, $p.view_url{"tags"});
        print_list_tags($tags, { "list-class" => "module-list", "item-class" => "module-list-item", "print_uses" => $*module_tags_opts_count_type });
        print_tag_manage_link();
        close_module();
    }
}
 
function print_module_active() {
    var Page p = get_page();
    if ( $p.has_activeentries and not $p isa FriendsPage ) {
        var Entry[] activeentries = $p.activeentries;
        var string[] subjects;
        open_module( "active", $*text_module_active_entries, "" );
        var int i = 1;
                foreach var Entry activeentry ( $activeentries ) {
                    var string subject = ( $activeentry.subject != "" ? $activeentry.subject : "$*text_nosubject" );
                    $subjects[size $subjects] = """$i: <a href="$activeentry.permalink_url">$subject</a>""";
                    $i++;
                 }
        print_module_list( $subjects );
        close_module();
    }
}
 
function print_module_calendar() {
    var Page p = get_page();
    var YearMonth mon = $p->get_latest_month();
 
    if ($*module_calendar_opts_type=="horizontal") {
        open_module("calendar","", "");
        "<div class='calendar-horizontal'>";
        print $mon->month_format("%%month%%", true);
        foreach var YearWeek week ($mon.weeks) {
            foreach var YearDay day ($week.days) {
                if ($day.num_entries > 0) {
                    var string entries = get_plural_phrase($day.num_entries, "text_calendar_num_entries");
                    print """<span class="entry-day"> <a href="$day.url" title="$entries">$day.day</a></span>""";
                }
                else {
                    print """<span class="empty-day"> $day.day </span>""";
                }
            }
        }
        print $mon->month_format("%%yyyy%%", true);
        "</div>";
        close_module();
    }
    else {
        open_module("calendar", $mon->month_format("", true), "");
        println """<table summary="Monthly calendar with links to each day's entries">""";
 
        println "<tr>";
        foreach var int d (weekdays()) {
            "<th>"+$*lang_dayname_shorter[$d]+"</th>\n";
        }
       println "</tr>";
 
        foreach var YearWeek week ($mon.weeks) {
            println "<tr>";
            foreach var int i (1 .. $week.pre_empty) {
                print "<td>&nbsp;</td>";
            }
 
            foreach var YearDay day ($week.days) {
                if ( $day.num_entries > 0) {
                    var string entries = get_plural_phrase($day.num_entries, "text_calendar_num_entries");
                    print """<td class="entry-day"> <a href="$day.url" title="$entries">$day.day</a></td>""";
                }
                else {
                    print """<td class="empty-day">$day.day</td>""";
                }
            }
 
            foreach var int i (1 .. $week.post_empty) {
                print "<td>&nbsp;</td>";
            }
            println "</tr>";
        }
 
        println """</table>""";
        close_module();
    }
}
function print_module_syndicate() {
    var Page p = get_page();
 
    if ((size $p.data_links_order) < 1) { return; }
    open_module("syndicate", $*text_module_syndicate, "");
 
    foreach var string k ($p.data_links_order) {
        print " $p.data_link{$k} ";
    }
 
    close_module();
}
function print_module_customtext() {
    var Page p = get_page();
    open_module("customtext", $*text_module_customtext, $*text_module_customtext_url);
    print safe $*text_module_customtext_content;
    close_module();
}
function print_module_links() {
    var Page p = get_page();
    var UserLink[] links = $p.linklist;
    if (size($links) < 1 or not $*linklist_support) { return; }
 
    var bool box_open = false;
    if (not $links[0].is_heading) {
        open_module("typelist", $*text_module_links, "");
        $box_open = true;
    }
 
    var string[] items = [];
    foreach var UserLink link ($links) {
        if ($link.is_heading and $link.title != "") {
            if ($box_open) {
                print_module_list($items);
                close_module();
                $items = [];
            }
            open_module("typelist", $link.title, "");
            $box_open = true;
        }
        if ($link.title == "") {
             print_module_list($items);
             $items = [];
        }
        if (not $link.is_heading and $link.title != "") {
            $items[size $items] = """<a href="$link.url">$link.title</a>""";
        }
    }
    if ($box_open) {
        print_module_list($items);
        close_module();
    }
}
function print_module_credit() {
    var string ret = "";
    var int authors_size = size $*layout_authors;
    if ( $authors_size > 0 ) {
        var int count = 0;
        $ret = $ret  + "<dt>$*text_layout_authors</dt> ";
        foreach var string{} author ( $*layout_authors ) {
            var string item;
            if ( $author{"url"} != "" ) {
                $item = "<a href='" + $author{"url"} + "'>" + $author{"name"} + "</a>";
            } elseif ( $author{"type"} == "user" ) {
                var UserLite u = UserLite( $author{"name"} );
                if ( defined $u ) {
                    $item = $u->ljuser();
                }
            }
            if ( $item == "" ) {
                $item = $author{"name"};
            }
 
            $ret = $ret + "<dd>$item";
            $count++;
            if ($count < $authors_size ) { $ret = $ret + ","; }
            $ret = $ret + "</dd>\n";
        }
    }
 
    var int resources_size = size $*layout_resources;
    if ( $resources_size > 0 ) {
        var int count = 0;
        $ret = $ret  + "<dt>$*text_layout_resources</dt> ";
        foreach var string{} resource ( $*layout_resources ) {
            var string item;
            if ( $resource{"url"} != "" ) {
                $item = "<a href='" + $resource{"url"} + "'>" + $resource{"name"} + "</a>";
            } else {
                $item = $resource{"name"};
            }
 
            $ret = $ret + "<dd>$item";
            $count++;
            if ($count < $resources_size ) { $ret = $ret + ","; }
            $ret = $ret + "</dd>\n";
        }
    }
 
    if ( $ret != "" ) {
        open_module("credit", $*text_module_credit, "");
        print safe "<dl>$ret</dl>";
        close_module();
    }
}
 
function handle_module_group_array(string[][] list) {
    foreach var string[] item ($list) {
        var string module = $item[0];
 
        # we can have blank modulenames because the ordering will not necessarily be consecutive
        if ($module == "") {
            continue;
        }
        elseif ($module == "userprofile") {
            print_module_userprofile();
        }
        elseif ($module == "navlinks") {
            print_module_navlinks();
        }
        elseif ($module == "time") {
            print_module_time();
        }
        elseif ($module == "poweredby") {
            print_module_poweredby();
        }
        elseif ($module == "pagesummary") {
            print_module_pagesummary();
        }
        elseif ($module == "tags") {
            print_module_tags();
        }
        elseif ($module == "active") {
            print_module_active();
        }
        elseif ($module == "calendar") {
            print_module_calendar();
        }
        elseif ($module == "syndicate") {
            print_module_syndicate();
        }
        elseif ($module == "customtext") {
            print_module_customtext();
        }
        elseif ($module == "links") {
            print_module_links();
        }
        elseif ($module == "credit") {
            print_module_credit();
        }
        elseif ($module == "search") {
            print_module_search();
        }
    }
}
 
 
function Page::print_module_section ( string section_name ) {
    """<div class="module-wrapper"><div class="separator separator-before"><div class="inner"></div></div>\n<div class="module-section-$section_name">\n<div class="inner">""";
    handle_module_group_array( $*module_sections{$section_name} );
    """</div>\n</div><div class="separator separator-after"><div class="inner"></div></div>\n</div>""";
}
 
function Page::print()
"The meat of each new layout. Describes how each page will look. In nearly all cases, the logic and decision-making processes should come from pre-existing functions in core2, and should not get written here. If you limit the structure of the page to HTML, function calls, and attached CSS, then you will be able to pick up all of the enhancements  and accessibility requirements managed by core2."
{
    """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n<head profile="http://www.w3.org/2006/03/hcard http://purl.org/uF/hAtom/0.1/ http://gmpg.org/xfn/11">\n""";
    $this->print_head();
    $this->print_stylesheets();
    $this->print_head_title();
    """</head>""";
    $this->print_wrapper_start();
    $this->print_control_strip();
    """
    <div id="canvas">
        <div class="inner">
            <div id="header">
                <div class="inner">
                    """;
                    $this->print_global_title();
                    $this->print_global_subtitle();
                    $this->print_title();
    """
                </div><!-- end header>inner -->
            </div><!-- end header -->
            <div id="content">
                <div class="inner">
                    <div id="primary"><div class="inner">
                        """; 
                        $this->print_body();
    """
                    </div></div><!-- end primary and primary>inner -->
                    <div id="secondary"><div class="inner">
                        """;
                        $this->print_module_section("one");
    """
                    </div></div><!--  end secondary and secondary>inner -->
                    <div id="invisible-separator" style="float: left; width: 1px;"></div> <!-- this is a hack for IE7 + two-columns-right -->
                    <div id="tertiary"><div class="inner">
                        """;
                        $this->print_module_section("two");
    """
                    </div></div><!-- end tertiary and tertiary>inner -->
                    <div id="content-footer"></div>
                </div><!-- end content>inner -->
            </div> <!-- end content -->
        </div> <!-- end canvas>inner --> 
    """;
 
    """
    <div id="footer">
        <div class="inner">
            """;
            print safe """
                <div class="page-top"><a href="#">$*text_page_top</a></div>
        </div><!-- end footer>inner -->
    </div><!-- end footer -->
 
    </div> <!-- end canvas -->
    """;
    $this->print_wrapper_end();
    """</html>""";
}
 
function Page::print_time() {
    $this->print_time("","");
}
 
function Page::print_time(string datefmt, string timefmt) {
    if ($datefmt == "") {
        $datefmt = "med";
    }
    if ($timefmt == "") {
        if ($this.timeformat24) {
            $timefmt = "short_24";
        } else {
            $timefmt = "short";
        }
    }
 
    var string ret;
    if ($datefmt != "none") { $ret = $ret + $this.local_time->date_format($datefmt); }
    if ($datefmt != "none" and $timefmt != "none") { $ret = $ret + " "; }
    if ($timefmt != "none") { $ret = $ret + $this.local_time->time_format($timefmt); }
 
    print safe """<span id="load-time">$*text_generated_on $ret</span>""";
}
 
function Page::print_body() {
    """<h2>No Default Renderer</h2><p>There is no body renderer for viewtype <tt>$.view</tt> defined.</p>""";
}
 
function Page::print_navigation() { $this->print_navigation( { "" => "" } ); }
function Page::print_navigation( string{} opts ) {}
 
function Page::print_head() {
    print $.head_content;
    $this->print_custom_head();
}
 
function Page::print_custom_head() {
    # blank
}
 
function Page::print_linklist() {
    if (size $.linklist <= 0) {
        return;
    } elseif (not $*linklist_support) {
        return;
    }
 
    foreach var UserLink l ($.linklist) {
        if ($l.title) {
            if ($l.is_heading) {
                "<b>$l.title</b>";
            } else {
                "<a href='$l.url'>$l.title</a>";
            }
        }
        "<br />";
    }
}
 
# Prints the name of the account which posted the entry
function Entry::print_poster {
    var Page p = get_page();
    print safe "<span class=\"poster entry-poster\">";
    if ($p isa FriendsPage or $p isa EntryPage or $p isa ReplyPage) {
        $this.poster->print();
        if (not $this.poster->equals($this.journal)) {
            print $*text_posting_in;
            $this.journal->print();
        }
    }
    else {
        if (not $this.poster->equals($this.journal)) {
          $this.poster->print();
        }
    }
    print safe "</span>";
}
 
# For any given comment, print the commentor's name (local, anonymous, or openid)
function Comment::print_poster() {
    var string poster = defined $this.poster ? $this.poster->as_string() : "<span class=\"anonymous\">$*text_poster_anonymous</span>";
    if ($this.metadata{"imported_from"}) { $poster = "<span class=\"imported-from\">$poster ($*text_openid_from " + $this.metadata{"imported_from"} + ")</span>"; }
 
    print safe "<span class=\"poster comment-poster\">$*text_comment_from $poster</span>\n";
}
 
 
function Page::print_entry(Entry e) 
"The meat of each new layout. Describes how each page will look. In nearly all cases, the logic and decision-making processes should come from pre-existing functions in core2, and should not get written here. If you limit the structure of the page to HTML, function calls, and attached CSS, then you will be able to pick up all of the enhancements  and accessibility requirements managed by core2."
{
    ## For most styles, this will be overridden by FriendsPage::print_entry and such.
    $e->print_wrapper_start();
    """<div class="header">\n""";
    $e->print_subject();
    $e->print_metatypes();
    $e->print_time();
    """</div>\n""";
    """<div>\n""";
    """<div class="contents">\n""";
    """<div class="inner">\n""";
    $e->print_userpic();
    $e->print_poster();
    if ($*entry_metadata_position == "top") { $e->print_metadata(); }
    $e->print_text();
    if ($*entry_metadata_position == "bottom") { $e->print_metadata(); }
    """</div>\n""";
    """</div>\n""";
    """</div>\n""";
    """<div class="footer">\n""";
    """<div class="inner">\n""";
    $e->print_tags();
    $e->print_management_links();
    if ($this isa EntryPage) {
        """<hr class="above-entry-interaction-links" />""";
        $e->print_interaction_links("topcomment");
        $this->print_reply_container({ "target" => "topcomment" });
        """<hr class="below-reply-container" />""";
    }
    else {
        $e->print_interaction_links();
    }
    "</div>\n</div>\n";
    $e->print_wrapper_end();
 
}
 
function RecentPage::print_sticky_entry(StickyEntry s)
"function to print the sticky entry. can be overrised by styles to print it differently than other entries."
{
    $this->print_entry( $s );
}
 
function Comment::print_edit_text() {
    if ($this.edited) {
        var string editreason = $this.editreason == "" ? "" : "($this.editreason) ";
        print "<div class='edittime'><em>$*text_comment_edittime $editreason";
        $this->print_edittime();
        print "</em></div>";
    }
}
 
function EntryLite::print_text() [fixed] {
    if ($this isa Comment) {
        """<div class="comment-content">""";
        print $.text;
        var Comment c = $this as Comment;
        $c->print_edit_text();
        """</div>""";
    }
    else {
        """<div class="entry-content">""";
        print $.text;
        """</div>""";
    }
}
 
function EntryLite::print_subject() [fixed] {
    $this->print_subject({ "" => "" });
}
 
function EntryLite::print_subject( string{} opts ) [fixed] {
    if ($this isa Comment) {
        var Comment c = $this as Comment;
        """<div class="comment-title">""";
        if ($c.screened) {
            print $*text_screened;
        }
        if ($c.frozen) {
            print $*text_frozen;
        }
        print $this->formatted_subject( $opts );
        "</div>";
    }
    else {
        if ($this isa StickyEntry) {
            var StickyEntry s = $this as StickyEntry;
            """<h3 class="sticky-entry-title entry-title">""";
            $s->print_sticky_icon();
            print $*text_stickyentry_subject + $this->formatted_subject( $opts );
            "</h3>";
        }
        else {
            """<h3 class="entry-title">""";
            print $this->formatted_subject( $opts );
            "</h3>";
        }
    }
}
 
function StickyEntry::print_sticky_icon() {
    """<span class="sticky-entry-icon">""";
    $this.sticky_entry_icon->print($*text_icon_alt_sticky_entry);
    """</span>\n""";
}
 
function EntryLite::print_wrapper_start() {
}
function Entry::print_wrapper_start() {
# FIXME: need to ensure that calling the alternate function doesn't interfere with the printing of comments or the printing of module groups.
    var string alternate = alternate ("entry-wrapper-odd", "entry-wrapper-even");
    var string security = $this.security ? $this.security : "public";
    var string adult_content_level = $this.adult_content_level ? $this.adult_content_level : "none";
    var string journal_type = "journal-type-$this.journal.journal_type";
    var string poster;
    var string journal;
    if ($this.journal.journal_type != "I") {
        $poster = "poster-$this.poster.username";
        $journal = "journal-$this.journal.username";
    }
    var string userpic = $this.userpic ? "has-userpic" : "no-userpic";
 
    #additional classes for sticky entries added
    if ($this isa StickyEntry) {
        """<div class="sticky-entry-wrapper entry-wrapper $alternate security-$security restrictions-$adult_content_level $journal_type $poster $journal $userpic" id="sticky-entry-wrapper-$this.itemid">\n""";
        """<div class="separator separator-before"><div class="inner"></div></div>\n""";
        """<div class="sticky-entry entry" id="sticky-entry-$this.itemid">\n""";
    }
    else {
        """<div class="entry-wrapper $alternate security-$security restrictions-$adult_content_level $journal_type $poster $journal $userpic" id="entry-wrapper-$this.itemid">\n""";
        """<div class="separator separator-before"><div class="inner"></div></div>\n""";
        """<div class="entry" id="entry-$this.itemid">\n""";
    }
    """<div class="inner">\n""";
}
function Comment::print_wrapper_start() {
    var EntryPage ep = get_page() as EntryPage;
    var string alternate = alternate ("comment-wrapper-odd", "comment-wrapper-even");
    var string screened = $this.screened ? "screened" : "visible";
    var string deletedstate = $this.deleted ? "deleted" : "";
    var string frozen = $this.frozen ? "frozen" : "";
    var string poster = defined $this.poster ? "poster-" + $this.poster.username : "poster-anonymous";
    var string full = $this.full ? "full" : "partial";
    var string userpic = $this.userpic ? "has-userpic" : "no-userpic";
    var string entryauthor = "";
    if (not isnull $ep and defined $this.poster and $this.poster.username == $ep.entry.poster.username) {
        $entryauthor = "entry-author";
    }
    """<div class="comment-wrapper $alternate $screened $frozen $deletedstate $poster $entryauthor $full $userpic">\n""";
    """<div class="separator separator-before"><div class="inner"></div></div>\n""";
    """<div class="comment" id="comment-$this.dom_id">\n""";
    """<div class="inner">\n""";
}
 
function EntryLite::print_wrapper_end() {
}
function Entry::print_wrapper_end() {
    "</div>\n</div>\n";
    """<div class="separator separator-after"><div class="inner"></div></div>\n""";
     "</div>\n";  
}
function Comment::print_wrapper_end() {
    "</div>\n</div>\n"; 
    """<div class="separator separator-after"><div class="inner"></div></div>\n""";
    "</div>\n";
}
 
function EntryLite::print_metadata() {
}
function Entry::print_metadata() {
    if (size $.metadata) {
        var string position = ($*entry_metadata_position == "top") ? " top-metadata" : " bottom-metadata";
        """<div class="metadata$position">\n<ul>\n""";
        foreach var string m ($.metadata) {
            var string metadata_name = lang_metadata_title($m);
            """<li><span class="metadata-label metadata-label-$m">$metadata_name: </span>""";
            if ($m == "mood") {
                " $.mood_icon ";
            }
            """<span class="metadata-item metadata-item-$m">$.metadata{$m}</span></li>\n""";
        }
        "</ul>\n</div>\n";
    }
}
function Comment::print_metadata() {
    if ($.metadata{"poster_ip"}) {
        print safe "<span class=\"poster-ip\">$*text_comment_ipaddr (" + $.metadata{"poster_ip"} + ")</span>";
    }
}
 
function EntryLite::print_metatypes() {
}
function Entry::print_metatypes(bool icon, bool info) {
    if ($this.security) {
        """<span class="access-filter">""";
        if ($icon) {
            $this.security_icon->print($this.security);
        }
        if ($info) {
            print $this.security;
        }
        """</span>\n""";
    }
    if ($this.adult_content_level) {
        """<span class="restrictions">""";
        if ($icon) {
            $this.adult_content_icon->print($this.adult_content_level);
        }
        if ($info) {
            print $this.adult_content_level;
        }
        """</span>\n""";
    }
}
 
function Entry::print_metatypes() {
    $this->print_metatypes(true, false);
}
 
function Comment::print_metatypes() {
    if (defined $this.subject_icon) {
        """<h3 class="comment-subjecticon">$this.subject_icon</h3>""";
    }
}
 
function Entry::print_tags() [fixed] {
    if ($this.tags) {
        var int tag_count = 0;
        """<div class="tag"><span class="tag-text">$*text_tags</span><ul>\n""";
        foreach var Tag t ($this.tags) {
            """<li><a rel="tag" href="$t.url">$t.name</a>""";
            $tag_count++;
            if ($tag_count < size $.tags) { print $*text_tags_item_sep; }
            "</li>\n";
        }
        "</ul></div>";
    }
}
 
function EntryLite::print_userpic() {
    print "<div class=\"userpic\">";
    if ( defined $this.userpic )
    {
        if ( $*use_shared_pic )
        {
            print """<a href="$this.journal.userpic_listing_url">""";
        }
        else
        {
            print """<a href="$this.poster.userpic_listing_url">""";
        }
        $this.userpic->print();
        print "</a>";
    }
    println "</div>";
}
 
function EntryLite::print_management_links() {}
function Comment::print_management_links()
"Prints comment management links, aka delete/screen/freeze/track."
{
    # FIXME: HTML is not valid if there are no items inside a list, so there should
    # be a check that there are comment management links before opening the ul class.
    """<ul class="comment-management-links">""";
    var Link link;
    var int count;
    var string extras;
    $count = 0;
    foreach var string k ($.link_keyseq) {
        $link = $this->get_link($k);
        if ($link.url) {
            $count ++;
            if ($*comment_management_links == "text") {
                foreach var string extra ( $link.extra ) {
                    var string value = $link.extra{$extra};
                    $extras = $extras + "$extra='$value' ";
                }
                """<li class="link $k""" + ( ($count == 1) ? " first-item" : "" ) + """"><a href="$link.url" $extras>$link.caption</a></li>\n""";
            }
            else { ## if ($*comment_management_links == "icon")
                """<li class="link $k""" + ( ($count == 1) ? " first-item" : "" ) + """">$link</li>\n""";
            }
        }
    }
    """</ul>""";
}
 
function EntryLite::print_interaction_links() {}
function Comment::print_interaction_links() 
"Prints comment interaction links, aka reply/thread/parent/expand."
{
    """<ul class="comment-interaction-links">""";
    print safe """<li class="link commentpermalink"><a href="$this.permalink_url">$*text_comment_link</a></li>\n""";
    if ($this.frozen) {
        print safe """<li class="link frozen first-item">$*text_comment_frozen</li>\n""";
    } else {
        """<li class="link reply first-item">""";
        $this->print_reply_link({"linktext" => $*text_comment_reply});
        """</li>\n""";
    }
    if ($this.threadroot_url != "") { print safe """<li class="link threadroot"><a href="$this.threadroot_url">$*text_comment_threadroot</a></li>\n""";}
    if ($this.parent_url != "") { print safe """<li class="link commentparent"><a href="$this.parent_url">$*text_comment_parent</a></li>\n"""; }
    if ($this.thread_url != "") {
        print safe """<li class="link thread"><a href="$this.thread_url">$*text_comment_thread</a></li>\n""";
    }
    var Link expand_link = $this->get_link("expand_comments");
    if (defined $expand_link) {
        """<li class="link expand">""";
        $this->print_expand_link();
        """</li>\n""";
    }
    """</ul>""";
}
 
function Entry::print_link_next() {
    var Link link = $this->get_link("nav_next");
    if ($*entry_management_links == "text") {
         """<a href="$link.url">$link.caption</a>""";
    }
    else {
        " $link";
    }
}
function Entry::print_link_prev() {
    var Link link = $this->get_link("nav_prev");
    if ($*entry_management_links == "text") {
         """<a href="$link.url">$link.caption</a>""";
    }
    else {
        " $link";
    }
}
function Entry::print_management_links() 
"Prints entry management links, aka prev/edit/tag/memory/tag/next, with standarized CSS."
{
    ## There's no point in showing previous/next links on pages which show
    ## multiple entries anyway, so we only print them on EntryPage and ReplyPage.

    var Page p = get_page();
    var int count;
    var string extras;
    $count = 0;
    """<ul class="entry-management-links">""";
    var bool show_interentry = ($p.view == "entry" or $p.view == "reply");
    if ($show_interentry) {
        $count ++;
        """<li class="link link_prev first-item">""";
        $this->print_link_prev();
        """</li>\n""";
    }
    var Link link;
    foreach var string k ($.link_keyseq) {
        $link = $this->get_link($k);
        if ($link.url) {
            $count ++;
            if ($*entry_management_links == "text") {
                foreach var string extra ( $link.extra ) {
                    var string value = $link.extra{$extra};
                    $extras = $extras + "$extra='$value' ";
                }
                """<li class="link $k""" + ( $count == 1 ? " first-item" : "" ) + """"><a href="$link.url" $extras>$link.caption</a></li>\n""";
            }
            else { ## if ($*entry_management_links == "icon")
                """<li class="link $k""" + ( $count == 1 ? " first-item" : "" ) + """">$link</li>\n""";
            }
        }
    }
    if ($show_interentry) {
        """<li class="link link_next">""";
        $this->print_link_next();
        """</li>\n""";
    }
    """</ul>""";
}
function Entry::print_interaction_links() {
    $this->print_interaction_links( "" );
}
 
function Entry::print_interaction_links(string target) 
"Prints entry interaction links, aka # comments/leave a comment, with standardized CSS."
{
    var Page p = get_page();
    var int count;
    $count = 0;
        if ($p isa EntryPage) {
        """<ul class="entry-interaction-links">""";
            var EntryPage ep = $p as EntryPage;
        if ($.comments.enabled and $ep.comment_pages.total_subitems > 0) {
            $count ++;
            """<li class="entry-readlink first-item">""";
                $this.comments->print_readlink();
            "</li>\n";
            }
        if ($.comments.enabled) {
            $count ++;
            """<li class="entry-replylink""" + ( $count == 1 ? " first-item" : "" ) + """">""";
            $ep->print_reply_link({ "linktext" => $*text_post_comment, "target" => $target });
            "</li>\n";
        }
        "</ul>";
    } else {
            $this.comments->print();
        }
    }
 
### RecentPage and related functions

function RecentPage::print_body {
    # Creator for both the Recent and Friends views, since they are similar
    # If someone wants to do the two views differently, they can create
    # FriendsPage::print_body since FriendsPage extends RecentPage.
    """
    <div id="entries" class="hfeed">
        <div class="inner">
            """;
        $this->print_navigation( { "class" => "topnav" } );
 
    foreach var Entry e ($.entries) {
        # Print the entry
        if ( $e isa StickyEntry ) {
            var StickyEntry s = $e as StickyEntry;
            $this->print_sticky_entry($s);
        } 
        else {
	    $this->print_entry($e);
        }
    }
 
            $this->print_navigation( { "class" => "bottomnav" } );
            """
        </div><!-- entries>inner-->
    </div><!-- entries -->
    """;
 
 
}
 
function RecentPage::print_navigation( string{} opts ) [fixed] {
    var bool empty = $.nav.backward_url == "" and $.nav.forward_url == "";
    """
    <div class="navigation $opts{"class"}">
        <div class="inner">
    """;
 
    if ( not $empty ) { "<ul>"; }
    if ( $.nav.backward_url != "" ) {
        print safe """<li class="page-back"><a href="$.nav.backward_url">""" + get_plural_phrase( $.nav.backward_count, "text_skiplinks_back" ) + "</a></li>\n";
    }
    if ( $.nav.backward_url != "" and $.nav.forward_url != "" ) {
        print safe """<li class="page-separator">|</li>""";
    }
    if ( $.nav.forward_url != "" ) {
        print safe """<li class="page-forward"><a href="$.nav.forward_url">""" + get_plural_phrase( $.nav.forward_count, "text_skiplinks_forward" ) + "</a></li>\n";
    }
    if ( not $empty ) { "</ul>"; }
    """
        </div><!-- navigation>inner -->
    </div><!-- navigation -->
    """;
}
 
function secs_to_string (int sec) : string {
   if ($sec < 60) {
        return string($sec) + " seconds";
    }
   if ($sec < 3600) {
        return string($sec / 60) + " minutes";
    }
   if ($sec < 86400) {
        return string($sec / 3600) + " hours";
    }
   return string($sec / 86400) + " days";
 }
 
function EntryLite::print_time(string datefmt, string timefmt) {
    print $this->time_display($datefmt, $timefmt);
}
 
function EntryLite::time_display(string datefmt, string timefmt) : string {
    if ($datefmt == "") {
        $datefmt = "med";
    }
    if ($timefmt == "") {
        if ($this.timeformat24) {
            $timefmt = "short_24";
        } else {
            $timefmt = "short";
        }
    }
 
    var string ret;
    if ($datefmt != "none") { $ret = $ret + $this.time->date_format($datefmt); }
    if ($datefmt != "none" and $timefmt != "none") { $ret = $ret + " "; }
    if ($timefmt != "none") { $ret = $ret + $this.time->time_format($timefmt); }
 
    return $ret;
}
 
function Entry::time_display(string datefmt, string timefmt) : string {
    var Page p = get_page();
 
    var bool linkify = ( $p.view == "recent" or $p.view == "entry" or $p.view == "reply" or $p.view == "day" );
 
    if ($datefmt == "") {
        $datefmt = "med";
    }
    if ($timefmt == "") {
        if ($this.timeformat24) {
            $timefmt = "short_24";
        } else {
            $timefmt = "short";
        }
    }
 
    var string ret;
    if ($datefmt != "none") { $ret = $ret + """<span class="date">""" + $this.time->date_format($datefmt, $linkify) + "</span>"; }
    if ($datefmt != "none" and $timefmt != "none") { $ret = $ret + " "; }
    if ($timefmt != "none") { $ret = $ret + """<span class="time">""" + $this.time->time_format($timefmt) + "</span>"; }
 
    if ($ret != "") { $ret = """<span class="datetime">$ret</span>"""; }
 
    return $ret;
}
 
# edittime argument is true if we want the get the edit time of the comment
function Comment::print_time (string datefmt, string timefmt, bool edittime) {
    print $this->time_display($datefmt, $timefmt, $edittime);
}
function Comment::time_display (string datefmt, string timefmt, bool edittime) : string {
    var DateTime time = ($edittime ? $this.edittime : $this.time);
    var DateTime time_remote = ($edittime ? $this.edittime_remote : $this.time_remote);
    var DateTime time_poster = ($edittime ? $this.edittime_poster : $this.time_poster);
 
    if ($datefmt == "") {
        $datefmt = "iso";
    }
    if ($timefmt == "") {
        if ($this.timeformat24) {
            $timefmt = "short_24";
        } else {
            $timefmt = "short";
        }
    }
 
    var string tooltip = "";
    if ($edittime == false) {
        var string etime = secs_to_string($this.seconds_since_entry);
        $tooltip = $etime + " after journal entry";
    }
 
    var string main;
 
    var string display_date;
    var string display_time;
 
    if ($time_remote) {
        $display_date = $time_remote->date_format($datefmt);
        if ($timefmt == "none") { $display_date = $display_date + " (local)"; }
        $display_time = $time_remote->time_format($timefmt) + " (local)";
    } else {
        $display_date = $time->date_format($datefmt);
        if ($timefmt == "none") { $display_date = $display_date + " (UTC)"; }
        $display_time = $time->time_format($timefmt) + " (UTC)";
    }
 
    if (defined $time_poster and defined $this.poster)
    {
        var string poster_date = $time_poster->date_format($datefmt);
        if ($edittime == false) {
            $tooltip = $tooltip + ", ";
        }
 
        if ($poster_date == $display_date and $timefmt != "none") { $poster_date = ""; }
        else { $poster_date = $poster_date + " "; }
 
        if ($timefmt != "none") {
            $tooltip = $tooltip + $poster_date + $time_poster->time_format($timefmt) + " (" + $this.poster.username + "'s time)";
        } else {
            $tooltip = $tooltip + $poster_date + "(" + $this.poster.username + "'s time)";
        }
    }
 
    if ($datefmt != "none") { $main = $main + $display_date; }
    if ($datefmt != "none" and $timefmt != "none") { $main = $main + " "; }
    if ($timefmt != "none") { $main = $main + $display_time; }
 
    return "<span class=\"datetime\">" + $*text_comment_date + " <span title=\"" + ehtml($tooltip) + "\">" + ehtml($main) + "</span></span>";
}
 
function Comment::print_time (string datefmt, string timefmt) {
    $this->print_time($datefmt, $timefmt, false);
}
 
function Comment::time_display (string datefmt, string timefmt) : string {
    return $this->time_display($datefmt, $timefmt, false);
}
 
function EntryLite::print_time() {
    print $this->time_display();
}
 
function EntryLite::time_display() : string {
    # Let the real function decide on some nice defaults
    return $this->time_display("", "");
}
 
function Comment::print_edittime() {
    print $this->edittime_display();
}
 
function Comment::edittime_display() : string {
    return $this->time_display("", "", true);
}
 
function Comment::print_edittime (string datefmt, string timefmt){
    print $this->edittime_display($datefmt, $timefmt);
}
 
function Comment::edittime_display (string datefmt, string timefmt) : string {
    return $this->time_display($datefmt, $timefmt, true);
}
 
### Year view

function YearPage::print_body {
    """
    <div id="archive-year">
        <div class="inner">
            """;
            $this->print_navigation( { "class" => "topnav" } );
            """
            <div class="year">
                <div class="inner">
                    """;
                    foreach var YearMonth m ($.months) {
                        $this->print_month($m);
                    }
                    """
                </div><!-- year>inner -->
            </div><!-- year -->
            """;
            $this->print_navigation( { "class" => "bottomnav" } );
            """
        </div><!-- archive-year>inner -->
    </div><!-- archive-year -->
    """;
}
 
function YearPage::print_navigation( string{} opts ) [fixed] {
    """
    <div class="navigation $opts{"class"}">
        <div class="inner">
    """;
 
        $this->print_year_links();
 
    """
        </div><!-- navigation>inner -->
    </div><!-- navigation -->
    """;
}
 
function YearPage::print_year_links() {
    if (size $.years) {
        """<ul>\n""";
        foreach var YearYear y ($.years) {
            if ($y.displayed) {
                """<li class="active">$y.year</li>\n""";
            } else {
                """<li><a href="$y.url">$y.year</a></li>\n""";
            }
        }
       """</ul>\n""";
    }
}
function YearPage::print_month(YearMonth m) {
 
    if (not $m.has_entries) { return; }
 
    var string month_label = $m->month_format();
    """
    <div class="month-wrapper">
        <div class="separator separator-before"><div class="inner"></div></div>
        <div class="month">
            <div class="inner">
                <div class="header">
                    <h3><span>$month_label</span></h3>
                </div><!-- header -->
                <div class="contents">
                    <div class="inner">
                        <table summary="Monthly calendar with links to each day's entries" class="month" cellspacing="0" cellpadding="0">
                            <caption>$month_label</caption>
                            <thead>
                                <tr>""";
                                foreach var int d ( weekdays() ) {
                                    "<th>"+$*lang_dayname_short[$d]+"</th>\n";
                                }
                                """
                                </tr>
                            </thead>
                            <tbody>""";
                            foreach var YearWeek w ($m.weeks) {
                                $w->print();
                            }
                            """
                            </tbody>
                        </table>
                    </div><!-- contents>inner -->
                </div><!-- contents -->
                <div class="footer">
                    <div class="inner">
                        <a href="$m.url">$*text_view_month</a>
                    </div><!-- footer>inner -->
                </div><!-- footer -->
            </div><!-- month>inner -->
        </div><!-- month -->
        <div class="separator separator-after"><div class="inner"></div></div>
    </div><!-- month-wrapper -->
    """;
}
 
function YearWeek::print() {
   "<tr>";
   if ($.pre_empty > 0) {
      """<td class="day-empty" colspan="$.pre_empty">&nbsp;</td>\n""";
   }
   foreach var YearDay d ($.days) {
       if ($d.num_entries > 0) {
            """
            <td class="day day-has-entries">
                <span class="label">$d.day</span>
                <p><a href="$d.url">$d.num_entries</a></p>
            </td>
            """;
       } else {
            """
            <td class="day">
                <span class="label">$d.day</span>
            </td>
            """;       
       }
   }
   if ($.post_empty > 0) {
      """<td class="day-empty" colspan="$.post_empty">&nbsp;</td>\n""";
   }
 
   "</tr>";
}
 
function MonthPage::view_title : string {
    return $.date->date_format($*lang_fmt_month_long);
}
 
function MonthPage::print_body {
    """
    <div id="archive-month">
        <div class="inner">
    """;
    $this->print_navigation( { "class" => "topnav" } );
 
    """
            <div class="month">
                <div class="inner">
                    <dl>
                    """;
 
    foreach var MonthDay d ($.days) {
        if ($d.has_entries) {
            """<dt><a href="$d.url">""";
            print lang_ordinal($d.day);
            "</a></dt>";
 
            "\n<dd>";
            $d->print_subjectlist();
            "</dd>\n";
        }
    }
                """
                </dl>
            </div><!-- month>inner -->
        </div><!-- month -->
    """;
 
    $this->print_navigation( { "class" => "bottomnav" } );
 
    """
        </div><!-- archive-month>inner -->
    </div><!--archive-month -->
    """;
}
 
function MonthPage::print_navigation( string{} opts ) [fixed] {
    """
    <div class="navigation $opts{"class"}">
        <div class="inner"> 
            <ul>
            <form method='post' action='$.redir.url'>
            """;
 
            $.redir->print_hiddens();
 
            if ($.prev_url != "") { "<li class='month-back'>[<a href='$.prev_url'>&lt;&lt;&lt;</a>]</li>\n"; }
 
            if (size $.months > 1) {
                "<li><select name='redir_key'>\n";
                foreach var MonthEntryInfo mei ($.months) {
                    var string sel;
                    if ($mei.date.year == $.date.year and $mei.date.month == $.date.month) {
                        $sel = " selected='selected'";
                    }
                    "<option value='$mei.redir_key'$sel>" + $mei.date->date_format($*lang_fmt_month_long) + "</option>";
                }
                "</select>\n<input type='submit' value='View' /></li>\n";
            }
 
            if ($.next_url != "") { "\n<li class='month-forward'>[<a href='$.next_url'>&gt;&gt;&gt;</a>]</li>\n"; }
 
            """
            </form>
            </ul>
        </div><!-- navigation>inner -->
    </div><!-- navigation -->
    """;
}
 
function MonthDay::print_subjectlist() {
    # Too many tables...
    var Page p = get_page();
    foreach var Entry e ($.entries) {
        if ($p.timeformat24) {
            $e->print_time("none", "short_24");
        } else {
            $e->print_time("none", "short");
        }
        $e->print_poster();
        $e->print_metatypes();
        $e->print_subject();
        if ($e.comments.count > 0) {
            print safe " - " + get_plural_phrase($e.comments.count, "text_read_comments");
        }
        if ($e.comments.screened) {
            print safe " <b>$*text_month_screened_comments</b>";
        }
        "<br />\n";
        $e->print_tags();
        "<br />\n";
    }
}
 
### Day view

function DayPage::print_body() {
    """
    <div id="archive-day">
        <div class="inner">
            <div id="entries">
                <div class="inner">
                    """;
                    $this->print_navigation( { "class" => "topnav" } );
                    """
                    <div class="day">
                        <div class="inner">
                    """;
                    print "<h3 class='day-date'>" + $.date->date_format( "long" ) + "</h3>";
                    if ($.has_entries) {
                        foreach var Entry e ($.entries) {
                            $this->print_entry($e);
                        }
                    } else {
                        print safe "<div class=\"text_noentries text_noentries_day\">$*text_noentries_day</div>";
                    }
 
                    """
                        </div><!-- day>inner -->
                    </div><!-- day -->
                    """;
 
                    $this->print_navigation( { "class" => "bottomnav" } );
                    """
                </div> <!-- entries>inner -->
            </div> <!-- entries -->
        </div> <!-- archive-day>inner -->
    </div><!-- archive-day -->
    """;
}
 
function DayPage::print_navigation( string{} opts ) [fixed] {
    var bool empty = $.prev_url == "" and $.next_url == "";
    """
    <div class="navigation $opts{"class"}">
        <div class="inner">
    """;
 
    if ( not $empty ) { "<ul>"; }
    if ( $.prev_url != "" ) {
        print safe """<li class="page-back"><a href="$.prev_url">$*text_day_prev</a></li>\n""";
    }
    if ( $.next_url != "" ) {
        print safe """<li class="page-forward"><a href="$.next_url">$*text_day_next</a></li>\n""";
    }
    if ( not $empty ) { "</ul>"; }
 
    """
        </div><!-- navigation>inner -->
    </div><!-- navigation -->
    """;
}
 
### CommentInfo functions

function CommentInfo::print_readlink {
    var Page p = get_page();
    print safe "<a href=\"$.read_url#comments\">"+
        get_plural_phrase($.count, $p.view == "read" ?
                          "text_read_comments_friends" : "text_read_comments")+
    "</a>";
}
function CommentInfo::print_postlink() {
    var Page p = get_page();
    if ($.maxcomments) {
        print safe "$*text_max_comments";
    } else {
        print safe "<a href=\"$.post_url\">"+($p.view == "read" or $p.view == "network" ? $*text_post_comment_friends : $*text_post_comment)+"</a>";
    }
}
function CommentInfo::print() {
        """<ul class="entry-interaction-links">\n""";
        """<li class="entry-permalink first-item"><a href="$.permalink_url">$*text_permalink</a></li>\n""";
        if ($.show_readlink) {
            """<li class="entry-readlink">""";
            $this->print_readlink();
            "</li>\n";
        }
        if ($.show_postlink and $.enabled) {
            """<li class="entry-replylink">""";
            $this->print_postlink();
            "</li>\n";
        }
        "</ul>";
}
 
 
### Link object functions

function Link::print_button() [fixed] {
    print $this->as_string();
}
 
function Link::as_string() [fixed] : string {
    if ($.url == "") { return ""; }
    var string ealt = ehtml($.caption);
    var string{} extraLink = $.extra;
    var string{} extraImg = $.icon.extra;
    var string extraParamsLink = "";
    var string extraParamsImg = "";
 
    foreach var string extraKeyLink ($extraLink) {
        $extraParamsLink = $extraParamsLink + """$extraKeyLink="$extraLink{$extraKeyLink}" """;
    }
 
    foreach var string extraKeyImg ($extraImg) {
        $extraParamsImg = $extraParamsImg + """$extraKeyImg="$extraImg{$extraKeyImg}" """;
    }
 
    return """<a href="$.url" $extraParamsLink><img border='0' width="$.icon.width" height="$.icon.height" alt="$ealt" title="$ealt" src="$.icon.url" $extraParamsImg /></a>""";
}
 
# Redirector

function Redirector::start_form ()
{
    "<form method='post' action='$.url' style='display: inline'>";
    $this->print_hiddens();
}
function Redirector::print_hiddens ()
{
    "<input type='hidden' name='redir_user' value='$.user' />\n";
    "<input type='hidden' name='redir_vhost' value='$.vhost' />\n";
    "<input type='hidden' name='redir_type' value='$.type' />\n";
}
function Redirector::end_form ()
{
    "</form>";
}
 
### EntryPage functions

function EntryPage::print_comment_section(Entry e) {
   "<div id='comments'><div class='inner'>";
   $.comment_pages->print({ "anchor" => "comments", "class" => "comment-pages" });
   if ( $e.comments.comments_disabled_maintainer ) {
       """<div class='comments-message'>$*text_comments_disabled_maintainer</div>""";
   } 
   if ($.comment_pages.total_subitems > 0) {
       $this->print_multiform_start();
   }
   $this->print_comments($.comments);
   if ($.comment_pages.total_subitems > 0) {
        "<div class='bottomcomment'>";
        $e->print_management_links();
        $e->print_interaction_links("bottomcomment");
        $this->print_reply_container({ "target" => "bottomcomment" });
        $this->print_multiform_actionline();
        $this->print_multiform_end();
        "</div>";
   }
   $.comment_pages->print({ "anchor" => "comments", "class" => "comment-pages" });
    "</div></div>";
}
 
function EntryPage::print_comments (Comment[] cs) {
    if (size $cs == 0) { return; }
    foreach var Comment c ($cs) {
        var string parity = $c.depth % 2 ? "odd" : "even";
        var int indent = ($c.depth - 1) * 25;
        "<div class='comment-thread comment-depth-$parity comment-depth-$c.depth'>\n";
        "<div id='$c.dom_id' style='margin-left: ${indent}px; margin-top: 5px'>\n";
        if ($c.full) {
            $this->print_comment($c);
        } else {
            $this->print_comment_partial($c);
        }
        "</div>";
        $this->print_comments($c.replies);
        "</div>"; 
    }
}
 
function ReplyPage::print_comment (Comment c) {
# ReplyPage and EntryPage work nicest if they output the same structure for printing comments and entries, but this requires to manually change both ReplyPage::print_comment and EntryPage::print_comment.  Layout authors can also choose to override separately for different structures.
# Note that there is no multiform check on the ReplyPage.

    $c->print_wrapper_start();
    """<div class="header">\n""";
    $c->print_subject();
    $c->print_metatypes();
    $c->print_time();
    """</div>\n""";
    """<div class="contents">\n""";
    """<div class="inner">\n""";
    $c->print_userpic();
    """<div class="poster-information">\n""";
    $c->print_poster();
    $c->print_metadata();
    """</div>\n""";
    $c->print_text();
    """</div>\n""";
    """</div>\n""";
    """<div class="footer">\n""";
    """<div class="inner">\n""";
    $c->print_management_links();
    $c->print_interaction_links();
    $c->print_reply_container();
    "</div>\n</div>\n";
    $c->print_wrapper_end();   
}
 
function EntryPage::print_comment (Comment c) {
# ReplyPage and EntryPage work nicest if they output the same structure for printing comments and entries, but this requires to manually change both ReplyPage::print_comment and EntryPage::print_entry.  Layout authors can also choose to override separately for different structures.

    $c->print_wrapper_start();
    """<div class="header">\n""";
    $c->print_subject();
    $c->print_metatypes();
    $c->print_time();
    """</div>\n""";
    """<div class="contents">\n""";
    """<div class="inner">\n""";
    $c->print_userpic();
    $c->print_poster();
    $c->print_metadata();
    $c->print_text();
    """</div>\n""";
    """</div>\n""";
    """<div class="footer">\n""";
    """<div class="inner">\n""";
    if ($this.multiform_on) {
        """<span class="multiform-checkbox">""";
        print safe " <label for='ljcomsel_$c.talkid'>$*text_multiform_check</label> ";
        $c->print_multiform_check();
	"</span>";
    }
    $c->print_management_links();
    $c->print_interaction_links();
    $c->print_reply_container();
    "</div>\n</div>\n";
    $c->print_wrapper_end();
}
 
function EntryPage::print_comment_partial (Comment c) {
    $c->print_wrapper_start();
    if ($c.deleted) { print $*text_deleted; }
    elseif ($c.fromsuspended) { print $*text_fromsuspended; }
    else {
        var string poster = defined $c.poster ? $c.poster->as_string() : "<i>$*text_poster_anonymous</i>";
        $c->print_subject();
        print safe " - $poster";
        var Link expand_link = $c->get_link("expand_comments");
        if (defined $expand_link) {
            " "; $c->print_expand_link();
        }
    }
    $c->print_wrapper_end();
}
 
function ItemRange::print() {
    $this->print({ "anchor" => "", "class" => "" });
}
 
function ItemRange::print(string{} opts) {
    if ($.all_subitems_displayed) { return; }
 
    var string anchor = $opts{"anchor"} ? "#$opts{"anchor"}" : "";
    var string class = $opts{"class"} ? $opts{"class"} : "pages";
 
    """<div class="$class">""";
    print "<b>" + lang_page_of_pages($.current, $.total) + "</b>";
    var string url_prev = $this->url_of($.current - 1);
    if ($.current != 1) {
        print " <a href='$url_prev$anchor'>$*comment_page_prev</a> ";
    } else {  
        print " $*comment_page_prev "; 
    }
    foreach var int i (1..$.total) {
        if ($i == $.current) { "<b>[$i]</b> "; }
        else {
            var string url_of = $this->url_of($i);
            "<a href='$url_of$anchor'><b>[$i]</b></a> ";
        }
    }
    var string url_next = $this->url_of($.current + 1);
    if ($.current != $.total) {
         print " <a href='$url_next$anchor'>$*comment_page_next</a> ";  
    } else { 
         print " $*comment_page_next ";
    }
    "</div>";
}
 
function EntryPage::print_body
{
    var Entry e = $.entry;
    $this->print_entry($e);
    $this->print_comment_section($e);
}
 
function ReplyPage::print_body
{
    if (not $.entry.comments.enabled) {
        print safe "<h2>$*text_reply_nocomments_header</h2><p>$*text_reply_nocomments</p>";
        return;
    }
 
    if ($.replyto isa Entry) {
        var Entry e = $.replyto as Entry;
        $this->print_entry($e);
    }
    elseif ($.replyto isa Comment) {
        var Comment c = $.replyto as Comment;
        $this->print_comment($c);
    }
    $.form->print();
}
 
### TagsPage functions

function TagsPage::print_body
{
    print safe "<div class='tags-container'><h2>$*text_tags_page_header</h2>";
 
    if ($*tags_page_type == "multi") {
        print_multilevel_tags($.tags, { "list-class" => "ljtaglist tags_multilevel", "print_uses" => $*tags_page_count_type });
        print_tag_manage_link();
    }
    elseif ($*tags_page_type == "cloud") {
        print_cloud_tags($.tags, { "list-class" => "ljtaglist tags_cloud", "print_uses" => $*tags_page_count_type });
        print_tag_manage_link();
    }
    else {
        print_list_tags($.tags, { "list-class" => "ljtaglist tags_list", "print_uses" => $*tags_page_count_type });
        print_tag_manage_link();
    }
 
    print safe "</div>";
}
 
### MessagePage functions

function MessagePage::view_title() : string {
    return $.title;
}
 
function MessagePage::print_body() {
    $this->print_links();
    $this->print_message();
}
 
function MessagePage::print_message() {
    print $.message;
}
 
function MessagePage::print_links() {
    println """<div style='text-align: center;'>""";
    foreach var string k ($.link_keyseq) {
        println """ $.links{$k} """;
    }
    println """</div>""";
}