fu: Close-up of Fu, bringing a scoop of water to her mouth (Default)
fu ([personal profile] fu) wrote in [site community profile] changelog2011-10-31 05:23 am

[dw-free] Revamp /update

[commit: http://hg.dwscoalition.org/dw-free/rev/63e3af5ee420]

http://bugs.dwscoalition.org/show_bug.cgi?id=2524

First big batch of updates to the update page: tweaked the workflow, allows
hiding and reordering of modules, revamped frontend and backend. Actually
posts and saves data. No: RTE, drafts, a few other things. Requires turning
on the update page beta (different from the jquery beta). This version is
more than a rough draft, less than fully polished. Needs feedback which can
only be gleaned from actual usage. Lots of help from [personal profile] hope who
transformed the original very rough drafts 90% of the way into the version
seen here today.

Patch by [personal profile] fu.

Files modified:
  • bin/upgrading/en.dat
  • bin/upgrading/proplists.dat
  • cgi-bin/DW/Controller.pm
  • cgi-bin/DW/Controller/Entry.pm
  • cgi-bin/DW/Template.pm
  • cgi-bin/DW/Template/Plugin/FormHTML.pm
  • cgi-bin/LJ/Entry.pm
  • cgi-bin/LJ/User.pm
  • htdocs/img/silk/site/cog.png
  • htdocs/js/jquery.autocompletewithunknown.js
  • htdocs/js/jquery.crosspost.js
  • htdocs/js/jquery.iconrandom.js
  • htdocs/js/jquery.iconselector.js
  • htdocs/js/jquery.postform.js
  • htdocs/js/jquery.postoptions.js
  • htdocs/js/jquery.radioreveal.js
  • htdocs/js/jquery.tagselector.js
  • htdocs/js/jquery.vertigro.js
  • htdocs/preview/entry.bml
  • htdocs/stc/base-colors-dark.css
  • htdocs/stc/base-colors-light.css
  • htdocs/stc/blueshift/blueshift.css
  • htdocs/stc/celerity/celerity.css
  • htdocs/stc/gradation/gradation.css
  • htdocs/stc/jquery.autocompletewithunknown.css
  • htdocs/stc/jquery.iconselector.css
  • htdocs/stc/jquery.postoptions.css
  • htdocs/stc/jquery.tagselector.css
  • htdocs/stc/lynx/lynx.css
  • htdocs/stc/postform-minimal.css
  • htdocs/stc/postform-resize.css
  • htdocs/stc/postform.css
  • htdocs/stc/simple-form.css
  • htdocs/update.bml
  • schemes/blueshift.tt
  • schemes/celerity.tt
  • schemes/gradation-horizontal.tt
  • schemes/gradation-vertical.tt
  • schemes/lynx.tt
  • t/post.t
  • views/dev/classes.tt
  • views/entry-preview.tt
  • views/entry-preview.tt.text
  • views/entry-success.tt
  • views/entry.tt
  • views/entry.tt.text
  • views/entry/access.tt
  • views/entry/access.tt.text
  • views/entry/age_restriction.tt
  • views/entry/age_restriction.tt.text
  • views/entry/comments-new.tt
  • views/entry/comments.tt
  • views/entry/comments.tt.text
  • views/entry/crosspost.tt
  • views/entry/crosspost.tt.text
  • views/entry/currents.tt
  • views/entry/currents.tt.text
  • views/entry/displaydate.tt
  • views/entry/displaydate.tt.text
  • views/entry/icons.tt
  • views/entry/icons.tt.text
  • views/entry/journal.tt
  • views/entry/journal.tt.text
  • views/entry/options.tt
  • views/entry/options.tt.text
  • views/entry/scheduled.tt
  • views/entry/status.tt
  • views/entry/tags.tt
  • views/entry/tags.tt.text
  • views/success.tt
--------------------------------------------------------------------------------
diff -r 139a07b97016 -r 63e3af5ee420 bin/upgrading/en.dat
--- a/bin/upgrading/en.dat	Mon Oct 31 01:22:31 2011 +0800
+++ b/bin/upgrading/en.dat	Mon Oct 31 13:23:18 2011 +0800
@@ -3789,6 +3789,8 @@
 
 support.email.update.unknown_username=[Unknown or undefined example username]
 
+success=Success
+
 taglib.error.access=You are not allowed to tag entries in this journal.
 
 taglib.error.add=You are not allowed to create new tags for this journal; the entry was not tagged with [[tags]].
@@ -4333,6 +4335,20 @@
 
 widget.betafeature.journaljquery.title=New JS on Journals
 
+widget.betafeature.updatepage.off<<
+<p>Activate the testing version of the new Create Entries management page. This is a complete rewrite of the existing update page in order to modernize the code and allow for future feature expansion. It is <b>not complete</b>, but is reasonably full-featured and will work for most if not all updating purposes.</p>
+
+<p>To report bugs with the new Create Entries page, <a href="http://dw-beta.dreamwidth.org/12259.html">leave a comment</a> in <?ljuser dw_beta ljuser?></p>.
+.
+
+widget.betafeature.updatepage.on<<
+<p>You are currently testing the Create Entries management page. This is a complete rewrite of the existing update page in order to modernize the code and allow for future feature expansion. It is <b>not complete</b>, but is reasonably full-featured and will work for most if not all updating purposes.</p>
+
+<p>To report bugs with the new Create Entries page, <a href="http://dw-beta.dreamwidth.org/12259.html">leave a comment</a> in <?ljuser dw_beta ljuser?></p>.
+.
+
+widget.betafeature.updatepage.title=New Create Entries Page
+
 widget.comms.notavailable=This list is currently unavailable.
 
 widget.comms.recentactive=Recently Active Communities
diff -r 139a07b97016 -r 63e3af5ee420 bin/upgrading/proplists.dat
--- a/bin/upgrading/proplists.dat	Mon Oct 31 01:22:31 2011 +0800
+++ b/bin/upgrading/proplists.dat	Mon Oct 31 13:23:18 2011 +0800
@@ -294,6 +294,22 @@
   multihomed: 0
   prettyname: Default entry editor
 
+userproplist.entryform_width:
+  cldversion: 4
+  datatype: char
+  des: Whether the entry textarea should take up the full width "F" or partial width "P"
+  indexed: 0
+  multihomed: 0
+  prettyname: Width of the entry field when posting an entry
+
+userproplist.entryform_panels:
+  cldversion: 4
+  datatype: blobchar
+  des: Storable hashref containing information about panel visibility, order, collapsedness
+  indexed: 0
+  multihomed: 0
+  prettyname: Entry form panel configuration
+
 userproplist.esn_has_managed:
   cldversion: 4
   datatype: bool
@@ -502,6 +518,14 @@
   multihomed: 0
   prettyname: Journal Box Placement
 
+userproplist.js_animations_minimal:
+  cldversion: 4
+  datatype: bool
+  des: Use minimal animation effects when possible
+  indexed: 0
+  multihomed: 0
+  prettyname: Use minimal JS animations
+
 userproplist.last_fm_user:
   cldversion: 4
   datatype: char
diff -r 139a07b97016 -r 63e3af5ee420 cgi-bin/DW/Controller.pm
--- a/cgi-bin/DW/Controller.pm	Mon Oct 31 01:22:31 2011 +0800
+++ b/cgi-bin/DW/Controller.pm	Mon Oct 31 13:23:18 2011 +0800
@@ -51,7 +51,7 @@
 # return a success page using a language string
 sub success_ml {
     return DW::Template->render_template(
-        'success.tt', { message => LJ::Lang::ml( @_ ) }
+        'success.tt', { message => LJ::Lang::ml( $_[0], $_[1] ), links => $_[2] }
     );
 }
 
diff -r 139a07b97016 -r 63e3af5ee420 cgi-bin/DW/Controller/Entry.pm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/DW/Controller/Entry.pm	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,1154 @@
+#!/usr/bin/perl
+#
+# DW::Controller::Entry
+#
+# This controller is for the create entry page
+#
+# Authors:
+#      Afuna <coder.dw@afunamatata.com>
+#
+# Copyright (c) 2011 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself. For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+#
+
+package DW::Controller::Entry;
+
+use strict;
+
+use DW::Controller;
+use DW::Routing;
+use DW::Template;
+
+use Hash::MultiValue;
+
+
+=head1 NAME
+
+DW::Controller::Entry - Controller which handles posting and editing entries
+
+=head1 Controller API
+
+Handlers for creating and editing entries
+
+=cut
+
+DW::Routing->register_string( '/entry/new', \&new_handler, app => 1 );
+DW::Routing->register_regex( '/entry/([^/]+)/new', \&new_handler, app => 1 );
+
+DW::Routing->register_string( '/entry/preview', \&preview_handler, app => 1, methods => { POST => 1 } );
+
+DW::Routing->register_string( '/entry/options', \&options_handler, app => 1, format => "html" );
+DW::Routing->register_string( '/__rpc_entryoptions', \&options_rpc_handler, app => 1, format => "html" );
+
+                             # /entry/username/ditemid/edit
+#DW::Routing->register_regex( '^/entry/(?:(.+)/)?(\d+)/edit$', \&edit_handler, app => 1 );
+
+
+=head2 C<< DW::Controller::Entry::new_handler( ) >>
+
+Handles posting a new entry
+
+=cut
+sub new_handler {
+    my ( $call_opts, $usejournal ) = @_;
+
+    my $r = DW::Request->get;
+    my $remote = LJ::get_remote();
+
+    return error_ml( "/entry.tt.beta.off", { aopts => "href='$LJ::SITEROOT/betafeatures'" } )
+        unless $remote && LJ::BetaFeatures->user_in_beta( $remote => "updatepage" );
+
+    my @error_list;
+    my @warnings;
+    my $post;
+    my %spellcheck;
+
+    if ( $r->did_post ) {
+        $post = $r->post_args;
+
+        my $mode_preview = $post->{"action:preview"};
+        my $mode_spellcheck = $post->{"action:spellcheck"};
+
+        push @error_list, LJ::Lang::ml( 'bml.badinput.body' )
+            unless LJ::text_in( $post );
+
+        my $okay_formauth = ! $remote || LJ::check_form_auth( $post->{lj_form_auth} );
+
+        # ... but see TODO below
+        push @error_list, LJ::Lang::ml( "error.invalidform" )
+            unless $okay_formauth;
+
+        if ( $mode_preview ) {
+            # do nothing
+        } elsif ( $mode_spellcheck ) {
+            if ( $LJ::SPELLER ) {
+                my $spellchecker = LJ::SpellCheck-> new( {
+                                    spellcommand => $LJ::SPELLER,
+                                    class        => "searchhighlight",
+                                } );
+                my $event = $post->{event};
+                $spellcheck{results} = $spellchecker->check_html( \$event, 1 );
+                $spellcheck{did_spellcheck} = 1;
+            }
+        } elsif ( $okay_formauth ) {
+            my $flags = {};
+
+            my %auth = _auth( $flags, $post, $remote );
+
+            my $uj = $auth{journal};
+            push @error_list, $LJ::MSG_READONLY_USER
+                if $uj && $uj->readonly;
+
+            # do a login action to check if we can authenticate as unverified_username
+            # and to display any important messages connected to your account
+            {
+                # build a clientversion string
+                my $clientversion = "Web/3.0.0";
+
+                # build a request object
+                my %login_req = (
+                    ver             => $LJ::PROTOCOL_VER,
+                    clientversion   => $clientversion,
+                    username        => $auth{unverified_username},
+                );
+
+                my $err;
+                my $login_res = LJ::Protocol::do_request( "login", \%login_req, \$err, $flags );
+
+                unless ( $login_res ) {
+                    push @error_list, LJ::Lang::ml( "/update.bml.error.login" )
+                        . " " . LJ::Protocol::error_message( $err );
+                }
+
+                # e.g. not validated
+                push @warnings, {   type => "info",
+                                    message => LJ::auto_linkify( LJ::ehtml( $login_res->{message} ) )
+                                } if $login_res->{message};
+            }
+
+            my $form_req = {};
+            my %status = _decode( $form_req, $post );
+            push @error_list, @{$status{errors}}
+                if exists $status{errors};
+
+            # if we didn't have any errors with decoding the form, proceed to post
+            unless ( @error_list ) {
+                my %post_res = _do_post( $form_req, $flags, \%auth, warnings => \@warnings );
+
+                # oops errors when posting: show error, fall through to show form
+                push @error_list, $post_res{errors} if $post_res{errors};
+            }
+        }
+    }
+
+    # figure out times
+    my $datetime;
+    my $trust_datetime_value = 0;
+
+    if ( $post && $post->{entrytime} && $post->{entrytime_hr} && $post->{entrytime_min} ) {
+        $datetime = "$post->{entrytime} $post->{entrytime_hr}:$post->{entrytime_min}";
+        $trust_datetime_value = 1;
+    } else {
+        my $now = DateTime->now;
+
+        # if user has timezone, use it!
+        if ( $remote && $remote->prop( "timezone" ) ) {
+            my $tz = $remote->prop( "timezone" );
+            $tz = $tz ? eval { DateTime::TimeZone->new( name => $tz ); } : undef;
+            $now = eval { DateTime->from_epoch( epoch => time(), time_zone => $tz ); }
+               if $tz;
+        }
+
+        $datetime = $now->strftime( "%F %R" ),
+        $trust_datetime_value = 0;  # may want to override with client-side JS
+    }
+
+    my $get = $r->get_args;
+    $usejournal ||= $get->{usejournal};
+    my $vars = init( {  usejournal  => $usejournal,
+                        altlogin    => $get->{altlogin},
+                        datetime    => $datetime || "",
+                        trust_datetime_value => $trust_datetime_value,
+                      }, @_ );
+
+    # these kinds of errors prevent us from initiating the form at all
+    # so abort and return it without the form
+    return error_ml( $vars->{abort}, $vars->{args} )
+        if $vars->{abort};
+
+
+    # now look for errors that we still want to recover from
+    push @error_list, LJ::Lang::ml( "/update.bml.error.invalidusejournal" )
+        if defined $usejournal && ! $vars->{usejournal};
+
+    # this is an error in the user-submitted data, so regenerate the form with the error message and previous values
+    $vars->{error_list} = \@error_list if @error_list;
+    $vars->{warnings} = \@warnings;
+
+    $vars->{spellcheck} = \%spellcheck;
+
+    # prepopulate if we haven't been through this form already
+    $vars->{formdata} = $post || _prepopulate( $get );
+
+    # we don't need this JS magic if we are sending everything over SSL
+    unless ( $LJ::IS_SSL ) {
+        $vars->{chalresp_js} = (! $LJ::REQ_HEAD_HAS{'chalresp_js'}++) ? $LJ::COMMON_CODE{'chalresp_js'} : "";
+        $vars->{login_chal} = LJ::challenge_generate( 3600 ); # one hour to post if they're not logged in
+    }
+
+    $vars->{vclass} = [qw(
+        midimal
+        minimal
+        maximal
+    )]->[$get->{v}||0];
+    $vars->{show_unimplemented} = $get->{highlight} ? 1 : 0;
+    $vars->{betacommunity} = LJ::load_user( "dw_beta" );
+    return DW::Template->render_template( 'entry.tt', $vars );
+}
+
+
+=head2 C<< DW::Controller::Entry::init( ) >>
+
+Initializes entry form values.
+
+Can be used when posting a new entry or editing an old entry. .
+
+Arguments:
+* form_opts: options for initializing the form
+=over
+
+=item altlogin      bool: whether we are posting as someone other than the currently logged in user
+=item usejournal    string: username of the journal we're posting to (if not provided,
+                        use journal of the user we're posting as)
+=item datetime      string: display date of the entry in format "$year-$mon-$mday $hour:$min" (already taking into account timezones)
+
+=back
+
+* call_opts: instance of DW::Routing::CallInfo
+
+=cut
+sub init {
+    my ( $form_opts, $call_opts ) = @_;
+
+    my ( $ok, $rv ) = controller( anonymous => 1 );
+    return $rv unless $ok;
+
+    my $post_as_other = $form_opts->{altlogin} ? 1 : 0;
+    my $u = $post_as_other ? undef : $rv->{remote};
+    my $vars = {};
+
+    my @icons;
+    my $defaulticon;
+
+    my %moodtheme;
+    my @moodlist;
+    my $moods = DW::Mood->get_moods;
+
+    # we check whether the user can actually post to this journal on form submission
+    # journal we explicitly say we want to post to
+    my $usejournal = LJ::load_user( $form_opts->{usejournal} );
+    my @journallist;
+    push @journallist, $usejournal if LJ::isu( $usejournal );
+
+    # the journal we are actually posting to (whether implicitly or overriden by usejournal)
+    my $journalu = LJ::isu( $usejournal ) ? $usejournal : $u;
+
+    my @crosspost_list;
+    my $crosspost_main = 0;
+
+    my $panels;
+    my $formwidth;
+    my $min_animation;
+    if ( $u ) {
+        return { abort => "/update.bml.error.nonusercantpost", args => { sitename => $LJ::SITENAME } }
+            if $u->is_identity;
+
+        return { abort => '/update.bml.error.cantpost' }
+            unless $u->can_post;
+
+        return { abort => '/update.bml.error.disabled' }
+            if $u->can_post_disabled;
+
+
+        # icons
+        @icons = LJ::Userpic->load_user_userpics( $u );
+        @icons = LJ::Userpic->separate_keywords( \@icons )
+            if @icons;
+
+        $defaulticon = $u->userpic;
+
+
+        # moods
+        my $theme = DW::Mood->new( $u->{moodthemeid} );
+
+        if ( $theme ) {
+            $moodtheme{id} = $theme->id;
+            foreach my $mood ( values %$moods )  {
+                $theme->get_picture( $mood->{id}, \ my %pic );
+                next unless keys %pic;
+
+                $moodtheme{pics}->{$mood->{id}}->{pic} = $pic{pic};
+                $moodtheme{pics}->{$mood->{id}}->{width} = $pic{w};
+                $moodtheme{pics}->{$mood->{id}}->{height} = $pic{h};
+                $moodtheme{pics}->{$mood->{id}}->{name} = $mood->{name};
+            }
+        }
+
+
+        @journallist = ( $u, $u->posting_access_list )
+            unless $usejournal;
+
+
+        # crosspost
+        my @accounts = DW::External::Account->get_external_accounts( $u );
+        if ( scalar @accounts ) {
+            foreach my $acct ( @accounts ) {
+                my $selected;
+
+                # FIXME: edit, spellcheck
+                $selected = $acct->xpostbydefault;
+
+                push @crosspost_list, {
+                    id          => $acct->acctid,
+                    name        => $acct->displayname,
+                    selected    => $selected,
+                    need_password => $acct->password ? 0 : 1,
+                };
+
+                $crosspost_main = 1 if $selected;
+            }
+        }
+
+        $panels = $u->entryform_panels;
+        $formwidth = $u->entryform_width;
+        $min_animation = $u->prop( "js_animations_minimal" ) ? 1 : 0;
+    }
+
+    @moodlist = ( { id => "", name => LJ::Lang::ml( "entryform.mood.noneother" ) } );
+    push @moodlist, { id => $_, name => $moods->{$_}->{name} }
+        foreach sort { $moods->{$a}->{name} cmp $moods->{$b}->{name} } keys %$moods;
+
+    my ( @security, @custom_groups );
+    if ( $journalu && $journalu->is_community ) {
+        @security = (
+            { value => "public",  label => LJ::Lang::ml( 'label.security.public2' ) },
+            { value => "access",  label => LJ::Lang::ml( 'label.security.members' ) },
+            { value => "private", label => LJ::Lang::ml( 'label.security.maintainers' ) },
+        );
+    } else {
+        @security = (
+            { value => "public",  label => LJ::Lang::ml( 'label.security.public2' ) },
+            { value => "access",  label => LJ::Lang::ml( 'label.security.accesslist' ) },
+            { value => "private", label => LJ::Lang::ml( 'label.security.private2' ) },
+        );
+
+        if ( $u ) {
+            @custom_groups = map { { value => $_->{groupnum}, label => $_->{groupname} } } $u->trust_groups;
+
+            push @security, { value => "custom", label => LJ::Lang::ml( 'label.security.custom' ) }
+                if @custom_groups;
+        }
+    }
+
+    my ( $year, $mon, $mday, $hour, $min ) = split( /\D/, $form_opts->{datetime} || "" );
+    my %displaydate;
+    $displaydate{year}  = $year;
+    $displaydate{month} = $mon;
+    $displaydate{day}   = $mday;
+    $displaydate{hour}  = $hour;
+    $displaydate{minute}   = $min;
+
+    $displaydate{trust_initial} = $form_opts->{trust_datetime_value};
+
+# TODO:
+#             # JavaScript sets this value, so we know that the time we get is correct
+#             # but always trust the time if we've been through the form already
+#             my $date_diff = ($opts->{'mode'} eq "edit" || $opts->{'spellcheck_html'}) ? 1 : 0;
+
+    $vars = {
+        remote => $u,
+
+        icons       => @icons ? [ { userpic => $defaulticon }, @icons ] : [],
+        defaulticon => $defaulticon,
+
+        moodtheme => \%moodtheme,
+        moods     => \@moodlist,
+
+        journallist => \@journallist,
+        usejournal  => $usejournal,
+        post_as     => $form_opts->{altlogin} ? "other" : "remote",
+
+        security     => \@security,
+        customgroups => \@custom_groups,
+
+        journalu    => $journalu,
+
+        crosspost_entry => $crosspost_main,
+        crosspostlist => \@crosspost_list,
+        crosspost_url => "$LJ::SITEROOT/manage/settings/?cat=othersites",
+
+        displaydate => \%displaydate,
+
+
+        can_spellcheck => $LJ::SPELLER,
+
+        panels      => $panels,
+        formwidth   => $formwidth eq "P" ? "narrow" : "wide",
+        min_animation => $min_animation ? 1 : 0,
+    };
+
+    return $vars;
+}
+
+=head2 C<< DW::Controller::Entry::edit_handler( ) >>
+
+Handles generating the form for, and handling the actual edit of an entry
+
+=cut
+sub edit_handler {
+    # FIXME: this needs careful handling for auth, but for right now let me just skip that altogether
+    return _edit(@_);
+}
+
+# FIXME: remove
+sub _edit {
+    my ( $opts, $username, $ditemid ) = @_;
+}
+
+# returns:
+# poster: user object that contains the poster of the entry. may be the current remote user,
+#           or may be someone logging in via the login form on the entry
+# journal: user object for the journal the entry is being posted to. may be the same as the
+#           poster, or may be a community
+# unverified_username: username that current remote is trying to post as; remote may not
+#           actually have access to this journal so don't treat as trusted
+#
+# modifies/sets:
+# flags: hashref of flags for the protocol
+#   noauth = 1 if the user is the same as remote or has authenticated successfully
+#   u = user we're posting as
+
+sub _auth {
+    my ( $flags, $post, $remote, $referer ) = @_;
+    # referer only should be passed in if outside web context, such as when running tests
+
+    my %auth;
+    foreach ( qw( username chal response password ) ) {
+        $auth{$_} = $post->{$_} || "";
+    }
+    $auth{post_as_other} = ( $post->{post_as} || "" ) eq "other" ? 1 : 0;
+
+    my $user_is_remote = $remote && $remote->user eq $auth{username};
+    my %ret;
+
+    if ( $auth{username}            # user argument given
+        && ! $user_is_remote        # user != remote
+        && ( ! $remote || $auth{post_as_other} ) ) {  # user not logged in, or user is posting as other
+
+        my $u = LJ::load_user( $auth{username} );
+
+        my $ok;
+        if ( $auth{response} ) {
+            # verify entered password, if it is present
+            $ok = LJ::challenge_check_login( $u, $auth{chal}, $auth{response} );
+        } else {
+            # js disabled, fallback to plaintext
+            $ok = LJ::auth_okay( $u, $auth{password} );
+        }
+
+        if ( $ok ) {
+            $flags->{noauth} = 1;
+            $flags->{u} = $u;
+
+            $ret{poster} = $u;
+            $ret{journal} = $post->{postas_usejournal} ? LJ::load_user( $post->{postas_usejournal} ) : $u;
+        }
+    } elsif ( $remote && LJ::check_referer( undef, $referer ) ) {
+        $flags->{noauth} = 1;
+        $flags->{u} = $remote;
+
+        $ret{poster} = $remote;
+        $ret{journal} = $post->{usejournal} ? LJ::load_user( $post->{usejournal} ) : $remote;
+    }
+
+    $ret{unverified_username} = $ret{poster} ? $ret{poster}->username : $auth{username};
+    return %ret;
+}
+
+# decodes the posted form into a hash suitable for use with the protocol
+# $post is expected to be an instance of Hash::MultiValue
+sub _decode {
+    my ( $req, $post ) = @_;
+
+    my @errors;
+
+    # handle event subject and body
+    $req->{subject} = $post->{subject};
+    $req->{event} = $post->{event} || "";
+
+    push @errors, LJ::Lang::ml( "/update.bml.error.noentry" )
+        if $req->{event} eq "";
+
+
+    # initialize props hash
+    $req->{props} ||= {};
+    my $props = $req->{props};
+
+    my %mapped_props = (
+        # currents / metadata
+        current_mood        => "current_moodid",
+        current_mood_other  => "current_mood",
+        current_music       => "current_music",
+        current_location    => "current_location",
+
+        taglist             => "taglist",
+
+        icon                => "picture_keyword",
+    );
+    while ( my ( $formname, $propname ) = each %mapped_props ) {
+        $props->{$propname} = $post->{$formname}
+            if defined $post->{$formname};
+    }
+    $props->{opt_backdated} = $post->{entrytime_outoforder} ? 1 : 0;
+    # FIXME
+    $props->{opt_preformatted} = 0;
+#     $req->{"prop_opt_preformatted"} ||= $POST->{'switched_rte_on'} ? 1 :
+#         $POST->{event_format} && $POST->{event_format} eq "preformatted" ? 1 : 0;
+
+    # old implementation of comments
+    # FIXME: remove this before taking the page out of beta
+    $props->{opt_screening}  = $post->{opt_screening};
+    $props->{opt_nocomments} = $post->{comment_settings} && $post->{comment_settings} eq "nocomments" ? 1 : 0;
+    $props->{opt_noemail}    = $post->{comment_settings} && $post->{comment_settings} eq "noemail" ? 1 : 0;
+
+
+    # see if an "other" mood they typed in has an equivalent moodid
+    if ( $props->{current_mood} ) {
+        if ( my $moodid = DW::Mood->mood_id( $props->{current_mood} ) ) {
+            $props->{current_moodid} = $moodid;
+            delete $props->{current_mood};
+        }
+    }
+
+
+    # nuke taglists that are just blank
+    $props->{taglist} = "" unless $props->{taglist} && $props->{taglist} =~ /\S/;
+
+    if ( LJ::is_enabled( 'adult_content' ) ) {
+        $props->{adult_content} = {
+            ''              => '',
+            'none'          => 'none',
+            'discretion'    => 'concepts',
+            'restricted'    => 'explicit',
+        }->{$post->{age_restriction}} || "";
+
+        $props->{adult_content_reason} = $post->{age_restriction_reason} || "";
+    }
+
+
+    # entry security
+    my $sec = "public";
+    my $amask = 0;
+    {
+        my $security = $post->{security} || "";
+        if ( $security eq "private" ) {
+            $sec = "private";
+        } elsif ( $security eq "access" ) {
+            $sec = "usemask";
+            $amask = 1;
+        } elsif ( $security eq "custom" ) {
+            $sec = "usemask";
+            foreach my $bit ( $post->get_all( "custom_bit" ) ) {
+                $amask |= (1 << $bit);
+            }
+        }
+    }
+    $req->{security} = $sec;
+    $req->{allowmask} = $amask;
+
+
+    # date/time
+    my ( $year, $month, $day ) = split( /\D/, $post->{entrytime} || "" );
+    my ( $hour, $min ) = ( $post->{entrytime_hr}, $post->{entrytime_min} );
+
+    # if we trust_datetime, it's because we either are in a mode where we've saved the datetime before (e.g., edit)
+    # or we have run the JS that syncs the datetime with the user's current time
+    # we also have to trust the datetime when the user has JS disabled, because otherwise we won't have any fallback value
+    if ( $post->{trust_datetime} || $post->{nojs} ) {
+        delete $req->{tz};
+        $req->{year}    = $year;
+        $req->{mon}     = $month;
+        $req->{day}     = $day;
+        $req->{hour}    = $hour;
+        $req->{min}     = $min;
+    }
+
+    # crosspost
+    $req->{crosspost_entry} = $post->{crosspost_entry} ? 1 : 0;
+    if ( $req->{crosspost_entry} ) {
+        foreach my $acctid ( $post->get_all( "crosspost" ) ) {
+            $req->{crosspost}->{$acctid} = {
+                id          => $acctid,
+                password    => $post->{"crosspost_password_$acctid"},
+                chal        => $post->{"crosspost_chal_$acctid"},
+                resp        => $post->{"crosspost_resp_$acctid"},
+            };
+        }
+    }
+
+    return ( errors => \@errors ) if @errors;
+
+    return ();
+}
+
+sub _save_new_entry {
+    my ( $form_req, $flags, $auth ) = @_;
+
+    my $req = {
+        ver         => $LJ::PROTOCOL_VER,
+        username    => $auth->{poster} ? $auth->{poster}->user : undef,
+        usejournal  => $auth->{journal} ? $auth->{journal}->user : undef,
+        tz          => 'guess',
+        xpost       => '0', # don't crosspost by default; we handle this ourselves later
+        %$form_req
+    };
+
+
+    my $err = 0;
+    my $res = LJ::Protocol::do_request( "postevent", $req, \$err, $flags );
+
+    return { errors => LJ::Protocol::error_message( $err ) } unless $res;
+    return $res;
+}
+
+sub _do_post {
+    my ( $form_req, $flags, $auth, %opts ) = @_;
+
+    my $res = _save_new_entry( $form_req, $flags, $auth );
+    return %$res if $res->{errors};
+
+    # post succeeded, time to do some housecleaning
+    _persist_props( $auth->{poster}, $form_req );
+
+    my $ret = "";
+    my @links;
+    my @crossposts;
+
+    # we may have warnings generated by previous parts of the process
+    my @warnings = @{ $opts{warnings} || [] };
+
+    # special-case moderated: no itemid, but have a message
+    if ( ! defined $res->{itemid} && $res->{message} ) {
+        $ret .= qq{<div class="message-box info-box"><p>$res->{message}</p></div>};
+        DW::Template->render_template(
+            'entry-success.tt', {
+                poststatus  => $ret,
+            }
+        );
+
+    } else {
+        # e.g., bad HTML in the entry
+        push @warnings, {   type => "warning",
+                            message => LJ::auto_linkify( LJ::ehtml( $res->{message} ) )
+                        } if $res->{message};
+
+        my $u = $auth->{poster};
+        my $ju = $auth->{journal} || $auth->{poster};
+
+
+        # we updated successfully! Now tell the user
+        my $update_ml = $ju->is_community ? "/update.bml.update.success2.community" : "/update.bml.update.success2";
+        $ret .= LJ::Lang::ml( $update_ml, {
+            aopts => "href='" . $ju->journal_base . "/'",
+        } );
+
+
+
+        # bunch of helpful links
+        my $juser = $ju->user;
+        my $ditemid = $res->{itemid} * 256 + $res->{anum};
+        my $itemlink = $res->{url};
+        my $edititemlink = "$LJ::SITEROOT/editjournal?journal=$juser&itemid=$ditemid";
+
+        my @links = (
+            { url => $itemlink,
+                text => LJ::Lang::ml( "/update.bml.success.links.view" ) }
+        );
+
+        if ( $form_req->{props}->{opt_backdated} ) {
+            # we have to do some gymnastics to figure out the entry date
+            my $e = LJ::Entry->new_from_url( $itemlink );
+            my ( $y, $m, $d ) = ( $e->{eventtime} =~ /^(\d+)-(\d+)-(\d+)/ );
+            push @links, {
+                url => $ju->journal_base . "/$y/$m/$d/",
+                text => LJ::Lang::ml( "/update.bml.success.links.backdated" ),
+            }
+        }
+
+        push @links, (
+            { url => $edititemlink,
+                text => LJ::Lang::ml( "/update.bml.success.links.edit" ) },
+            { url => "$LJ::SITEROOT/tools/memadd?journal=$juser&itemid=$ditemid",
+                text => LJ::Lang::ml( "/update.bml.success.links.memories" ) },
+            { url => "$LJ::SITEROOT/edittags?journal=$juser&itemid=$ditemid",
+                text => LJ::Lang::ml( "/update.bml.success.links.tags" ) },
+        );
+
+
+        # crosspost!
+        my @crossposts;
+        if ( $u->equals( $ju ) && $form_req->{crosspost_entry} ) {
+            my $user_crosspost = $form_req->{crosspost};
+            my ( $xpost_successes, $xpost_errors ) =
+                LJ::Protocol::schedule_xposts( $u, $res->{itemid}, 0,
+                        sub {
+                            my $submitted = $user_crosspost->{$_[0]->acctid} || {};
+
+                            # first argument is true if user checked the box
+                            # false otherwise
+                            return ( $submitted->{id} ? 1 : 0,
+                                {
+                                    password => $submitted->{password},
+                                    auth_challenge => $submitted->{chal},
+                                    auth_response => $submitted->{resp},
+                                }
+                            );
+                        } );
+
+            foreach my $crosspost ( @{$xpost_successes||[]} ) {
+                push @crossposts, { text => LJ::Lang::ml( "xpost.request.success2", {
+                                                account => $crosspost->displayname,
+                                                sitenameshort => $LJ::SITENAMESHORT,
+                                            } ),
+                                    status => "ok",
+                                };
+            }
+
+            foreach my $crosspost( @{$xpost_errors||[]} ) {
+                push @crossposts, { text => LJ::Lang::ml( 'xpost.request.failed', {
+                                                    account => $crosspost->displayname,
+                                                    editurl => $edititemlink,
+                                                } ),
+                                    status => "error",
+                                 };
+            }
+        }
+
+        DW::Template->render_template(
+            'entry-success.tt', {
+                poststatus  => $ret,        # did the update succeed or fail?
+                warnings    => \@warnings,   # warnings about the entry or your account
+                crossposts  => \@crossposts,# crosspost status list
+                links       => \@links,
+            }
+        );
+    }
+
+    return ( status => "ok" );
+}
+
+# remember value of properties, to use the next time the user makes a post
+sub _persist_props {
+    my ( $u, $form ) = @_;
+
+    return unless $u;
+# FIXME:
+#
+#                 # persist the default value of the disable auto-formatting option
+#                 $u->disable_auto_formatting( $POST{event_format} ? 1 : 0 );
+#
+#                 # Clear out a draft
+#                 $remote->set_prop('entry_draft', '')
+#                     if $remote;
+#
+#                 # Store what editor they last used
+#                 unless (!$remote || $remote->prop('entry_editor') =~ /^always_/) {
+#                      $POST{'switched_rte_on'} ?
+#                          $remote->set_prop('entry_editor', 'rich') :
+#                          $remote->set_prop('entry_editor', 'plain');
+#                  }
+
+}
+
+sub _prepopulate {
+    my $get = $_[0];
+
+    my $subject = $get->{subject};
+    my $event   = $get->{event};
+    my $tags    = $get->{tags};
+
+    # if a share url was passed in, fill in the fields with the appropriate text
+    if ( $get->{share} ) {
+        eval "use DW::External::Page; 1;";
+        if ( ! $@ && ( my $page = DW::External::Page->new( url => $get->{share} ) ) ) {
+            $subject = LJ::ehtml( $page->title );
+            $event = '<a href="' . $page->url . '">' . ( LJ::ehtml( $page->description ) || $subject || $page->url ) . "</a>\n\n";
+        }
+    }
+
+    return {
+        subject => $subject,
+        event   => $event,
+        taglist => $tags,
+    };
+}
+
+
+=head2 C<< DW::Controller::Entry::preview_handler( ) >>
+
+Shows a preview of this entry
+
+=cut
+sub preview_handler {
+    my $r = DW::Request->get;
+    my $remote = LJ::get_remote();
+
+    my $post = $r->post_args;
+    my $styleid;
+    my $siteskinned = 1;
+
+    my $altlogin = $post->{post_as} eq "other" ? 1 : 0;
+    my $username = $altlogin ? $post->{username} : $remote->username;
+    my $usejournal = $altlogin ? $post->{postas_usejournal} : $post->{usejournal};
+
+    # figure out poster/journal
+    my ( $u, $up );
+    if ( $usejournal ) {
+        $u = LJ::load_user( $usejournal );
+        $up = $username ? LJ::load_user( $username ) : $remote;
+    } elsif ( $username && $altlogin ) {
+        $u = LJ::load_user( $username );
+    } else {
+        $u = $remote;
+    }
+    $up ||= $u;
+
+    # set up preview variables
+    my ( $ditemid, $anum, $itemid );
+
+    my $form_req = {};
+    _decode( $form_req, $post );    # ignore errors
+
+    my ( $event, $subject ) = ( $form_req->{event}, $form_req->{subject} );
+    LJ::CleanHTML::clean_subject( \$subject );
+
+
+    # parse out embed tags from the RTE
+    $event = LJ::EmbedModule->transform_rte_post( $event );
+
+    # do first expand_embedded pass with the preview flag to extract
+    # embedded content before cleaning and replace with tags
+    # the cleaner won't eat
+    LJ::EmbedModule->parse_module_embed( $u, \$event, preview => 1 );
+
+    # clean content normally
+    LJ::CleanHTML::clean_event( \$event, {
+        preformatted => $form_req->{props}->{opt_preformatted},
+    } );
+
+    # expand the embedded content for real
+    LJ::EmbedModule->expand_entry($u, \$event, preview => 1 );
+
+
+    my $ctx;
+    if ( $u && $up ) {
+        $r->note( "_journal"    => $u->{user} );
+        $r->note( "journalid"   => $u->{userid} );
+
+        # load necessary props
+        $u->preload_props( qw( s2_style ) );
+
+
+        # determine style system to preview with
+        my $forceflag = 0;
+        LJ::Hooks::run_hooks( "force_s1", $u, \$forceflag );
+
+        $ctx = LJ::S2::s2_context( $u->{s2_style} );
+        my $view_entry_disabled = ! LJ::S2::use_journalstyle_entry_page( $u, $ctx );
+
+        if ( $forceflag || $view_entry_disabled ) {
+            # force site-skinned
+            ( $siteskinned, $styleid ) = ( 1, 0 );
+        } else {
+            ( $siteskinned, $styleid ) = ( 0, $u->{s2_style} );
+        }
+    } else {
+        ( $siteskinned, $styleid ) = ( 1, 0 );
+    }
+
+
+    if ( $siteskinned ) {
+        my $vars = {
+            event   => $event,
+            subject => $subject,
+            journal => $u,
+            poster  => $up,
+        };
+
+        my $pic = LJ::Userpic->new_from_keyword( $up, $form_req->{props}->{picture_keyword} );
+        $vars->{icon} = $pic ? $pic->imgtag : undef;
+
+
+        my $etime = LJ::date_to_view_links( $u, "$form_req->{year}-$form_req->{mon}-$form_req->{day}" );
+        my $hour = sprintf( "%02d", $form_req->{hour} );
+        my $min = sprintf( "%02d", $form_req->{min} );
+        $vars->{displaydate} = "$etime $hour:$min:00";
+
+
+        my %current = LJ::currents( $form_req->{props}, $up );
+        if ( $u ) {
+            $current{Groups} = $u->security_group_display( $form_req->{allowmask} );
+            delete $current{Groups} unless $current{Groups};
+        }
+
+        my @taglist = ();
+        LJ::Tags::is_valid_tagstring( $form_req->{props}->{taglist}, \@taglist );
+        if ( @taglist ) {
+            my $base = $u ? $u->journal_base : "";
+            $current{Tags} = join( ', ',
+                                   map { "<a href='$base/tag/" . LJ::eurl( $_ ) . "'>" . LJ::ehtml( $_ ) . "</a>" }
+                                   @taglist
+                               );
+        }
+        $vars->{currents} = LJ::currents_table( %current );
+
+        my $security = "";
+        if ( $form_req->{security} eq "private" ) {
+            $security = BML::fill_template( "securityprivate" );
+        } elsif ( $form_req->{security} eq "usemask" ) {
+            $security = BML::fill_template( "securityprotected" );
+        }
+        $vars->{security} = $security;
+
+        return DW::Template->render_template( 'entry-preview.tt', $vars );
+    } else {
+        my $ret = "";
+        my $opts = {};
+
+        $LJ::S2::ret_ref = \$ret;
+        $opts->{r} = $r;
+
+        $u->{_s2styleid} = ( $styleid || 0 ) + 0;
+        $u->{_journalbase} = $u->journal_base;
+
+        $LJ::S2::CURR_CTX = $ctx;
+
+        my $p = LJ::S2::Page( $u, $opts );
+        $p->{_type} = "EntryPreviewPage";
+        $p->{view} = "entry";
+
+
+        # Mock up entry from form data
+        my $userlite_journal = LJ::S2::UserLite( $u );
+        my $userlite_poster  = LJ::S2::UserLite( $up );
+
+        my $userpic = LJ::S2::Image_userpic( $up, 0, $form_req->{props}->{picture_keyword} );
+        my $comments = LJ::S2::CommentInfo({
+            read_url => "#",
+            post_url => "#",
+            permalink_url => "#",
+            count => "0",
+            maxcomments => 0,
+            enabled => ( $u->{opt_showtalklinks} eq "Y"
+                            && ! $form_req->{props}->{opt_nocomments} ) ? 1 : 0,
+            screened => 0,
+            });
+
+        # build tag objects, faking kwid as '-1'
+        # * invalid tags will be stripped by is_valid_tagstring()
+        my @taglist = ();
+        LJ::Tags::is_valid_tagstring( $form_req->{props}->{taglist}, \@taglist );
+        @taglist = map { LJ::S2::Tag( $u, -1, $_ ) } @taglist;
+
+        # custom friends groups
+        my $group_names = $u ? $u->security_group_display( $form_req->{allowmask} ) : undef;
+
+        # format it
+        my $raw_subj = $form_req->{subject};
+        my $s2entry = LJ::S2::Entry($u, {
+            subject     => $subject,
+            text        => $event,
+            dateparts   => "$form_req->{year} $form_req->{mon} $form_req->{day} $form_req->{hour} $form_req->{min} 00 ",
+            security    => $form_req->{security},
+            allowmask   => $form_req->{allowmask},
+            props       => $form_req->{props},
+            itemid      => -1,
+            comments    => $comments,
+            journal     => $userlite_journal,
+            poster      => $userlite_poster,
+            new_day     => 0,
+            end_day     => 0,
+            tags        => \@taglist,
+            userpic     => $userpic,
+            permalink_url       => "#",
+            adult_content_level => $form_req->{props}->{adult_content},
+            group_names         => $group_names,
+        });
+
+        my $copts;
+        $copts->{out_pages} = $copts->{out_page} = 1;
+        $copts->{out_items} = 0;
+        $copts->{out_itemfirst} = $copts->{out_itemlast} = undef;
+
+        $p->{comment_pages} = LJ::S2::ItemRange({
+            all_subitems_displayed  => ( $copts->{out_pages} == 1 ),
+            current                 => $copts->{out_page},
+            from_subitem            => $copts->{out_itemfirst},
+            num_subitems_displayed  => 0,
+            to_subitem              => $copts->{out_itemlast},
+            total                   => $copts->{out_pages},
+            total_subitems          => $copts->{out_items},
+            _url_of                 => sub { return "#"; },
+        });
+
+        $p->{entry} = $s2entry;
+        $p->{comments} = [];
+        $p->{preview_warn_text} = LJ::Lang::ml( '/entry-preview.tt.entry.preview_warn_text' );
+
+        $p->{viewing_thread} = 0;
+        $p->{multiform_on} = 0;
+
+
+        # page display settings
+        if ( $u->should_block_robots ) {
+            $p->{head_content} .= LJ::robot_meta_tags();
+        }
+        $p->{head_content} .= '<meta http-equiv="Content-Type" content="text/html; charset=' . $opts->{'saycharset'} . "\" />\n";
+        # Don't show the navigation strip or invisible content
+        $p->{head_content} .= qq{
+            <style type="text/css">
+            html body {
+                padding-top: 0 !important;
+            }
+            #lj_controlstrip {
+                display: none !important;
+            }
+            .invisible {
+                position: absolute;
+                left: -10000px;
+                top: auto;
+            }
+            .highlight-box {
+                border: 1px solid #c1272c;
+                background-color: #ffd8d8;
+                color: #000;
+            }
+            </style>
+        };
+
+
+        LJ::S2::s2_run( $r, $ctx, $opts, "EntryPage::print()", $p );
+        return DW::Template->render_string( $ret, { no_sitescheme => 1 } );
+    }
+}
+
+
+=head2 C<< DW::Controller::Entry::options_handler( ) >>
+
+Show the entry options page in a separate page
+
+=cut
+sub options_handler {
+    return DW::Template->render_template( 'entry/options.tt', _options( @_ ) );
+}
+
+
+=head2 C<< DW::Controller::Entry::options_rpc_handler( ) >>
+
+Show the entry options page in a form suitable for loading via JS
+
+=cut
+sub options_rpc_handler {
+    my $vars = _options( @_ );
+    $vars->{use_js} = 1;
+    my $status = @{$vars->{error_list} || []} ? DW::Request->get->HTTP_BAD_REQUEST : DW::Request->get->HTTP_OK;
+
+    return DW::Template->render_template( 'entry/options.tt', $vars, { no_sitescheme => 1, status => $status } );
+}
+
+sub _options {
+    my ( $ok, $rv ) = controller();
+    return $rv unless $ok;
+
+    my $u = $rv->{remote};
+
+    my $panel_element_name = "visible_panels";
+    my @panel_options;
+    foreach ( qw( access comments age_restriction journal crosspost
+                    icons tags currents displaydate ) ) {
+        push @panel_options, {
+            label_ml    => "/entry/$_.tt.header",
+            panel_name  => $_,
+            id          => "panel_$_",
+            name        =>  $panel_element_name,
+        }
+    }
+
+    my $vars = {
+        panels => \@panel_options
+    };
+
+    my $r = DW::Request->get;
+    if ( $r->did_post ) {
+        my $post = $r->post_args;
+        $vars->{formdata} = $post;
+
+        if ( LJ::check_form_auth( $post->{lj_form_auth} ) ) {
+            $u->set_prop( entryform_width => $post->{entry_field_width} );
+
+            my %panels;
+            my %post_panels = map { $_ => 1 } $post->get_all( $panel_element_name );
+            foreach my $panel ( @panel_options ) {
+                my $name = $panel->{panel_name};
+                $panels{$name} = $post_panels{$name} ? 1 : 0;
+            }
+            $u->entryform_panels_visibility( \%panels );
+
+
+            my @columns;
+            foreach my $column_index ( 0...2 ) {
+                my @col;
+
+                foreach ( $post->get_all( "column_$column_index" ) ) {
+                    my ( $order, $panel ) = m/(\d+):(.+)_component/;
+                    $col[$order] = $panel;
+
+                }
+
+                # remove any in-betweens in case we managed to skip a number in the order somehow
+                $columns[$column_index] = [ grep { $_ } @col];
+            }
+            $u->entryform_panels_order( \@columns );
+
+            $u->set_prop( js_animations_minimal => $post->{minimal_animations} );
+        } else {
+            $vars->{error_list} = [ LJ::Lang::ml( "error.invalidform") ];
+        }
+
+    } else {
+
+        my $default = {
+            entry_field_width   => $u->entryform_width,
+            minimal_animations  => $u->prop( "js_animations_minimal" ) ? 1 : 0,
+        };
+
+        my $user_panels = $u->entryform_panels;
+
+        my @panels;
+        foreach my $panel_group ( @{$user_panels->{order}} ) {
+            foreach my $panel ( @$panel_group ) {
+                push @panels, $panel if $user_panels->{show}->{$panel};
+            }
+        }
+        $default->{$panel_element_name} = \@panels;
+
+        $vars->{formdata} = $default;
+    }
+
+    return $vars;
+}
+
+1;
diff -r 139a07b97016 -r 63e3af5ee420 cgi-bin/DW/Template.pm
--- a/cgi-bin/DW/Template.pm	Mon Oct 31 01:22:31 2011 +0800
+++ b/cgi-bin/DW/Template.pm	Mon Oct 31 13:23:18 2011 +0800
@@ -47,6 +47,8 @@
 
     root    => $LJ::SITEROOT,
     imgroot => $LJ::IMGPREFIX,
+    jsroot  => $LJ::JSPREFIX,
+    statroot=> $LJ::STATPREFIX,
     ssl => {
         root => $LJ::SSLROOT,
         imgroot => $LJ::SSLIMGPREFIX,
diff -r 139a07b97016 -r 63e3af5ee420 cgi-bin/DW/Template/Plugin/FormHTML.pm
--- a/cgi-bin/DW/Template/Plugin/FormHTML.pm	Mon Oct 31 01:22:31 2011 +0800
+++ b/cgi-bin/DW/Template/Plugin/FormHTML.pm	Mon Oct 31 13:23:18 2011 +0800
@@ -17,6 +17,8 @@
 use base 'Template::Plugin';
 use strict;
 
+use Hash::MultiValue;
+
 =head1 NAME
 
 DW::Template::Plugin::FormHTML - Template Toolkit plugin to generate HTML elements
@@ -26,9 +28,9 @@
 
 The form plugin generates HTML elements with attributes suitably escaped, and values automatically prepopulated, depending on the form's data field.
 
-The "data" field is an instance of Hash::MultiValue, with the keys being the form element's name, and the values being the form element's desired value.
+The "data" field is a hashref, with the keys being the form element's name, and the values being the form element's desired value.
 
-If a "formdata" property is available via the context, this is used to automatically populate the plugin's data field.
+If a "formdata" property is available via the context, this is used to automatically populate the plugin's data field. It may be either a hashref or an instance of Hash::MultiValue.
 =cut
 
 sub load {
@@ -38,9 +40,15 @@
 sub new {
     my ( $class, $context, @params ) = @_;
 
+    my $data;
+    if ( $context ) {
+        my $formdata = $context->stash->{formdata};
+        $data = ref $formdata eq "Hash::MultiValue" ? $formdata : Hash::MultiValue->from_mixed( $formdata );
+    }
+
     my $self = bless {
         _CONTEXT => $context,
-        data     => $context ? $context->stash->{formdata} : undef,
+        data     => $data,
     }, $class;
 
     return $self;
@@ -199,6 +207,24 @@
     return $ret;
 }
 
+=head2 [% form.password( label="A Label", id="elementid", name="elementname",... ) %]
+
+Return a password field with a matching label, if provided. Values are never prepopulated
+
+=cut
+sub password {
+    my ( $self, $args ) = @_;
+
+    $args->{type} = "password";
+
+    my $ret = "";
+    $ret .= $self->_process_value_and_label( $args, noautofill => 1 );
+    $ret .= LJ::html_text( $args );
+
+    return $ret;
+
+}
+
 
 # populate the element's value, modifying the $args hashref
 # return the label HTML if applicable
@@ -206,17 +232,18 @@
     my ( $self, $args, %opts ) = @_;
 
     my $valuekey = $opts{use_as_value} || "value";
+    my $default = delete $args->{default};
+
     if ( defined $args->{$valuekey} ) {
         # explicitly override with a value when we created the form element
         # do nothing! Just use what we passed in
     } else {
         # we didn't pass in an explicit value; check our data source (probably form post)
-        if ( $self->{data} && ! $opts{noautofill} ) {
+        if ( $self->{data} && ! $opts{noautofill} && $args->{name} ) {
             $args->{$valuekey} = $self->{data}->{$args->{name}};
         }
 
         # no data source, value not set explicitly, use a default if provided
-        my $default = delete $args->{default};
         $args->{$valuekey} ||= $default;
     }
 
diff -r 139a07b97016 -r 63e3af5ee420 cgi-bin/LJ/Entry.pm
--- a/cgi-bin/LJ/Entry.pm	Mon Oct 31 01:22:31 2011 +0800
+++ b/cgi-bin/LJ/Entry.pm	Mon Oct 31 13:23:18 2011 +0800
@@ -957,6 +957,17 @@
     return $self->journal->adult_content_marker;
 }
 
+# return whether this entry has comment emails enabled or not
+sub comment_email_disabled {
+    my $self = $_[0];
+
+    my $entry_no_email = $self->prop( 'opt_noemail' );
+    return $entry_no_email if $entry_no_email;
+
+    #my $journal_no_email = $self->
+    return 0;
+}
+
 # return whether this entry has comments disabled, either by the poster or by the maintainer
 sub comments_disabled {
     my $self = $_[0];
@@ -2196,7 +2207,7 @@
     return unless ref $props eq 'HASH';
     my %current;
 
-    my ( $key, $entry, $s2imgref );
+    my ( $key, $entry, $s2imgref ) = ( "", undef, undef );
     if ( $opts && ref $opts ) {
         $key = $opts->{key} || '';
         $entry = $opts->{entry};
diff -r 139a07b97016 -r 63e3af5ee420 cgi-bin/LJ/User.pm
--- a/cgi-bin/LJ/User.pm	Mon Oct 31 01:22:31 2011 +0800
+++ b/cgi-bin/LJ/User.pm	Mon Oct 31 13:23:18 2011 +0800
@@ -4768,6 +4768,92 @@
     return $u->prop('entry_draft');
 }
 
+sub entryform_width {
+    my ( $u, $prop ) = @_;
+
+    if ( $u->raw_prop( 'entryform_width' ) =~ /^(F|P)$/ ) {
+        return $u->raw_prop( 'entryform_width' )
+    } else {
+        return 'F';
+    }
+}
+
+# getter/setter
+sub entryform_panels {
+    my ( $u, $val ) = @_;
+
+    if ( defined $val ) {
+        $u->set_prop( entryform_panels => Storable::nfreeze( $val ) );
+        return $val;
+    }
+
+    my $prop = $u->prop( "entryform_panels" );
+    return $prop ? Storable::thaw( $prop ) : {
+        order => [ [ "tags", "displaydate" ],
+
+                   # FIXME: should be [ "status"  "journal" "comments" "age_restriction" ] %]
+                   [ "access", "journal", "currents", "comments", "age_restriction" ],
+
+                   # FIXME: should be [ "icons" "crosspost" "scheduled" ]
+                   [ "icons", "crosspost" ],
+                ],
+        show => {
+            "tags"          => 1,
+            "currents"      => 1,
+            "displaydate"   => 0,
+            "access"        => 1,
+            "journal"       => 1,
+            "comments"      => 0,
+            "age_restriction" => 0,
+            "icons"         => 1,
+            "crosspost"     => 0,
+
+            #"scheduled"     => 0,
+            #"status"        => 1,
+        },
+        collapsed => {
+        }
+    };
+}
+
+sub entryform_panels_order {
+    my ( $u, $val ) = @_;
+
+    my $panels = $u->entryform_panels;
+
+    if ( defined $val ) {
+        $panels->{order} = $val;
+        $panels = $u->entryform_panels( $panels );
+    }
+
+    return $panels->{order};
+}
+
+sub entryform_panels_visibility {
+    my ( $u, $val ) = @_;
+
+    my $panels = $u->entryform_panels;
+    if ( defined $val ) {
+        $panels->{show} = $val;
+        $panels = $u->entryform_panels( $panels );
+    }
+
+    return $panels->{show};
+}
+
+sub entryform_panels_collapsed {
+    my ( $u, $val ) = @_;
+
+    my $panels = $u->entryform_panels;
+    if ( defined $val ) {
+        $panels->{collapsed} = $val;
+        $panels = $u->entryform_panels( $panels );
+    }
+
+    return $panels->{collapsed};
+}
+
+
 
 # <LJFUNC>
 # name: LJ::get_post_ids
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/img/silk/site/cog.png
Binary file htdocs/img/silk/site/cog.png has changed
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/js/jquery.autocompletewithunknown.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.autocompletewithunknown.js	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,344 @@
+(function($) {
+    // this doesn't act on the autocompletewithunknown widget. Instead, it's called on our input field with autocompletion
+    function _handleComplete(e, ui, $oldElement) {
+        var self = $(this).data("autocompletewithunknown");
+        var ele = self.element;
+
+        var curtagslist = self.cachemap[self.currentCache];
+
+        var items = ui.item.value.toLowerCase();
+        $.each( items.split(","), function() {
+            var tag = $.trim(this);
+
+            // check if the tag is empty, or if we had previously used it
+            if ( tag == "" ) return;
+
+            // TODO: don't put it in if the word exceeds the maximum length of a tag
+            if ( ! self.tagslist[tag] ) {
+                self.tagslist[tag] = true;
+
+
+                var tokentype = curtagslist && curtagslist[tag] ? self.options.curTokenClass : self.options.newTokenClass;
+
+                var $text = $("<span></span>")
+                    .addClass(self.options.tokenTextClass)
+                    .text(tag);
+                var $a = $("<a></a>").addClass(self.options.tokenRemoveClass).attr({
+                    href: "#remove-"+tag,
+                    title: "Remove " + tag
+                }).text("x");
+
+                var $li = $("<li></li>").addClass(self.options.tokenClass + " " + tokentype)
+                    .append($text).append($a).append(" ")
+                    .attr( "title", tokentype == self.options.curTokenClass ? "tag: " + tag : "new tag: " + tag);
+                if ( $oldElement )
+                    $oldElement.replaceWith($li);
+                else
+                    $li.appendTo(self.uiAutocompletelist);
+            }
+        });
+
+        $("#"+self.options.id+"_count_label").text("characters per tag:");
+        $(this).val("");
+        if ( self.options.grow ) {
+            // shrink
+            $(this).height((parseInt($(this).css('lineHeight').replace(/px$/,''))||20) + 'px');
+            if ( self.options.maxlength )
+                $("#"+self.options.id+"_count").text(self.options.maxlength);
+        }
+
+        var tags_array = [];
+        $.each( self.tagslist, function(key) {
+            tags_array.push( key );
+        });
+
+        ele.val( tags_array.join(",") );
+
+        if ( $.browser.opera ) {
+            var keyCode = $.ui.keyCode;
+            if( e.which == keyCode.ENTER || e.which == keyCode.TAB )
+                self.justCompleted = true;
+        }
+
+        // this prevents the default behavior of having the
+        // form field filled with the autocompleted text.
+        // Target the event type, to avoid issues with selecting using TAB
+        if ( e.type == "autocompleteselect" ) {
+            e.preventDefault();
+            return false;
+        }
+    }
+
+    $.widget("ui.autocompletewithunknown", {
+            options: {
+                id: null,
+                tokenClass: "token",
+                tokenTextClass: "token_text",
+                tokenRemoveClass: "token_remove",
+                newTokenClass: "new",
+                curTokenClass: "autocomplete",
+                numMatches: 20,
+                populateSource: null, // initial location to populate from
+                populateId: "",       // initial identifier for the list to be cached
+                grow: false           // whether to automatically grow the autocomplete textarea or not
+            },
+
+            _filterTags: function( array, term ) {
+                var self = this;
+                var startsWithTerm = [];
+
+                term = $.trim(term.toLowerCase());
+                var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term) );
+
+                var filtered = $.grep( array, function(value) {
+                            var val = value.label || value.value || value;
+                            if ( !self.tagslist[val] && matcher.test( val ) )  {
+                                if ( val.indexOf(term) == 0 ) {
+                                    // ugly, but we'd like to handle terms that start with
+                                    // in a different manner
+                                    startsWithTerm.push({ value: val,
+                                        label: val.replace(term, "<strong>"+term+"</strong>")});
+                                    return false;
+                                }
+                                return true;
+                            }
+                        });
+
+                // second step, because we first need to find out how many
+                // total start with the term, and so need to be in the list
+                // those that only contain the term fill in any remaining slots
+                var responseArray = startsWithTerm;
+                $.each(filtered, function(index, value) {
+                    if ( responseArray.length >= self.options.numMatches )
+                        return false;
+
+                    responseArray.push({ value: value,
+                        label: value.replace(term, "<strong>"+term+"</strong>")});
+                })
+
+                return responseArray;
+            },
+
+            _create: function() {
+                var self = this;
+
+                if (!self.options.id)
+                    self.options.id = "autocomplete_"+self.element.attr("id");
+
+                self.uiAutocomplete = self.element.wrap("<div class='autocomplete_container'></div>").parent().attr("id", self.options.id);
+
+                self.uiAutocompletelist = $("<ul class='autocomplete_list'></ul>").appendTo(self.uiAutocomplete).attr( "aria-live", "assertive" );
+
+                // this is just frontend; will use JS to transfer contents to the original field (now hidden)
+                self.uiAutocompleteInput = $("<textarea class='autocomplete_input' rows='1'></textarea>")
+                        .appendTo(self.uiAutocomplete)
+                        .data("autocompletewithunknown", self);
+
+                if ( self.options.grow ) {
+                    self.uiAutocomplete.after("<div class='autocomplete_count_container'><span id='"+self.options.id+"_count_label'>characters per tag:</span> <span id='"+self.options.id+"_count' class='autocomplete_count'>50</span></div>");
+                    $(self.uiAutocompleteInput).vertigro(self.options.maxlength, self.options.id + "_count");
+                }
+
+                self.element.hide();
+
+                self.tagslist = {};
+                self.cache = {};
+                self.cachemap = {};
+
+                if ( self.options.populateSource )
+                    self.populate(self.options.populateSource, self.options.populateId);
+
+                self.uiAutocompleteInput.autocomplete({
+                    source: function (request, response) {
+                        if ( self.cache[self.currentCache] != null )
+                            return response( self._filterTags( self.cache[self.currentCache], request.term ) );
+                    },
+
+                    select: _handleComplete
+                }).bind("keydown.autocompleteselect", function( event ) {
+                    var keyCode = $.ui.keyCode;
+                    var $input = $(this);
+
+                    $("#"+self.options.id+"_count_label").text("characters left:");
+
+                    switch( event.which ) {
+                        case keyCode.ENTER:
+                            _handleComplete.apply( $input,
+                                    [event, { item: { value: $input.val() } } ]);
+                            self.justCompleted = true;
+                            event.preventDefault();
+
+                            var $menu = $input.data("autocomplete").menu;
+                            $menu.deactivate();
+
+                            return;
+                        case keyCode.TAB:
+                            var $menu = $input.data("autocomplete").menu;
+                            if ( $menu.element.is(":visible")) {
+                                if ( !$menu.active ) {
+                                    $menu.next(event);
+                                    $menu.select();
+                                    self.justCompleted = true;
+                                }
+                                event.preventDefault();
+                            } else if ($input.val()) {
+                                _handleComplete.apply( $input,
+                                    [event, { item: { value: $input.val() } } ]);
+                                self.justCompleted = true;
+                                event.preventDefault();
+                            }
+                            return;
+                        case keyCode.BACKSPACE:
+                            if( ! $input.val() ) {
+                                $("."+self.options.tokenRemoveClass + ":last", self.uiAutocomplete).focus();
+                                event.preventDefault();
+                            }
+                            return;
+                    }
+                }).bind("keyup.autocompleteselect", function( event ) {
+                    var $input = $(this);
+                    if ( $input.val().indexOf(",") > -1 )
+                        _handleComplete.apply( $input, [event, { item: { value: $input.val() } } ]);
+                }).change(function(event){
+                    // if we have the menu open, let that handle the autocomplete
+                    var $menu = $(this).data("autocomplete").menu;
+                    if ( $menu.element.is(":visible") ) return;
+
+                    _handleComplete.apply( $(this), [event, { item: { value: $(event.currentTarget).val() } } ]);
+
+                    // workaround for autocompleting with TAB in opera
+                    if ( $.browser.opera && self.justCompleted ) {
+                        $(this).focus();
+                        self.justCompleted = false;
+                    }
+                })
+                .data("autocomplete")._renderItem = function( ul, item ) {
+                        return $( "<li></li>" )
+                            .data( "item.autocomplete", item )
+                            .append( "<a>" + item.label + "</a>" )
+                            .appendTo( ul );
+                };
+
+
+                // so other things can reinitialize the widget with their own text
+                $(self.element).bind("autocomplete_inittext", function( event, new_text ) {
+                    self.tagslist = {};
+                    self.uiAutocompletelist.empty();
+                    _handleComplete.apply( self.uiAutocompleteInput, [ event, { item: { value: new_text } } ] );
+                });
+                $(self.element).trigger("autocomplete_inittext", self.element.val());
+
+                // replace one text
+                $(self.element).bind("autocomplete_edittext", function ( event, $element, new_text ) {
+                    _handleComplete.apply( self.uiAutocompleteInput, [ event, { item: { value: new_text } }, $element ] );
+                });
+
+                $("span."+self.options.tokenTextClass, self.uiAutocomplete.get(0))
+                .live("click", function(event) {
+                    delete self.tagslist[$(this).text()];
+                    var $input = $("<input type='text' />")
+                        .addClass(self.options.tokenTextClass)
+                        .val($(this).text())
+                        .width($(this).width()+5);
+                    $(this).replaceWith($input);
+                    $input.focus();
+                });
+
+                $("input."+self.options.tokenTextClass,self.uiAutocomplete.get(0))
+                .live("blur", function(event) {
+                    $(self.element).trigger("autocomplete_edittext", [ $(this).closest("li"), $(this).val() ] );
+                });
+
+                $("."+self.options.tokenRemoveClass, self.uiAutocomplete.get(0)).live("click", function(event) {
+                    var $token = $(this).closest("."+self.options.tokenClass);
+
+                    delete self.tagslist[$token.children("."+self.options.tokenTextClass).text()];
+                    $token.fadeOut(function() {$(this).remove()});
+
+                    event.preventDefault();
+                    event.stopPropagation();
+                }).live("focus", function(event) {
+                    $(this).parent().addClass("focus");
+                }).live("blur", function(event) {
+                    $(this).parent().removeClass("focus");
+                }).live("keydown", function(event) {
+                    if ( event.which == $.ui.keyCode.BACKSPACE ) {
+                        event.preventDefault();
+                        var $prevToken = $(this).closest("."+self.options.tokenClass).prev("."+self.options.tokenClass);
+                        $(this).click();
+
+                        if ($prevToken.length == 1)
+                            $prevToken.find("."+self.options.tokenRemoveClass).focus();
+                        else
+                            self.uiAutocompleteInput.focus();
+                    } else if (event.which != $.ui.keyCode.TAB) {
+                        $(this).siblings("."+self.options.tokenTextClass).click();
+                    }
+                });
+
+                $("."+self.options.tokenTextClass, self.uiAutocomplete.get(0)).live("hover", function(event) {
+                     if (event.type == 'mouseover') {
+                        $(this).addClass("hover");
+                      } else {
+                        $(this).removeClass("hover");
+                      }
+                });
+
+                // workaround for autocompleting with ENTER in opera
+                self.justCompleted = false;
+                $.browser.opera && $(self.element.get(0).form).bind("submit.autocomplete", function(e) {
+                    // this tries to make sure that we don't try to validate crossposting, if we only hit enter
+                    // to autocomplete. Workaround for opera.
+                    // Sort of like a lock, to mark which handler last prevented the form submission.
+                    // TODO: refactor this out into something that we're sure works. We are at the mercy
+                    // of the way that Opera and other browsers order the handlers.
+                    if ( self.element.data("preventedby") == self.options.id)
+                        self.element.data("preventedby", null)
+
+                    if( self.justCompleted ) {
+                        if ( ! self.element.data("preventedby") )
+                            self.element.data("preventedby", self.options.id);
+                        self.justCompleted = false;
+                        return false;
+                    }
+                });
+            },
+
+            // store this in an array, and in a hash, so that we can easily look up presence of tags
+            _cacheData: function(array, key) {
+                this.currentCache=key;
+
+                this.cache[this.currentCache] = array;
+                this.cachemap[this.currentCache] = {};
+                for( var i in array ) {
+                    this.cachemap[this.currentCache][array[i]] = true;
+                }
+            },
+
+            tagstatus: function(key) {
+                var self = this;
+
+                if ( !self.cachemap[key] )
+                    return;
+
+                // recheck tags status in case tags list loaded slowly
+                // or we switched comms
+                $("."+self.options.tokenClass, self.uiAutocomplete).each(function() {
+                    var exists = self.cachemap[key][$(this).find("."+self.options.tokenTextClass).text()];
+                    $(this).toggleClass(self.options.newTokenClass, !exists).toggleClass(self.options.curTokenClass, exists);
+                });
+            },
+
+            populate: function(url, id) {
+                var self = this;
+
+                $.getJSON(url, function(data) {
+                    if ( !data ) return;
+
+                    self._cacheData(data.tags, id);
+                    self.tagstatus(id);
+                });
+            }
+    });
+
+})(jQuery);
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/js/jquery.crosspost.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.crosspost.js	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,273 @@
+(function($){
+
+var skipChecks = 0;
+var $accounts;
+
+$.widget("dw.crosspostaccount", {
+options: {
+    mainCheckbox: undefined,
+    locked: false,          // account is currently processing authentication
+    failed: false,          // account has failed authentication
+    strings: {
+        passwordRequired: "Password required",
+        authenticating: "Authenticating...",
+        cancel: "Cancel"
+    }
+},
+
+_create: function() {
+    var self = this;
+    var $checkbox = self.element;
+
+    self.$container = $checkbox.siblings(".crosspost_password_container");
+    if ( self.$container.length > 0 )
+        self.needsPassword = true;
+    else
+        self.needsPassword = false;
+
+    $checkbox.change(function() {
+        var $this = $(this);
+        self.$container
+            .fadeToggle($this.is(":checked"));
+    })
+
+    self.$container.toggle($checkbox.is(":checked"));
+},
+
+_findPasswordElements: function() {
+    if ( ! this.needsPassword || this.foundPasswordElements ) return;
+
+    this.$chal = this.$container.find("input.crosspost_chal");
+    this.$resp = this.$container.find("input.crosspost_resp");
+    this.$status = this.$container.find(".crosspost_password_status");
+    this.$password = this.$container.find(".crosspost_password");
+
+    this.foundPasswordElements = true;
+},
+
+_error: function( errMsg ) {
+    this.$container.addClass("error");
+    return this._message(errMsg).wrapInner("<span class='error-msg'></span>");
+},
+_message: function ( msg ) {
+    if ( msg == null || msg == "" )
+        return this._clearMessage();
+    return this.$status.html(msg).fadeIn();
+},
+_clearMessage: function() {
+    this.$container.removeClass("error");
+    return this.$status.fadeOut(null, function(){$(this).empty()});
+},
+
+needsChalResp: function() {
+    return this.needsPassword && this.element.is(":checked");
+},
+
+doChallengeResponse: function() {
+    var self = this;
+    var $main = $(self.options.mainCheckbox);
+
+    if ( ! self.needsPassword ) return;
+    self._findPasswordElements();
+
+    if ( ! self.options.locked && self.$chal.length > 0
+            && self.element.add($main).is(":checked") )
+    {
+        self.options.locked = true;
+        if ( self.$password.val() == null || self.$password.val() == "" ) {
+            self._error(self.options.strings.passwordRequired);
+            self.options.failed = true;
+            self.options.locked = false;
+
+            self._trigger("chalrespcomplete");
+            return;
+        }
+
+        self._message(self.options.strings.authenticating + "<button type='button' class='cancelCrosspostAuth ui-state-default'>"+self.options.strings.cancel+"</button>")
+            .find(".cancelCrosspostAuth").click(function() {
+                self._clearMessage();
+                self.cancel();
+            });
+
+
+        $.getJSON("/__rpc_extacct_auth", {"acctid": self.element.val()}, function(data) {
+            if ( self.options.locked ) {
+                if ( data.error ) {
+                    self._error(data.error);
+                    self.options.failed = true;
+                    self.options.locked = false;
+
+                    self._trigger("chalrespcomplete");
+                    return;
+                }
+
+                self._clearMessage();
+                if ( !data.success ) { self._trigger("chalrespcomplete"); return; }
+
+                var pass = self.$password.val();
+                var res = MD5(data.challenge + MD5(pass));
+                self.$resp.val(res);
+                self.$chal.val(data.challenge);
+
+                self.options.failed = false;
+                self.options.locked = false;
+                self._trigger("chalrespcomplete");
+            }
+        });
+    }
+},
+
+cancel: function() {
+    this.options.failed = false;
+    this.options.locked = false;
+
+    this._trigger( "cancel" );
+},
+
+submit: function() {
+    if (this.needsPassword)
+        this.$password.val("");
+}
+
+});
+
+$.widget("dw.crosspost", {
+options: {
+    strings: {
+        crosspostDisabled: {
+            community: "Community entries cannot be crossposted.",
+            draft: "Draft entries cannot be crossposted."
+        }
+    }
+},
+
+_create: function() {
+    function crosspostAccountUpdated() {
+        var $crosspost_entry = $("#crosspost_entry");
+        var allUnchecked = ! $accounts.is(":checked");
+        if ( allUnchecked ) {
+            $crosspost_entry.removeAttr("checked").attr("disabled","disabled");
+        } else {
+            $crosspost_entry.removeAttr("disabled").attr("checked","checked");
+        }
+    };
+
+    $accounts = $("#crosspost_accounts input[name='crosspost']")
+        .crosspostaccount({ mainCheckbox: "#crosspost_entry" })
+        .change(crosspostAccountUpdated)
+        .bind("crosspostaccountcancel", function() {
+            $(this).closest("form").find("input[type='submit']")
+                .removeAttr("disabled").removeClass("ui-state-disabled");
+        })
+        .bind("crosspostaccountchalrespcomplete", function () {
+            // use an array intead of $.each so that we can return out of the function
+            for ( var i = 0; i < $accounts.length;  i++ ) {
+                if ( $accounts.eq(i).crosspostaccount( "option", "locked" ) ) return false;
+            }
+
+            var acctErr = false;
+            $accounts.each(function(){
+                if ($(this).crosspostaccount("option", "failed") ) {
+                    acctErr = true;
+                    return false; // this just breaks us out of the each
+                }
+            });
+
+            var $form = $(this).closest("form");
+            if ( acctErr ) {
+                $accounts.crosspostaccount( "cancel" );
+            } else {
+                $accounts.crosspostaccount( "submit" );
+                $form.unbind("submit", this._checkSubmit).submit();
+            }
+
+            $form.find("input[type='submit']")
+                .removeAttr("disabled").removeClass("ui-state-disabled");
+        })
+
+    crosspostAccountUpdated();
+
+    $("#crosspost_entry").change(function() {
+        var do_crosspost_entry = $(this).is(":checked");
+        var $inputs = $("#crosspost_accounts").find("input");
+
+        if ( do_crosspost_entry ) {
+            $inputs.removeAttr("disabled")
+
+            var $checkboxes = $inputs.filter("[name='crosspost']");
+            if ( ! $checkboxes.is(":checked") )
+                $checkboxes.attr("checked", "checked")
+        } else {
+            $inputs.attr("disabled", "disabled")
+        }
+    });
+
+    $(this.element).closest("form").submit(this._checkSubmit);
+},
+
+// When the form is submitted, compute the challenge response and clear out the plaintext password field
+_checkSubmit: function (e) {
+    var $target = $(e.target);
+    if ( ! skipChecks && ! $target.data("preventedby") && ! $target.data("skipchecks") ) {
+        $(this).find("input[type='submit']").attr("disabled","disabled").addClass("ui-state-disabled");
+
+        var needChalResp = false;
+        $accounts.each(function() {
+            var ret = $(this).crosspostaccount( "needsChalResp" );
+            needChalResp = needChalResp || ret;
+        });
+
+        if ( needChalResp ) {
+            e.preventDefault();
+            e.stopPropagation();
+            $accounts.crosspostaccount("doChallengeResponse");
+        }
+    }
+},
+
+// to display or not to display the crosspost accounts
+toggle: function(why, allowThisCrosspost, animate) {
+    var self = this;
+    var $crosspost_entry = $("#crosspost_entry");
+
+    var msg_class = "crosspost_msg";
+    var msg_id = msg_class + "_" + why;
+    var $msg = $("#"+msg_id);
+
+    if( allowThisCrosspost ) {
+        $msg.remove();
+    } else if ( $msg.length == 0 ) {
+        var $p = $("<p></p>", { class: msg_class, id: msg_id }).text(self.options.strings.crosspostDisabled[why]);
+        $p.insertBefore("#crosspost_accounts");
+    }
+
+    var allowCrosspost = (allowThisCrosspost && $(msg_class).length == 0 );
+
+    // preserve existing disabled state if crosspost allowed
+    var allUnchecked = ! $accounts.is(":checked")
+    if ( ! allowCrosspost || allUnchecked )
+        $crosspost_entry.attr("disabled", "disabled")
+    else
+        $crosspost_entry.removeAttr("disabled")
+
+    var enableAccountCheckboxes = allowCrosspost && ( allUnchecked || $crosspost_entry.is(":checked") );
+
+    if (enableAccountCheckboxes)
+        $accounts.removeAttr("disabled");
+    else
+        $accounts.attr("disabled", "disabled");
+
+    if ( allowCrosspost ) {
+        skipChecks = false;
+        $("#crosspost_accounts, #crosspost_component h4").slideDown()
+            .siblings("p").hide();
+    } else {
+        skipChecks = true;
+        $("#crosspost_accounts, #crosspost_component h4").hide()
+            .siblings("p").slideDown();
+    }
+}
+
+});
+
+})(jQuery);
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/js/jquery.iconrandom.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.iconrandom.js	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,21 @@
+(function($) {
+    $.fn.iconrandom = function( opts ) {
+        var $selector = this;
+
+        if ( opts.trigger ) {
+            $(opts.trigger).click(
+                function() {
+                    var numicons = $selector.attr("length");
+
+                    // we need to ignore the first option "(default)"
+                    var randomnumber = Math.floor(Math.random() * (numicons - 1));
+                    $selector.attr("selectedIndex", randomnumber + 1);
+
+                    if ( opts.handler && $selector.get(0) )
+                        opts.handler.apply($selector.get(0));
+                    return false;
+                });
+        }
+    };
+
+})(jQuery);
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/js/jquery.iconselector.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.iconselector.js	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,276 @@
+(function($) {
+    var kwtoicon = {};
+    var opts;
+
+    $.fn.iconselector = function( options ) {
+        opts = $.extend({}, $.fn.iconselector.defaults, options );
+
+        $.fn.iconselector.owner = $(this);
+
+        if( opts.selectorButtons ) {
+            $(opts.selectorButtons).not("a").wrap("<a href='#'></a>").end()
+                .click(function(e) {
+                    _open.apply( $.fn.iconselector.owner, [ opts ] );
+                    e.preventDefault();
+                });
+        }
+
+        return $(this).wrap("<div class='iconselect_trigger_wrapper'></div>");
+    };
+
+    // selected icon
+    $.fn.iconselector.selected = null;
+    // selected keyword
+    $.fn.iconselector.selectedKeyword = null;
+
+    $.fn.iconselector.defaults = {
+        title: 'Choose Icon',
+        width: "70%",
+        height: $(window).height() * 0.8,
+        selectedClass: "iconselector_selected",
+        onSelect: function() {},
+        selectorButtons: null
+    };
+
+    function _dialogHTML() {
+            return "<div>\
+      <div class='iconselector_top'>\
+        <span class='iconselector_searchbox'>\
+          Search: <input type='text' id='iconselector_search'>\
+          </span>\
+        <span class='image-text-toggle' id='iconselector_image_text_toggle'>\
+          <span class='toggle-image-only'><a href='#' class='image_only'>Images only</a> / Show meta text</span>\
+          <span class='toggle-no-meta'>Images only / <a href='#' class='show_text'>Show meta text</a></span>\
+        </span>\
+        <div class='kwmenu'>\
+          <label for='iconselector_kwmenu'>Keywords of selected icon:</label>\
+          <div class='keywords'></div>\
+          <input id='iconselector_select' disabled='disabled' type='button' value='Select'>\
+        </div>\
+      </div>\
+      <div id='iconselector_icons'><span class='iconselector_status'>Loading...</span></div>\
+    </div>";
+    };
+
+    function _selectContainer($container, keyword, replaceKwMenu) {
+        $("#"+$.fn.iconselector.selected).removeClass(opts.selectedClass);
+        if ( $container.length == 0 ) return;
+
+        $.fn.iconselector.selected = $container.attr("id");
+        $container.addClass(opts.selectedClass);
+        $container.show();
+
+        if ( keyword != null ) {
+            // select by keyword
+            $.fn.iconselector.selectedKeyword = keyword;
+        } else {
+            // select by picid (first keyword)
+            $.fn.iconselector.selectedKeyword = $container.data("defaultkw");
+        }
+
+        if ( replaceKwMenu ) {
+            var $keywords = $container.find(".keywords");
+            $(".iconselector_top .keywords", $.fn.iconselector.instance)
+                .replaceWith($keywords.clone());
+            if ($keywords.length > 0)
+                $("#iconselector_select").removeAttr("disabled");
+            else
+                $("#iconselector_select").attr("disabled", "disabled");
+        } else {
+            $(".iconselector_top .selected", $.fn.iconselector.instance)
+                .removeClass("selected");
+        }
+
+        // can't rely on a cached value, because it may have been replaced
+        $(".iconselector_top .keywords", $.fn.iconselector.instance)
+            .find("a.keyword")
+            .filter(function() {
+                return $(this).text() == $.fn.iconselector.selectedKeyword;
+            })
+            .addClass("selected");
+    }
+
+    function _selectByKeyword(keyword) {
+        var iconcontainer_id = kwtoicon[keyword];
+        if ( iconcontainer_id )
+            _selectContainer($("#"+iconcontainer_id), keyword, true);
+    }
+
+    function _selectByKeywordClick(event) {
+        var $keyword = $(event.target).closest("a.keyword");
+        if ( $keyword.length > 0 ) {
+            var keyword = $keyword.text();
+            var iconcontainer_id = kwtoicon[keyword];
+            if ( iconcontainer_id )
+                _selectContainer($("#"+iconcontainer_id), keyword, false);
+        }
+
+        event.stopPropagation();
+        event.preventDefault();
+    }
+
+    function _selectByClick(event) {
+        var $icon = $(event.target).closest("li");
+        var $keyword = $(event.target).closest("a.keyword");
+
+        _selectContainer($icon, $keyword.length > 0 ? $keyword.text() : null, true);
+
+        event.stopPropagation();
+        event.preventDefault();
+    };
+
+    function _selectByEnter(event) {
+        if (event.keyCode && event.keyCode === $.ui.keyCode.ENTER) {
+            var $originalTarget = $(event.originalTarget);
+            if ($originalTarget.hasClass("keyword")) {
+                $originalTarget.click();
+            } else if ($originalTarget.is("a")) {
+                return;
+            }
+            _selectCurrent();
+        }
+    }
+
+    function _selectCurrent() {
+        if ($.fn.iconselector.selectedKeyword) {
+            $.fn.iconselector.owner.val($.fn.iconselector.selectedKeyword);
+            opts.onSelect.apply($.fn.iconselector.owner[0]);
+            $.fn.iconselector.instance.dialog("close");
+        }
+    }
+
+    function _filterPics(event) {
+        var val = $("#iconselector_search").val().toLocaleUpperCase();
+        $("#iconselector_icons_list li").hide().each(function(i, item) {
+            if ( $(this).data("keywords").indexOf(val) != -1 || $(this).data("comment").indexOf(val) != -1
+                || $(this).data("alt").indexOf(val) != -1 ) {
+
+                $(this).show();
+             }
+        });
+
+        var $visible = $("#iconselector_icons_list li:visible");
+        if ( $visible.length == 1 )
+            _selectContainer($visible, null, true);
+    };
+
+    function _open () {
+        if ( ! $.fn.iconselector.instance ) {
+            $.fn.iconselector.instance = $(_dialogHTML());
+
+            $.fn.iconselector.instance.dialog( { title: opts.title, width: opts.width, height: opts.height, dialogClass: "iconselector", modal: true,
+                close: function() { $("#iconselect").focus(); },
+                resize: function() {
+                    $("#iconselector_icons").height(
+                        $.fn.iconselector.instance.height() -
+                        $.fn.iconselector.instance.find('.iconselector_top').height()
+                        - 5
+                    );
+                }
+             } )
+            .keydown(_selectByEnter);
+
+            $("#iconselector_image_text_toggle a").click(function() {
+                if ( $(this).hasClass("image_only") ) {
+                    $("#iconselector_icons, #iconselector_image_text_toggle").addClass("no_meta");
+                } else {
+                    $("#iconselector_icons, #iconselector_image_text_toggle").removeClass("no_meta");
+                }
+
+                // refocus, because we just hid the link we just clicked on
+                $("#iconselector_image_text_toggle a").focus();
+
+                return false;
+            });
+
+            $("#iconselector_icons").height(
+                $.fn.iconselector.instance.height() -
+                $.fn.iconselector.instance.find('.iconselector_top').height()
+                - 5
+            );
+
+            $("button", $.fn.iconselector.instance.siblings()).attr("disabled", "true");
+            $(":input", $.fn.iconselector.instance).attr("disabled", "true");
+            $("#iconselector_search", $.fn.iconselector.instance).bind("keyup", _filterPics);
+
+            $.getJSON(Site.siteroot + "/tools/endpoints/getuserpics",
+                function(data) {
+                    if ( !data ) {
+                        $("#iconselector_icons").html("<h2>Error</h2><p>Unable to load icons data</p>");
+                        return;
+                    }
+
+                    if ( data.alert ) {
+                        $("#iconselector_icons").html("<h2>Error</h2><p>"+data.alert+"</p>");
+                        return;
+                    }
+
+                    var $iconslist = $("<ul id='iconselector_icons_list'></ul>");
+
+                    $.each(data.pics, function(id, icon) {
+                        var idstring = "iconselector_item_"+id;
+
+                        var $img = $("<img />").attr( { src: icon.url, alt: icon.alt, height: icon.height, width: icon.width } ).wrap("<div class='icon_image'></div>").parent();
+                        var $keywords = "";
+                        if ( icon.keywords ) {
+                            $keywords = $("<div class='keywords'></div>");
+                            var last = icon.keywords.length - 1;
+
+                            $.each(icon.keywords, function(i, kw) {
+                                kwtoicon[kw] = idstring;
+                                $keywords.append( $("<a href='#' class='keyword'></a>").text(kw) );
+                                if ( i < last )
+                                    $keywords.append(document.createTextNode(", "));
+                            });
+                        }
+
+                        var $comment = ( icon.comment != "" ) ? $("<div class='comment'></div>").text( icon.comment ) : "";
+
+                        var $meta = $("<div class='meta_wrapper'></div>").append($keywords).append($comment);
+                        var $item = $("<div class='iconselector_item'></div>").append($img).append($meta);
+                        $("<li></li>").append($item).appendTo($iconslist)
+                            .data( "keywords", icon.keywords.join(" ").toLocaleUpperCase() )
+                            .data( "comment", icon.comment.toLocaleUpperCase() )
+                            .data( "alt", icon.alt.toLocaleUpperCase() )
+                            .data( "defaultkw", icon.keywords[0] )
+                            .attr( "id", idstring );
+                    });
+
+                    $("#iconselector_icons").empty().append($iconslist);
+
+                    $("button", $.fn.iconselector.instance.siblings()).removeAttr("disabled");
+                    $(":input:not([id='iconselector_select'])", $.fn.iconselector.instance).removeAttr("disabled");
+                    $("#iconselector_icons_list")
+                        .click(_selectByClick)
+                        .dblclick(function(e) {
+                            _selectByClick(e);
+                            _selectCurrent();
+                        });
+
+                    $(".iconselector_top .kwmenu", $.fn.iconselector.instance)
+                        .click(_selectByKeywordClick)
+                        .dblclick(function(e) {
+                            _selectByKeywordClick(e);
+                            _selectCurrent();
+                        });
+
+
+                    $("#iconselector_search").focus();
+
+                    $("#iconselector_select").click(_selectCurrent);
+                    $(document).bind("keydown.dialog-overlay", _selectByEnter);
+
+                    // initialize
+                    _selectByKeyword($.fn.iconselector.owner.val());
+                 });
+        } else {
+            // reinitialize
+            _selectByKeyword($.fn.iconselector.owner.val());
+            $.fn.iconselector.instance.dialog("open");
+            $("#iconselector_search").focus();
+
+            $(document).bind("keydown.dialog-overlay", _selectByEnter);
+        }
+    };
+
+})(jQuery);
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/js/jquery.postform.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.postform.js	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,420 @@
+(function($) {
+$.postForm = {
+
+init: function(formData) {
+    if ( ! formData ) formData = {};
+
+    // icon
+    function initIcons() {
+        var $preview = $("#icon_preview");
+        if ( $preview.has(".noicon").length > 0 ) return;
+
+        var $select = $("#iconselect");
+        if ( $select.length == 0 ) return;
+
+        $preview.prepend("<ul class='icon-functions'><li><button id='icon_browser_link' class='ui-button ui-state-default'>browse</button></li><li><button id='icon_random_link' class='ui-button ui-state-default'>random</a></li></ul>");
+
+        function update_icon_preview() {
+            var icons = formData.icons;
+            if ( !icons ) return;
+
+            if ( this.selectedIndex != null && icons[this.selectedIndex] ) {
+                if ( icons[this.selectedIndex].src ) {
+                    if ( $("#icon_preview_image").length == 0 ) {
+                        $("#icon_preview .icon").append("<img id='icon_preview_image' />");
+                    }
+
+                    $("#icon_preview .icon img").attr({
+                        "src": icons[this.selectedIndex].src,
+                        "alt": icons[this.selectedIndex].alt
+                    });
+                } else {
+                    $("#icon_preview_image").remove();
+                }
+            }
+        }
+        if ( $.fn.iconselector ) {
+            $select.iconselector( { onSelect: update_icon_preview, selectorButtons: "#icon_preview .icon, #icon_browser_link" } );
+        } else {
+            $("#icon_browser_link").remove();
+        }
+        $select.iconrandom( { handler: update_icon_preview, trigger: "#icon_random_link" } );
+        $select.change(update_icon_preview);
+    }
+
+    // date
+    function initDisplayDate() {
+        // initialize date to current system time
+        function updateDateOnInputFields(date) {
+            if (!date) date = new Date();
+            $("#entrytime").val( $.datepicker.formatDate("yy-mm-dd", date) );
+            $("#entrytime_hr").val( date.getHours() );
+            $("#entrytime_min").val( date.getMinutes() );
+
+            $("#trust_datetime").val(1);
+        }
+
+        if ( $("#trust_datetime").val() != 1 ) updateDateOnInputFields();
+        $("#entrytime_auto_update").click(function() {
+            var fields = "#entrytime, #entrytime_hr, #entrytime_min, #entrytime_display_container button, #entrytime_container button";
+            var containers = "#entrytime_display_container, #entrytime_container";
+            if ( $(this).is(":checked") ) {
+                $(fields).attr("disabled", true);
+                $(containers).addClass("ui-state-disabled");
+            } else {
+                $(fields).attr("disabled", false);
+                $(containers).removeClass("ui-state-disabled");
+            }
+        });
+
+        $("#post_entry").submit(function() {
+            if ( $("#entrytime_auto_update").is(":checked") )
+                updateDateOnInputFields();
+        });
+
+
+        $.datepicker.setDefaults({
+            autoSize: true,
+            dateFormat: $.datepicker.ISO_8601,
+            showButtonPanel: true,
+
+            showOn: 'button',
+            buttonText: 'Pick date',
+
+            onClose: function(dateText) {
+                var $this = $(this);
+                // if we have previously tweaked this value, don't override
+                if ($this.data("customized")) return;
+
+                var now = new Date();
+                var hours = now.getHours();
+                if ( hours < 10 ) hours = "0" + hours;
+                $this.siblings(".time_hr").val(hours);
+
+                var min = now.getMinutes();
+                if ( min < 10 ) min = "0" + min;
+                $this.siblings(".time_min").val(min);
+
+                $this.focus();
+            }
+        });
+
+        var $editbutton = $("<button type='button' id='entrytime_container_edit' class='ui-state-default ui-corner-all ui-icon ui-icon-pencil'>edit</button>").click(function(e) {
+            $(this).hide();
+
+            $("#entrytime_display_container").hide();
+            $("#entrytime_container").show();
+
+            e.preventDefault();
+            e.stopPropagation();
+        });
+
+        var $datedisplay_edit = $("<span id='entrytime_display_edit'></span>").append($editbutton);
+        var $datedisplay_date = $("<span id='entrytime_display_date'></span>")
+            .text($("#entrytime").val());
+        var $datedisplay_time = $("<span id='entrytime_display_time'></span>")
+            .text(" " + $("#entrytime_hr").val()+":"+$("#entrytime_min").val());
+
+        $("<div id='entrytime_display_container'></div>")
+            .append($datedisplay_edit, $datedisplay_date, $datedisplay_time)
+            .insertBefore("#entrytime_container");
+        $("#entrytime_container").hide();
+
+        $("#entrytime").datepicker()
+                // take the button and put it before the textbox
+                .next()
+                    .insertBefore("#entrytime")
+                    .addClass("ui-state-default ui-corner-all ui-icon ui-icon-calendar");
+
+        // constrain format/value of date & time
+        $("#entrytime").change(function() {
+            // detect if there's an error in what we typed
+            // This uses internal datepicker functions.
+            var inst = $.datepicker._getInst(this);
+            try {
+                $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'),
+                   $(this).val() || "", $.datepicker._getFormatConfig(inst));
+            } catch(event) {
+                // nothing right now, but eventually want an error message
+            }
+        });
+
+        $(".time_container")
+            .addClass("time_container_with_picker")
+            .find(".time_hr")
+                .one( "change", function() {
+                    $(this).siblings(".hasDatepicker").data("customized", true);
+                })
+                .change(function() {
+                    var val = parseInt($(this).val());
+                    if ( isNaN(val) || val != $(this).val() || val <= 0 ) $(this).val("0");
+                    else if ( val > 23 ) $(this).val(23);
+                })
+                .end()
+            .find(".time_min")
+                .one( "change", function() {
+                    $(this).siblings(".hasDatepicker").data("customized", true);
+                })
+                .change(function() {
+                    var val = parseInt($(this).val());
+                    if ( isNaN(val) || val != $(this).val() || val <= 0 ) $(this).val("00");
+                    else if ( val > 59 ) $(this).val(59);
+                });
+    }
+
+    // currents
+    function initCurrents() {
+        var $moodSelect = $("#current_mood");
+        var $customMood = $("#current_mood_other");
+
+        function _mood() {
+            var selectedMood = $moodSelect.val();
+            return moodpics[selectedMood] ? moodpics[selectedMood] : ["", ""];
+        }
+
+        var updatePreview = function() {
+            var moodpics = formData.moodpics;
+            if ( ! moodpics ) return;
+
+            $("#moodpreview_image").fadeOut("fast", function() {
+                var $this = $(this);
+                $this.empty();
+
+                var mood = _mood();
+                if ( mood[1] !== "" ) {
+                    $this.append($("<img />",{ src: mood[1], width: mood[2], height: mood[3]}))
+                         .fadeIn();
+                }
+            });
+        }
+
+        var updatePreviewText = function () {
+            var customMoodText = $customMood.val();
+            if ( ! customMoodText ) {
+                var mood = _mood();
+                customMoodText = mood[0];
+            }
+            $("#moodpreview .moodpreview_text").text( customMoodText );
+
+        }
+
+        // initialize...
+        var moodpics = formData.moodpics;
+        if( moodpics ) {
+            $moodSelect
+                .change(updatePreview)
+                .change(updatePreviewText)
+                .closest("p").append("<div id='moodpreview'>"
+                        + "<div id='moodpreview_image'></div>"
+                        + "<div class='moodpreview_text'></div>"
+                        + "</div>");
+
+            $customMood.change(updatePreviewText);
+
+            updatePreview();
+            updatePreviewText();
+        }
+    }
+
+    // tags
+    function initTags() {
+        $("#post_entry").one("journalselect", function(e, journal) {
+            var options = {
+                grow: true,
+                maxlength: 50
+            }
+
+            if ( journal.name ) {
+                options.populateSource = Site.siteroot + "/tools/endpoints/gettags?user=" + journal.name;
+                options.populateId = journal.name;
+            }
+
+            var $taglist = $("#taglist");
+            $taglist.autocompletewithunknown(options);
+
+            if ( journal.name )
+                $taglist.tagselector({fallbackLink: "#taglist_link"});
+
+            $("#post_entry").bind("journalselect", function(e, journal) {
+                if ( ! journal.name ) return;
+                $taglist.autocompletewithunknown( "populate",
+                    Site.siteroot + "/tools/endpoints/gettags?user=" + journal.name, journal.name );
+            })
+        });
+    }
+
+    // journal listeners
+    function initJournalSelect() {
+        $("#usejournal").change(function() {
+            var $this = $(this);
+            var journal, iscomm;
+            if ( $this.is("select") ) {
+                var $option = $("option:selected", this);
+                journal = $option.text();
+                iscomm  = $option.val() !== "";
+            } else {
+                journal = $this.val();
+                iscomm = journal !== $.trim($("#post_as_remote").next("label").text());
+            }
+            $(this).trigger( "journalselect", {"name":journal, "iscomm":iscomm});
+        });
+
+        $("#post_to").radioreveal({ radio: "post_as_remote" });
+        $("#post_login").radioreveal({ radio: "post_as_other" });
+    }
+
+    // access
+    function initAccess() {
+        $("#custom_access_groups").hide();
+        $("#security").change( function() {
+            if ( $(this).val() == "custom" )
+                $("#custom_access_groups").slideDown();
+            else
+                $("#custom_access_groups").slideUp();
+        });
+
+        $("#post_entry").bind( "journalselect", function(e, journal) {
+            if ( journal.iscomm )
+                $("#custom_access_groups").slideUp();
+
+            var $security = $("#security");
+            if ( $security.length > 0 && journal.name ) {
+                $.getJSON( Site.siteroot + "/tools/endpoints/getsecurityoptions",
+                    { "user": journal.name },
+                    function(data) {
+                        if ( ! data ) return;
+
+                        $security.empty();
+                        if ( data.ret ) {
+                            var opts;
+                            if ( data.ret['is_comm'] ) {
+                                opts = [
+                                    "<option value='public'>Everyone (Public)</option>",
+                                    "<option value='access'>Members</option>"
+                                ];
+                                if ( data.ret['can_manage'] )
+                                    opts.push("<option value='private'>Admin</option>");
+                            } else {
+                                opts = [
+                                    "<option value='public'>Everyone (Public)</option>",
+                                    "<option value='access'>Access List</option>",
+                                    "<option value='private'>Private (Just You)</option>"
+                                ];
+                                if ( data.ret['friend_groups_exist'] )
+                                    opts.push("<option value='custom'>Custom</option>");
+                            }
+
+                            $security.append(opts.join("\n"))
+
+                            // select the minsecurity value and disable the values with lesser security
+                            $security.val(data.ret['minsecurity']);
+                            if ( data.ret['minsecurity'] == 'access' ) {
+                                $security.find("option[value='public']").attr("disabled", "disabled");
+                            } else if ( data.ret['minsecurity'] == 'private' ) {
+                                $security.find("option[value='public'],option[value='access'],option[value='custom']")
+                                    .attr("disabled", "disabled");
+                            }
+                        } else {
+                            // user is not known. no custom groups, no minsecurity
+                            $security.append([
+                                "<option value='public'>Everyone (Public)</option>",
+                                "<option value='access'>Access List</option>",
+                                "<option value='private'>Private (Just You)</option>"
+                            ].join("\n"))
+                        }
+                    }
+                );
+            }
+
+        });
+    }
+
+    function initCrosspost() {
+        $("#crosspost_component").crosspost();
+
+        $("#post_entry").bind("journalselect", function(e, journal) {
+            $("#crosspost_component").crosspost("toggle", "community", ! journal.iscomm, true);
+        });
+    }
+
+    function initPostButton() {
+        $("#post_entry").bind("journalselect", function(e, journal) {
+            var $submit = $("#submit_entry");
+            if ( journal.iscomm && journal.name )
+                $submit.val("Post Entry to " + journal.name);
+            else
+                $submit.val("Post Entry");
+        });
+
+        if ( $("#login_chal").length == 1 )
+            $("#post_entry").submit( function(e) { if ( ! $("#login_response").val() ) { sendForm(this.id) } } );
+    }
+
+    function initToolbar() {
+        $("#preview_button").click(function(e) {
+            var form = e.target.form;
+            var action = form.action;
+            var target = form.target;
+
+            var $password = $(form).find("input[type='password']:enabled");
+            $password.attr("disabled","disabled");
+
+            form.action = "/entry/preview";
+            form.target = 'preview';
+            window.open( '', 'preview', 'width=760,height=600,resizable=yes,status=yes,toolbar=no,location=no,menubar=no,scrollbars=yes');
+            form.submit();
+
+            form.action = action;
+            form.target = target;
+            $password.removeAttr("disabled");
+            e.preventDefault();
+        });
+
+        $("#spellcheck_button").click(function(e) {
+            $(this.form).data("skipchecks", "spellcheck");
+        });
+
+        $("#post_options").click(function(e){
+            e.preventDefault();
+
+            var $img = $(this).find("img");
+            var oldsrc = $img.attr("src");
+            $img.attr("src", $.throbber.src );
+            $("#settings-tools").load(Site.siteroot + "/__rpc_entryoptions", function(html,status,jqxhr) {
+                $img.attr("src", oldsrc);
+                $("#post_entry").trigger("deleted");
+            });
+        });
+
+        $.fx.off = formData.minAnimation;
+    }
+
+    // set up...
+    initIcons();
+    initDisplayDate();
+    initCurrents();
+    initTags();
+    initJournalSelect();
+    initAccess();
+    initPostButton();
+    initCrosspost();
+    initToolbar();
+
+    // trigger all handlers associated with a journal selection
+    if ( $("#usejournal").length == 1 ) {
+        $("#usejournal").triggerHandler("change");
+    } else {
+        // not logged in and no usejournal
+        $("#post_entry").trigger( "journalselect", { name: undefined, iscomm: false } );
+    }
+} }
+})(jQuery);
+
+jQuery(function($) {
+    $("#nojs").val(0);
+    $.postForm.init(window.postFormInitData);
+    $("body").delegate( "button", "hover", function() {
+		    $(this).toggleClass("ui-state-hover");
+        }
+    );
+});
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/js/jquery.postoptions.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.postoptions.js	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,104 @@
+jQuery(function($) {
+
+function saveOriginalValues(elements) {
+    elements.filter(":radio, :checkbox").each(function() {
+        var $this = $(this);
+        $this.data("originalValue",  $this.is(":selected") || $this.is(":checked"));
+    });
+}
+
+var $inputs = $("#post-options input");
+saveOriginalValues($inputs);
+
+var $cancel_button = $("<input>", { "type": "submit", "value" : "Cancel" }).click(function(e) {
+    e.preventDefault();
+    stopEditing();
+
+    // restore to original
+    $inputs.filter(":radio").each(function(i,val) {
+        var $this = $(this);
+        if ($this.data("originalValue") != $this.is(":selected") ) $this.click();
+    }).end()
+    .filter(":checkbox").each(function(i,val) {
+        var $this = $(this);
+        if ($this.data("originalValue") != $this.is(":checked") ) $this.click();
+    });
+
+}).wrap("<fieldset class='destructive submit'></fieldset>").parent();
+
+$inputs
+.filter(":submit").click(function(e) {
+    e.preventDefault();
+
+    var postPanels = [];
+    $(".column").each(function(columnNum) {
+        var ids = $(this).sortable("toArray");
+        for ( var i = 0; i < ids.length; i++ ) {
+            if ( ids[i] != "" )
+                postPanels.push( { "name" : "column_"+columnNum, "value": i+":"+ids[i] } );
+        }
+    })
+
+    var post = $(this.form).serialize();
+    var sep = ( post == "" ) ? "" : "&";
+    var jqxhr = $.post( Site.siteroot + "/__rpc_entryoptions", post + sep + $.param(postPanels) )
+        .success(function () {
+            stopEditing();
+            saveOriginalValues($inputs);
+        } )
+        .error(function(response) {
+            $("#settings-tools").html(response.responseText)
+        });
+    $(this).throbber( "before", jqxhr );
+})
+    .parent().after($cancel_button).end()
+.end()
+.filter(":checkbox[name='visible_panels']").click(function() {
+    // remove "panel_"
+    $("#"+this.id.substr(6)+"_component").toggleClass("inactive_component")
+}).end()
+.filter(":radio[name='entry_field_width']").click(function() {
+    var val = $(this).val();
+    $("#post_entry").toggleClass("entry-full-width", val == "F" )
+        .toggleClass("entry-partial-width", val == "P" );
+}).end();
+
+$("#minimal_animations").click(function() {
+    $.fx.off = $(this).is(":checked");
+});
+
+$(".panels-list").append("<p class='note'>Scroll down to arrange panels to your preference</p>")
+
+if ($(".sortable_column_text").length == 0) {
+    var $draginstructions = $("<div class='sortable_column_text'>drag and drop to rearrange</div>").disableSelection();
+
+    $(".column").sortable({
+      connectWith: ".column",
+      placeholder: "ui-state-highlight",
+      forcePlaceholderSize: true,
+      opacity: 0.8,
+      cancel: ".sortable_column_text"
+    })
+    .sortable( "disable" )
+    .append($draginstructions);
+}
+
+
+startEditing();
+
+function stopEditing() {
+    $(".column").sortable( "disable" ).enableSelection();
+    $(document.body).removeClass("screen-customize-mode");
+
+    $("#post_entry").addClass("minimal");
+    $("#post-options").slideUp();
+}
+
+function startEditing() {
+    $(".column").sortable( "enable" ).disableSelection();
+    $(document.body).addClass("screen-customize-mode");
+
+    $("#post_entry").removeClass("minimal");
+}
+
+});
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/js/jquery.radioreveal.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.radioreveal.js	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,31 @@
+(function($) {
+    $.fn.radioreveal = function( options ) {
+        var $caller = $(this);
+        var opts = $.extend({}, $.fn.radioreveal.defaults, options );
+
+        var $radio = $("#"+opts.radio).filter(":radio");
+
+        if ( $radio.length > 0 ) {
+            $radio.attr("checked") ? $caller.show() : $caller.hide();
+
+            var name = $radio.attr("name");
+            if ( name ) {
+                $("input:radio[name='"+name+"']").click(function() {
+                    if ( $("input:radio[name='"+name+"']:checked").attr("id") == opts.radio ) {
+                        $caller .show()
+                                .find(":input").first().focus();
+                    } else {
+                        $caller.hide();
+                    }
+                });
+            }
+        }
+
+        return $caller;
+    }
+
+    $.fn.radioreveal.defaults = {
+        radio: ""          // the id of the radio element that will reveal the caller
+    };
+})(jQuery);
+
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/js/jquery.tagselector.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.tagselector.js	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,203 @@
+(function($) {
+    var tagCache = {};
+    var opts;
+
+    $.fn.tagselector = function( options ) {
+        opts = $.extend({}, $.fn.tagselector.defaults, options );
+
+        $.fn.tagselector.owner = $(this);
+
+        if ( opts.fallbackLink ) {
+            $(opts.fallbackLink).remove();
+        }
+
+        $("<button class='tagselector_trigger ui-state-default'>browse</button>")
+            .click(function(e) {
+                    _open.apply( $.fn.tagselector.owner, [ opts ] );
+                    e.preventDefault();
+            })
+            .insertAfter($(this).closest("div"));
+
+        return $(this);
+    };
+
+    $.fn.tagselector.defaults = {
+        title: 'Choose Tags',
+        width: "70%",
+        height: $(window).height() * 0.8,
+        onSelect: function() {},
+        fallbackLink: undefined
+    };
+
+    function _tags() {
+        // FIXME: more generic, please
+        var tags_data = $("#taglist").data("autocompletewithunknown");
+        return tags_data ? tags_data.cache[tags_data.currentCache] : null;
+    }
+
+    function _selectedTags() {
+        var tags_data = $("#taglist").data("autocompletewithunknown");
+        if ( tags_data ) {
+            var selected = tags_data.tagslist;
+            var cachedTags = tags_data.cachemap[tags_data.currentCache];
+            var newTags = [];
+
+            $.each(selected, function(key, value) {
+                if (!cachedTags[key])
+                    newTags.push(key);
+            });
+
+            $("#tagselector_tags").data("new", newTags);
+            return selected;
+        }
+
+        return {};
+    }
+
+    function _updateSelectedTags(tags) {
+        $("#taglist").trigger("autocomplete_inittext", tags);
+    }
+
+    function _selectContainer($container, keyword, replaceKwMenu) {
+        $("#"+$.fn.tagselector.selected).removeClass(opts.selectedClass);
+        if ( $container.length == 0 ) return;
+
+        $.fn.iconselector.selected = $container.attr("id");
+        $container.addClass(opts.selectedClass);
+        $container.show();
+
+        if ( keyword != null ) {
+            // select by keyword
+            $.fn.iconselector.selectedKeyword = keyword;
+        } else {
+            // select by picid (first keyword)
+            $.fn.iconselector.selectedKeyword = $container.data("defaultkw");
+        }
+
+        if ( replaceKwMenu ) {
+            var $keywords = $container.find(".keywords");
+            $(".iconselector_top .keywords", $.fn.iconselector.instance)
+                .replaceWith($keywords.clone());
+            if ($keywords.length > 0)
+                $("#iconselector_select").removeAttr("disabled");
+            else
+                $("#iconselector_select").attr("disabled", "disabled");
+        } else {
+            $(".iconselector_top .selected", $.fn.iconselector.instance)
+                .removeClass("selected");
+        }
+
+        // can't rely on a cached value, because it may have been replaced
+        $(".iconselector_top .keywords", $.fn.iconselector.instance)
+            .find("a.keyword")
+            .filter(function() {
+                return $(this).text() == $.fn.iconselector.selectedKeyword;
+            })
+            .addClass("selected");
+    }
+
+    function _filter(event) {
+        var val = $("#tagselector_search").val().toLocaleLowerCase();
+        $("#tagselector_tags_list li").hide().each(function(i, item) {
+            if ( $(this).text().indexOf(val) != -1 )
+                $(this).show();
+        });
+
+        var $visible = $("#tagselector_tags_list li:visible");
+        if ( $visible.length == 1 )
+            _selectContainer($visible);
+    };
+
+    function _dialogHTML() {
+            return "<div>\
+      <div class='tagselector_top'>\
+        <span class='tagselector_searchbox'>\
+          Search: <input type='text' id='tagselector_search'>\
+          </span>\
+          <button id='tagselector_select' disabled='disabled'>Save</button>\
+      </div>\
+      <div id='tagselector_tags'><span class='tagselector_status'>Loading...</span></div>\
+    </div>";
+    };
+
+    function _initTags() {
+        $("button", $.fn.tagselector.instance.siblings()).attr("disabled", "true");
+        $(":input", $.fn.tagselector.instance).attr("disabled", "true");
+
+        var data = _tags();
+        if ( !data ) {
+            $("#tagselector_tags").html("<h2>Error</h2><p>Unable to load tags data</p>");
+            return;
+        }
+
+        var selected = _selectedTags();
+
+        var $tagslist = $("<ul id='tagselector_tags_list'></ul>");
+
+        $.each(data, function(index, value) {
+            var checked = selected[value] ? "checked='checked'" : "";
+            $("<li><input type='checkbox' id='tagselector_tag_"+value+"' value='"+value+"' "+checked+"/><label for='tagselector_tag_"+value+"'>"+value+"</label></li>").appendTo($tagslist);
+        });
+
+        $("#tagselector_tags").empty().append($tagslist);
+
+        $("button", $.fn.tagselector.instance.siblings()).removeAttr("disabled");
+        $(":input", $.fn.tagselector.instance).removeAttr("disabled");
+    }
+
+    function _save() {
+        // remove newly unchecked
+        // make sure new doesn't get lost
+        // add in newly checked
+        var selected = [];
+        $("#tagselector_tags_list input:checked").each(function() {
+            selected.push($(this).val());
+        });
+
+        if ($("#tagselector_tags").data("new"))
+            selected.push($("#tagselector_tags").data("new"));
+
+        _updateSelectedTags(selected.join(","));
+
+        $.fn.tagselector.instance.dialog("close");
+    }
+
+    function _open () {
+        if ( ! $.fn.tagselector.instance ) {
+            $.fn.tagselector.instance = $(_dialogHTML());
+
+            $.fn.tagselector.instance.dialog( { title: opts.title, width: opts.width, height: opts.height, dialogClass: "tagselector", modal: true,
+                close: function() {    $.fn.tagselector.owner.parent().find(":input").focus(); },
+                resize: function() {
+                    $("#tagselector_tags").height(
+                        $.fn.tagselector.instance.height() -
+                        $.fn.tagselector.instance.find('.tagselector_top').height()
+                        - 5
+                    );
+                }
+             } ).keydown(function(event) {
+                if (event.keyCode && event.keyCode === $.ui.keyCode.ENTER) {
+                    _save();
+
+                    event.stopPropagation();
+                    event.preventDefault();
+                }
+            });
+
+            $("#tagselector_tags").height(
+                $.fn.tagselector.instance.height() -
+                $.fn.tagselector.instance.find('.tagselector_top').height()
+                - 5
+            );
+
+            $("#tagselector_search").bind("keyup", _filter);
+            $("#tagselector_select").click(_save);
+
+            _initTags();
+        } else {
+            _initTags();
+            $.fn.tagselector.instance.dialog("open");
+        }
+    };
+
+})(jQuery);
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/js/jquery.vertigro.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/js/jquery.vertigro.js	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,34 @@
+/* vertigro v1.1 - Automatically grow your textarea vertically.
+   Copyright (C) 2009 Paul Pham <http://jquery.aquaron.com/vertigro>
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+(function($){
+   $.fn.vertigro = function($max,$div) {
+      return this.filter('textarea').each(function() {
+         var grow = function(e) {
+            if ($max && $div) {
+               if ($(this).val().length > $max && e.which != 8)
+                  return false;
+               $('#'+$div).html($max-$(this).val().length);
+            }
+            if (this.clientHeight < this.scrollHeight)
+               $(this).height(this.scrollHeight
+               + (parseInt($(this).css('lineHeight').replace(/px$/,''))||20)
+               + 'px');
+         };
+         $(this).css('overflow','hidden').keydown(grow).keyup(grow).change(grow);
+      });
+   };
+})(jQuery);
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/preview/entry.bml
--- a/htdocs/preview/entry.bml	Mon Oct 31 01:22:31 2011 +0800
+++ b/htdocs/preview/entry.bml	Mon Oct 31 13:23:18 2011 +0800
@@ -21,13 +21,18 @@
     my $remote = LJ::get_remote();
     my $styleid; my $stylesys = 1;
 
+
+    my $username = $POST{user} || $POST{username};
+    my $altlogin = $GET{altlogin} || $POST{post_as_other};
+    my $usejournal = $altlogin ? $POST{postas_usejournal} : $POST{usejournal};
+
     ### Figure out poster/journal
-    my ($u, $up);
-    if ($POST{'usejournal'}) {
-        $u = LJ::load_user($POST{'usejournal'});
-        $up = $POST{'user'} ? LJ::load_user($POST{'user'}) : $remote;
-    } elsif ($POST{'user'} && $GET{altlogin}) {
-        $u = LJ::load_user($POST{'user'});
+    my ( $u, $up );
+    if ( $usejournal ) {
+        $u = LJ::load_user( $usejournal );
+        $up = $username ? LJ::load_user( $username ) : $remote;
+    } elsif ( $username && $altlogin ) {
+        $u = LJ::load_user( $username );
     } else {
         $u = $remote;
     }
@@ -57,9 +62,9 @@
     my $r = BML::get_request();
     my $ctx;
 
-    # Get the preview message to hand to S2 or use in $ret. 
-    my $preview_warn_text = $ML{".entry.preview_warn_text"}; 
-    
+    # Get the preview message to hand to S2 or use in $ret.
+    my $preview_warn_text = $ML{".entry.preview_warn_text"};
+
     if ($u && $up) {
         $r->notes->{_journal} = $u->{user};
         $r->notes->{journalid} = $u->{userid};
@@ -196,7 +201,7 @@
 
 
         $ret .= "<br clear='all' /><hr width='100%' size='2' align='center' />";
-        
+
         $ret .= "\n<=body";
         $ret .= "\npage?>";
 
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/base-colors-dark.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/stc/base-colors-dark.css	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,111 @@
+.token {
+  background-color: #333;
+  border-color: #ccc;
+}
+.token.new {
+  border-color: #888;
+  background-color:#222;
+}
+.token .token_remove {
+  border-color: #aaa;
+  color: #666;
+}
+.token:hover, .token.hover, .token:focus, .token.focus {
+  color: #ccc;
+  border-color: #999966;
+}
+.token .token_remove:hover, .token .token_remove:focus {
+  color: #ccc;
+  background-color: #666666;
+}
+.autocomplete_count_container {
+  color: #808080;
+}
+
+#primary .component,
+#secondary .component,
+#tertiary .component,
+.column {
+  border-color:#888888;
+}
+.component {
+  background-color:#444;
+}
+.component h3 {
+  background-color:#727272;
+}
+.component-header-hover {
+  background-color: #999;
+}
+
+.ui-widget-header {
+  background-color:#727272;
+  background-image: url();
+}
+.ui-widget-content, .ui-sortable {
+  border-color: #999
+}
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {
+  color: #E9E9E0;
+  background:#815E00 none repeat scroll 0 0;
+}
+
+#iconselector_icons_list li {
+  border-color: #888;
+  background-color:#444;
+}
+#iconselector_icons_list li:hover, .kwmenu .selected, #iconselector_icons_list .iconselector_selected {
+  border-color: #fff;
+}
+.kwmenu .keyword {
+  border-color: #888;
+  background-color:#727272;
+}
+.kwmenu .selected, #iconselector_icons_list .iconselector_selected {
+  background-color:#727272;
+}
+.kwmenu .selected, #iconselector_icons_list .iconselector_selected  {
+  border-color: #fff;
+}
+
+#post_entry .permalink {
+  color:#888;
+}
+#post_entry .noicon {
+  border-color: #bbb;
+}
+#post_entry .screen-customize-mode .sortable_column_text {
+  color: #ccc;
+}
+
+.toolbar input, .submit input {
+  box-shadow: inset 0 0 1px 1px #777;
+  -moz-box-shadow: inset 0 0 1px 1px #777;
+  -webkit-box-shadow: inset 0 0 1px 1px #777;
+}
+
+.toolbar input:hover, .submit input:hover {
+  background: #444;
+  color: #eee;
+}
+
+.toolbar input:active, .submit input:active {
+  background-color: #d9d9d9;
+  box-shadow: inset 0 0 1px 1px #eaeaea;
+  -moz-box-shadow: inset 0 0 1px 1px #eaeaea;
+  -webkit-box-shadow: inset 0 0 1px 1px #eaeaea;
+  color: #000;
+}
+
+#main-tools {
+  background-color: #333;
+}
+
+#settings-tools {
+  background-color: #3f3f3f;
+}
+
+#plaintext-tools {
+  border-color: #444;
+  background-color: #4b4b4b;
+}
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/base-colors-light.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/stc/base-colors-light.css	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,112 @@
+.token {
+  background-color: #f2f2f2;
+  border-color: #ccc;
+}
+.token.new {
+  border-color: #888;
+  background-color:#fff;
+}
+.token .token_remove {
+  border-color: #aaa;
+  color: #666;
+}
+.token:hover, .token.hover, .token:focus, .token.focus {
+  border-color: #444;
+}
+.token .token_remove:hover, .token .token_remove:focus {
+  color: #999;
+  background-color: #ddd;
+}
+.autocomplete_count_container {
+  color: #808080;
+}
+
+#primary .component, 
+#secondary .component, 
+#tertiary .component,
+.column {
+  border-color:#ccc;
+}
+.component {
+  background-color:#fff;
+}
+.component h3 {
+  background-color:#F2F2F2;
+}
+.component-header-hover {
+  background-color: #ccc;
+}
+
+.ui-widget-header {
+  background-color:#F2F2F2;
+  background-image: url();
+}
+.ui-widget-content, .ui-sortable {
+  border-color: #ccc
+}
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {
+  color: #000;
+  background:#FFF8DC none repeat scroll 0 0;
+}
+
+#iconselector_icons_list li {
+  border-color: #ccc;
+  background-color:#fff;
+}
+.kwmenu .keyword {
+  border-color: #ccc;
+  background-color:#f2f2f2;
+}
+.kwmenu .selected, #iconselector_icons_list .iconselector_selected {
+  border-color: #444; 
+  background-color:#f2f2f2;
+}
+
+#post_entry .permalink {
+  color:#888;
+}
+#post_entry .noicon {
+  border-color: #bbb;
+}
+#post_entry .screen-customize-mode .sortable_column_text {
+  color: #ccc;
+}
+
+.toolbar input, .submit input {
+  background-color: #e3e3e3;
+  border-color: #ccc;
+  box-shadow: inset 0 0 1px 1px #f6f6f6;
+  -moz-box-shadow: inset 0 0 1px 1px #f6f6f6;
+  -webkit-box-shadow: inset 0 0 1px 1px #f6f6f6;
+  color: #333;
+  text-shadow: 0 1px 0px #fff;
+}
+
+.toolbar input:hover, .submit input:hover {
+  background: #f6f6f6;
+  box-shadow: inset 0 0 1px 1px #fff;
+  -moz-box-shadow: inset 0 0 1px 1px #fff;
+  -webkit-box-shadow: inset 0 0 1px 1px #fff;
+  color: #222;
+}
+
+.toolbar input:active, .submit input:active {
+  background-color: #d9d9d9;
+  box-shadow: inset 0 0 1px 1px #eaeaea;
+  -moz-box-shadow: inset 0 0 1px 1px #eaeaea;
+  -webkit-box-shadow: inset 0 0 1px 1px #eaeaea;
+  color: #000;
+}
+
+#main-tools {
+  background-color: #ebebeb;
+}
+
+#settings-tools {
+  background-color: #efefef;
+}
+
+#plaintext-tools {
+  border-color: #efefef;
+  background-color: #f2f2f2;
+}
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/blueshift/blueshift.css
--- a/htdocs/stc/blueshift/blueshift.css	Mon Oct 31 01:22:31 2011 +0800
+++ b/htdocs/stc/blueshift/blueshift.css	Mon Oct 31 13:23:18 2011 +0800
@@ -818,7 +818,8 @@
 }
 input.text,
 textarea.text,
-select.select {
+select.select,
+.autocomplete_container {
     background: #fff url("/img/input-bg.gif") repeat-x 0 -1px;
     border: 1px solid #bbb;
     border-top: 1px solid #999;
@@ -1084,6 +1085,24 @@
     /*color:#666;*/
 }
 
+/* post page */
+.token:hover, .token.hover, .token:focus, .token.focus {
+    color: #3960A0;
+    border-color: #002A92;
+}
+
+.token .token_remove:hover, .token .token_remove:focus {
+    color: #3960A0;
+    background-color: #E6ECF9;
+}
+
+#iconselector_icons_list li:hover, .kwmenu .selected, #iconselector_icons_list .iconselector_selected {
+    border-color: #3960A0;
+}
+
+.slidecontrols a:hover {
+    color: #002A92;
+}
 
 /* contextualhover.css */
 div.ContextualPopup div.Inner {
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/celerity/celerity.css
--- a/htdocs/stc/celerity/celerity.css	Mon Oct 31 01:22:31 2011 +0800
+++ b/htdocs/stc/celerity/celerity.css	Mon Oct 31 13:23:18 2011 +0800
@@ -440,7 +440,7 @@
     border-collapse: collapse;
 }
 table.grid, table.grid td {
-    border: 1px solid #999;    
+    border: 1px solid #999;
 }
 
 .select-list li, .NotificationTable td {
@@ -474,10 +474,10 @@
   background-color:#fff;
 }
 
-.simple-form .error input {
+.simple-form .error input, form .error input {
     border: 3px solid #ff0000;
 }
-.simple-form .error .error-msg {
+.simple-form .error .error-msg, form .error .error-msg {
     color: #ff0000;
     display: block;
 }
@@ -528,6 +528,24 @@
     border: 1px solid #fff;
 }
 
+.token:hover, .token.hover, .token:focus, .token.focus {
+    color: #999966;
+    border-color:#666611;
+}
+
+.token .token_remove:hover, .token .token_remove:focus {
+    color: #999966;
+    background-color: #EEEECC;
+}
+
+#iconselector_icons_list li:hover, .kwmenu .selected, #iconselector_icons_list .iconselector_selected {
+    border-color: #999966;
+}
+
+.slidecontrols a:hover {
+    color: #999966;
+}
+
 /* contextualhover.css */
 div.ContextualPopup div.Inner {
     background-color: #fff !important;
@@ -636,7 +654,8 @@
 
 input.text,
 textarea.text,
-select.select {
+select.select,
+.autocomplete_container {
     background: #fff url("/img/input-bg.gif") repeat-x 0 -1px;
     border: 1px solid #bbb;
     border-top: 1px solid #999;
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/gradation/gradation.css
--- a/htdocs/stc/gradation/gradation.css	Mon Oct 31 01:22:31 2011 +0800
+++ b/htdocs/stc/gradation/gradation.css	Mon Oct 31 13:23:18 2011 +0800
@@ -202,7 +202,7 @@
 	border-top: 1px solid #000;
 	border-bottom: 1px solid #000;
 }
-#canvas.horizontal-nav #menu ul { margin-left: 0; 
+#canvas.horizontal-nav #menu ul { margin-left: 0;
 	padding-left: 0;
 	list-style: none; }
 #canvas.horizontal-nav #menu ul li {
@@ -533,7 +533,7 @@
     border-collapse: collapse;
 }
 table.grid, table.grid td {
-    border: 1px solid #888;    
+    border: 1px solid #888;
 }
 
 .select-list li, .NotificationTable td {
@@ -566,10 +566,10 @@
   background-color:#111;
 }
 
-.simple-form .error input {
+.simple-form .error input, form .error input {
     border: 3px solid #ff0000;
 }
-.simple-form .error .error-msg {
+.simple-form .error .error-msg, form .error .error-msg {
     color: #ff0000;
     display: block;
 }
@@ -618,6 +618,10 @@
      border: 1px solid #111111;
 }
 
+/* posts page */
+.slidepanel {
+    margin-top: 0.5em;
+}
 
 /* contextualhover.css */
 div.ContextualPopup div.Inner {
@@ -723,7 +727,8 @@
 
 input.text,
 textarea.text,
-select {
+select,
+.autocomplete_container {
     /*background: #fff url("/img/input-bg.gif") repeat-x 0 -1px;*/
     background-color: #222;
     border: 1px solid #666666;
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/jquery.autocompletewithunknown.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/stc/jquery.autocompletewithunknown.css	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,72 @@
+
+.token {
+    font-size:1em;
+    padding:.2em .3em;
+    border-width: 1px;
+    border-style: solid;
+    line-height: 1.1em;
+    margin: 0.2em;
+    float: left;
+    -webkit-border-radius: 6px;
+    -moz-border-radius: 6px;
+    border-radius: 6px;
+}
+
+.token.new {
+    border-width: 1px;
+    border-style: dashed;
+}
+
+.token .token_remove {
+    padding: 0.2em 0.3em;
+    border-left: 1px solid;
+    margin-left: 0.2em;
+    font-weight: bold;
+    text-decoration: none;
+    -moz-border-radius-topright:5px;
+    -moz-border-radius-bottomright:5px;
+    -webkit-border-top-right-radius:5px;
+    -webkit-border-bottom-right-radius:5px;
+    border-top-right-radius:5px;
+    border-bottom-right-radius:5px;
+}
+
+.autocomplete_container {
+    border-width: 1px;
+    border-style: solid;
+    float: left;
+    width: 100%;
+    margin-top: 0.25em;
+    line-height: 1em;
+}
+
+.autocomplete_container textarea {
+    width: 97%;
+}
+
+.autocomplete_container input, .autocomplete_container textarea  {
+    border: 0px none !important;
+}
+
+.autocomplete_container input:focus, .autocomplete_container textarea {
+    outline: none;
+}
+
+ul.autocomplete_list {
+    margin: 0 !important;
+    padding: 0;
+    list-style-type: none;
+}
+
+.autocomplete_list li {
+    list-style-type: none;
+}
+
+.autocomplete_count_container {
+    float: right;
+}
+
+.autocomplete_count {
+    font-weight: bold;
+    font-size: larger;
+}
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/jquery.iconselector.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/stc/jquery.iconselector.css	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,148 @@
+.image-text-toggle {
+    display:block;
+    text-align:right;
+    margin-bottom:.5em;
+    font-size:90%;
+}
+
+.iconselector_searchbox {
+  float:left;
+  padding-bottom:1em;
+  font-weight:bold;
+}
+
+#iconselector_icons_list {
+    list-style-type:none;
+
+}
+#iconselector_icons_list li {
+    float:left;
+    margin:.25em;
+    padding:.25em;
+    width:110px;
+    text-align:center;
+    border-width:1px;
+    border-style: solid;
+    overflow:hidden;
+}
+
+#iconselector_icons_list li:hover {
+    border-width:1px;
+    border-style:solid;
+}
+
+.icon_image {
+    text-align:center;
+    padding:5px;
+    height:100px;
+    width:100px;
+}
+
+
+.meta_wrapper {
+    height: 5em;
+    font-size:90%;
+    line-height:1.2em;
+}
+
+.no_meta .meta_wrapper {
+    display: none;
+}
+
+.image-text-toggle .toggle-image-only, .image-text-toggle.no_meta .toggle-no-meta {
+    display: inline;
+}
+
+.image-text-toggle.no_meta .toggle-image-only, .image-text-toggle .toggle-no-meta {
+    display: none;
+}
+
+.comment {
+  margin-top:.5em;
+}
+
+.kwmenu {
+  clear:left;
+  padding-bottom:.1em;
+}
+
+.kwmenu .keyword {
+    font-size:90%;
+    border-width: 1px;
+    border-style: solid;
+    -webkit-border-radius: 6px;
+    -moz-border-radius: 6px;
+    border-radius: 6px;
+    margin:0 .2em;
+    padding:.2em .3em;
+    line-height:1.1em;
+    text-align:center;
+}
+
+.kwmenu .keyword, .kwmenu .keyword {
+  text-decoration:none;
+}
+
+.kwmenu .selected, #iconselector_icons_list .iconselector_selected {
+    border-width:1px;
+    border-style: solid;
+}
+
+.iconselector_top {
+  margin-bottom:1em;
+}
+
+.iconselector_top .keywords {
+    display: inline;
+}
+
+.iconselector .ui-dialog-content {
+    overflow: visible;
+}
+
+#iconselector_icons {
+    overflow: auto;
+}
+
+#iconselector_select {
+  float:right;
+  margin-top:-.3em;
+}
+
+/* UI overrides */
+
+.ui-dialog {
+  padding:0;
+}
+
+.ui-dialog .ui-dialog-content {
+  padding:.5em;
+}
+
+.ui-widget-content {
+  border-width:1px;
+  border-style:solid;
+}
+
+.ui-widget-header {
+  border:0;
+}
+.ui-dialog .ui-dialog-titlebar {
+  padding:.3em;
+}
+
+.ui-dialog .ui-dialog-buttonpane {
+  padding:.3em 0;
+  text-align:center;
+  margin:0;
+}
+
+.ui-dialog .ui-dialog-buttonpane button {
+  margin:0;
+  padding:0;
+  float:none;
+}
+
+.iconselector {
+  padding-bottom:1em;
+}
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/jquery.postoptions.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/stc/jquery.postoptions.css	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,77 @@
+.panels-list li {
+  float:left;
+  clear:none;
+  display:block;
+  width:25%;
+  min-width: 7em;
+}
+
+#post_entry fieldset.submit, #post_entry fieldset.destructive {
+  width: auto;
+  margin-top: 0.5em;
+}
+
+#post-options .note {
+  clear: both;
+}
+
+/* sortable */
+.ui-sortable {
+    border-width: 2px;
+    border-style: dashed;
+    min-height: 200px;
+    padding: 1em 0 10em 0 !important;
+    position: relative;
+}
+
+#post_entry .ui-sortable .component {
+    cursor: move;
+    border-left-style: none;
+    border-right-style: none;
+}
+
+#post_entry .ui-sortable .ui-sortable-helper {
+    border-style: solid;
+}
+
+.ui-sortable-disabled {
+    border-style: none;
+    min-height: 0;
+    padding: 0 !important;
+}
+
+.ui-sortable-disabled .component {
+    cursor: auto;
+}
+
+.sortable_column_text {
+    display: none;
+}
+
+.ui-sortable .sortable_column_text {
+    padding: 0.5em 1.5em;
+    text-align: center;
+    position: absolute;
+    bottom: 0;
+    z-index: -1;
+    opacity: 0.2;
+}
+
+.screen-customize-mode .sortable_column_text {
+    font-size: 2em;
+    font-weight: bold;
+    display: block;
+}
+
+.ui-sortable .ui-state-highlight {
+    min-width: 49%;
+}
+.ui-sortable .inactive_component {
+    display: block;
+    opacity: 0.4;
+}
+
+.ui-sortable .inactive_component .inner {
+    display: none;
+}
+
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/jquery.tagselector.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/stc/jquery.tagselector.css	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,27 @@
+#tagselector_tags label {
+    font-weight: normal;
+}
+
+#tagselector_tags li {
+    list-style: none;
+    float: left;
+    width: 15em;
+    text-indent:-1.7em;
+    padding:0.2em .7em 0.2em 1.7em;
+}
+
+.tagselector_trigger {
+    float: left;
+}
+
+.tagselector .ui-dialog-content {
+    overflow: visible;
+}
+
+#tagselector_tags {
+    overflow: auto;
+}
+
+#tagselector_select {
+  float:right;
+}
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/lynx/lynx.css
--- a/htdocs/stc/lynx/lynx.css	Mon Oct 31 01:22:31 2011 +0800
+++ b/htdocs/stc/lynx/lynx.css	Mon Oct 31 13:23:18 2011 +0800
@@ -84,7 +84,7 @@
     border-collapse: collapse;
 }
 table.grid, table.grid td {
-    border: 1px solid #999;    
+    border: 1px solid #999;
 }
 
 .collapsible .collapse-button {
@@ -96,10 +96,10 @@
     border-bottom: 1px solid #ccc;
 }
 
-.simple-form .error input {
+.simple-form .error input, form .error input {
     border: 3px solid #ff0000;
 }
-.simple-form .error .error-msg {
+.simple-form .error .error-msg, form .error .error-msg  {
     color: #ff0000;
     display: block;
 }
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/postform-minimal.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/stc/postform-minimal.css	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,44 @@
+.minimal .component h3 {
+  display: none;
+}
+
+.minimal .component label, .minimal #post_as fieldset legend {
+  width: 40% !important;
+  padding-right: 5px;
+  text-align: right;
+}
+
+.minimal #primary .component, .minimal #secondary .component, .minimal #tertiary .component {
+  border: 0;
+}
+
+.minimal .column {
+  border-width: 1px;
+  border-style: solid;
+}
+
+
+.midimal .component h3 {
+  font-weight: bold;
+  font-size: 100%;
+  background-color: transparent;
+  text-decoration: underline;
+  padding-top: 0;
+  padding-bottom: 0;
+}
+
+.midimal .component label, .midimal #post_as fieldset legend {
+  width: 40% !important;
+  padding-right: 5px;
+  text-align: right;
+}
+
+.midimal #primary .component, .midimal #secondary .component, .midimal #tertiary .component {
+  border: 0;
+}
+
+.midimal .column {
+  border-width: 1px;
+  border-style: solid;
+}
+
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/postform-resize.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/stc/postform-resize.css	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,148 @@
+/* Fluid layout when resizing */
+@media (min-width: 0) {
+  /* don't need the min-width for browsers which understand media queries*/
+  .column {
+    min-width: 0;
+  }
+  .entry-full-width #secondary {
+    float: left;
+  }
+  .entry-full-width #tertiary {
+    float: right;
+  }
+
+  /* entry field forced to smaller space, for slightly narrower windows */
+  #entry_field_width p.note {
+    display: none;
+  }
+}
+
+@media (min-width: 1024px) {
+  /* entry field forced to full screen, for wide windows */
+  .entry-full-width #primary {
+    width:100%;
+  }
+
+  .entry-full-width #secondary {
+    clear:left;
+    width:33%;
+    padding-top:.5em;
+  }
+
+  .entry-full-width #tertiary {
+    width:66%;
+  }
+}
+
+@media (max-width: 1023px) {
+  .entry-full-width #primary {
+    width:100%;
+  }
+
+  .entry-full-width #secondary {
+    clear:left;
+    width:33%;
+    padding-top:.5em;
+  }
+
+  .entry-full-width #tertiary {
+    width:66%;
+  }
+
+  .entry-partial-width #primary, .entry-partial-width #tertiary {
+    width: 72%;
+  }
+  .entry-partial-width #secondary {
+    width: 26.5%;
+    clear: none;
+    padding-top: 0;
+  }
+}
+
+@media (max-width: 800px) {
+  .entry-full-width #primary, .entry-partial-width #primary {
+    width:100%;
+  }
+
+.entry-full-width #secondary, .entry-partial-width #secondary {
+    clear:left;
+    width:100%;
+    margin-bottom:.5em;
+  }
+
+ #secondary.ui-sortable {
+    padding: 0 !important;
+ }
+
+ .ui-sortable {
+    min-height: 50px;
+ }
+
+  .entry-full-width #tertiary, .entry-partial-width #tertiary {
+    width:100%;
+    clear:both;
+  }
+
+  .entry-field-options {
+    display: none;
+  }
+
+  #entry_field_width p.note {
+    display: block;
+  }
+
+  .panels_column li {
+    width:30%;
+  }
+}
+
+
+/* narrow screens, mobile phones */
+@media screen and (max-width: 550px), only screen and (max-device-width: 480px) {
+  .entry-full-width #primary, .entry-partial-width #primary {
+    width:100%;
+  }
+
+.entry-full-width #secondary, .entry-partial-width #secondary {
+    clear:left;
+    width:100%;
+    margin-bottom:.5em;
+  }
+
+  #secondary .component,
+  #tertiary .component {
+    width:100%;
+  }
+
+  .entry-full-width #tertiary, .entry-partial-width #tertiary,
+  #tertiary .column {
+    width:100%;
+    clear:both;
+  }
+
+  .entry-field-options {
+    display: none;
+  }
+
+  #entry_field_width p.note {
+    display: block;
+  }
+  .panels_column li {
+    width:45%;
+  }
+
+  #shim-alpha {
+    height:13em;
+  }
+  #menu {
+    top:14em;
+  }
+
+  #masthead {
+    height:17em;
+  }
+
+  #account-links {
+    top:5em;
+  }
+}
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/postform.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/stc/postform.css	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,358 @@
+/* Page layout */
+#primary, #secondary, #tertiary {
+  margin-bottom: 0.5em;
+}
+
+.entry-full-width #primary {
+  width: 100%;
+}
+
+.entry-full-width #secondary {
+  clear:left;
+  float: none;
+  width:100%;
+  padding-top:.5em;
+}
+
+.entry-partial-width #primary, .entry-partial-width #tertiary {
+  width:72%;
+  float:left;
+  padding:0;
+  margin:0;
+}
+
+.entry-partial-width #secondary {
+  width:26.5%;
+  float:right;
+  padding:0;
+}
+
+#tertiary .column {
+  padding-top: .5em;
+}
+
+.column {
+  min-width: 15em;
+}
+
+
+#content  {
+  overflow:visible;
+}
+
+/* Components */
+
+.component {
+  margin-bottom:0.5em;
+}
+
+.inactive_component {
+  display: none;
+}
+
+#primary .component,
+#secondary .component,
+#tertiary .component {
+  border-width:1px;
+  border-style:solid;
+}
+
+#tertiary .column {
+  width: 49%;
+  float: left;
+  overflow: visible;
+}
+#tertiary .tertiary-right {
+  float:right;
+}
+
+#post_entry fieldset {
+  width:100%;
+  padding: 0;
+  margin: 0;
+  border: none;
+}
+
+.component legend {
+  width:100%;
+  font-weight:bold;
+}
+
+.component h3 {
+  font-size:110%;
+  margin: 0;
+  padding: .5em 0 .2em .5em;
+}
+
+.component h4 {
+  font-size:105%;
+  margin-top:.5em;
+}
+
+#content .component p {
+  margin-bottom:.5em;
+}
+
+.component .inner {
+  padding:5px;
+  font-size:90%;
+}
+
+.component .inner p {
+  clear:both;
+  margin-top:0;
+  padding:0;
+}
+
+.component ul {
+  list-style-type:none;
+  margin-left:0;
+  padding-left:0;
+}
+
+/* Labels */
+.component label {
+  font-weight:bold;
+}
+
+.component .radiolabel,
+.component .checkboxlabel {
+  font-weight:normal;
+}
+
+#currents_component label,
+.recurring_container label,
+.age_level_reason label,
+.posting_settings label {
+  float:left;
+  display:block;
+  width:8em;
+}
+
+#currents_component label {
+  width:38%;
+}
+
+.comments_settings label,
+#security_group label {
+  display:block;
+  float:left;
+  width:40%;
+}
+
+/* Subject & Entry */
+
+#subject {
+  font-size: 1.2em;
+}
+
+#event {
+  font-size: normal;
+  margin-bottom:.5em;
+}
+
+#event, #subject {
+  width: 100%;
+  -ms-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+
+.event-container, .subject-container {
+  padding: 0 2px;
+}
+
+.toolbar {
+  width: 100%;
+  margin-bottom: 0.2em;
+}
+.toolbar input, .submit input {
+  border-width: 1px;
+  border-style: solid;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  font-weight: bold;
+  line-height: 1;
+  padding: 3px 8px 4px;
+  text-align: center;
+}
+
+.toolbar img {
+  vertical-align: text-bottom;
+}
+
+.subtoolbar {
+  padding: 0.2em 1em;
+}
+
+.subtoolbar form, .subtoolbar form .inner {
+  width: 100%;
+}
+
+#main-tools {
+  -webkit-border-top-left-radius: 8px;
+  -webkit-border-top-right-radius: 8px;
+  -moz-border-radius-topleft: 8px;
+  -moz-border-radius-topright: 8px;
+  border-top-left-radius: 8px;
+  border-top-right-radius: 8px;
+  text-align: right;
+}
+
+#plaintext-tools {
+  -webkit-border-bottom-right-radius: 8px;
+  -webkit-border-bottom-left-radius: 8px;
+  -moz-border-radius-bottomright: 8px;
+  -moz-border-radius-bottomleft: 8px;
+  border-bottom-right-radius: 8px;
+  border-bottom-left-radius: 8px;
+  border-width: 1px;
+  border-style: solid;
+}
+
+/* Icon */
+
+#icon_component .inner {
+  text-align:center;
+}
+
+#icon_preview_image {
+  padding: 5px 5px 5px 5px;
+  height:100px;
+  position: absolute;
+}
+
+ul.icon-functions {
+  float: right;
+  margin-top: 65px;
+  line-height: 1.8em;
+}
+
+.icon {
+    width:100px;
+    height:100px;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+.noicon {
+    border-width: 1px;
+    border-style: solid;
+    line-height: 50px;
+}
+
+#icon_component :link img {
+    border: 0;
+}
+
+#iconselect {
+  width:100%;
+}
+
+/* Tags */
+
+#taglist { /* the tags textfield */
+  width:99%;
+}
+.autocomplete_container {
+  float:none;
+  margin-bottom: 5px;
+}
+#tags_component .inner {
+  overflow: auto;
+}
+
+/* Currents */
+
+#moodpreview {
+    margin-top: 5px;
+    text-align: center;
+}
+
+#currents_component input,
+#currents_component select {
+  width:11.9em;
+  margin-left:.1em;
+}
+
+#currents_component  p,
+#restrictions p,  {
+    margin-bottom:.5em;
+}
+
+/* Display Dates */
+
+#displaydate_component .ui-icon {
+    float: left;
+    margin-right: 0.5em;
+}
+
+.time_container_with_picker .dateformat {
+    display: none;
+}
+
+.displaydate_options {
+  margin-top:.5em;
+}
+
+.dateformat {
+  margin-left:16px;
+  padding-left:.5em;
+}
+
+/* Security & Access */
+
+#custom_access_groups li {
+  float:left;
+  width:50%;
+  margin-bottom:.3em;
+}
+
+/* Publishing */
+#post_as fieldset legend {
+  float: left;
+  width: 8em;
+}
+
+.posting_settings li {
+  margin-bottom:.5em;
+}
+
+.posting_settings {
+  margin-top:.5em;
+}
+
+.crosspost_password_container {
+  margin-left: 2em;
+}
+
+.crosspost-settings {
+  float:right;
+}
+
+
+/* Scheduled Posts */
+
+/* Footer Buttons */
+
+.action-bar {
+  clear:both;
+  padding: 0.2em .5em;
+  text-align:left;
+  overflow: auto;
+  position: relative;
+}
+
+#actions input {
+  float: right;
+  font-size: 1.2em;
+  line-height: 1.5em;
+}
+
+.otheractions {
+  line-height: 2em;
+  position: absolute;
+  bottom: 0.5em;
+}
+
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/stc/simple-form.css
--- a/htdocs/stc/simple-form.css	Mon Oct 31 01:22:31 2011 +0800
+++ b/htdocs/stc/simple-form.css	Mon Oct 31 13:23:18 2011 +0800
@@ -1,3 +1,11 @@
+.simple-form {
+    overflow: auto;
+}
+
+.simple-form .inner {
+    float: left; /* align the submit button */
+}
+
 .simple-form fieldset {
     position: relative;
     float: left;
@@ -29,6 +37,7 @@
     list-style: none;
     padding: 2.5em 0 0 0;
     margin: 0;
+    width: 100%;
     zoom: 1;
 }
 
@@ -51,11 +60,39 @@
 
 /* Submit/action buttons */
 .simple-form fieldset.submit {
-    float: none;     
+    float: right;
     width: auto;
-    text-align: center;
+    text-align: right;
     padding: 1em 0;
-    margin:0;
+    margin:0 2em;
+}
+.simple-form fieldset.destructive {
+   float: left;
+   clear: none;
+   width: auto;
+   text-align: left;
+   margin-right: 3em;
+   line-height: 3em;
+}
+
+.simple-form fieldset.submit input {
+  border-width: 1px;
+  border-style: solid;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  font-weight: bold;
+  line-height: 1;
+  padding: 3px 8px 4px;
+  text-align: center;
+
+  font-size: 1.2em;
+  line-height: 1.5em;
+}
+
+.simple-form fieldset.destructive input {
+  font-size: 1em;
+  line-height: 1em;
 }
 
 /* Nested Fieldsets */
@@ -64,8 +101,9 @@
     background-color: transparent;
 }
 .simple-form fieldset fieldset ul {
-    margin: 0 0 0 12.2em;
-    padding: 0;
+    padding: 0 0 0 12.2em;
+    margin: 0;
+    width: auto;
 }
 .simple-form fieldset fieldset legend {
     font-weight: normal;
diff -r 139a07b97016 -r 63e3af5ee420 htdocs/update.bml
--- a/htdocs/update.bml	Mon Oct 31 01:22:31 2011 +0800
+++ b/htdocs/update.bml	Mon Oct 31 13:23:18 2011 +0800
@@ -50,6 +50,9 @@
     # Errors that are unlikely to change between starting
     # to compose an entry and submitting it.
     if ($remote) {
+        BML::redirect( LJ::create_url( "/entry/new", cur_args => \%GET, keep_args => 1 ) )
+            if LJ::BetaFeatures->user_in_beta( $remote => "updatepage" );
+
         if ($remote->identity) {
             $$title = $ML{'Sorry'};
             $$body = BML::ml('.error.nonusercantpost', {'sitename' => $LJ::SITENAME});
diff -r 139a07b97016 -r 63e3af5ee420 schemes/blueshift.tt
--- a/schemes/blueshift.tt	Mon Oct 31 01:22:31 2011 +0800
+++ b/schemes/blueshift.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -22,5 +22,6 @@
         'stc/reset.css',
         'stc/jquery/jquery.ui.theme.smoothness.css',
         'stc/lj_base-app.css',
+        'stc/base-colors-light.css',
         'stc/blueshift/blueshift.css') -%]
-[%- END -%]
\ No newline at end of file
+[%- END -%]
diff -r 139a07b97016 -r 63e3af5ee420 schemes/celerity.tt
--- a/schemes/celerity.tt	Mon Oct 31 01:22:31 2011 +0800
+++ b/schemes/celerity.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -22,12 +22,13 @@
         'stc/reset.css',
         'stc/jquery/jquery.ui.theme.smoothness.css',
         'stc/lj_base-app.css',
+        'stc/base-colors-light.css',
         'stc/celerity/celerity.css') -%]
 [%- END -%]
 
 [%- account_link_options = {
     no_userpic = 1,
-    
+
 } -%]
 
 [%- userpic_class = 'header-userpic' -%]
@@ -59,7 +60,7 @@
                 <div id="header-divider"> <div id="header-divider-insert"></div></div>
                 <div id="header-search">
                     [% dw_scheme.search_render %]
-                </div><!-- end header-search--> 
+                </div><!-- end header-search-->
                 <div id="footer">
                     [% PROCESS block.footer %]
                 </div>
diff -r 139a07b97016 -r 63e3af5ee420 schemes/gradation-horizontal.tt
--- a/schemes/gradation-horizontal.tt	Mon Oct 31 01:22:31 2011 +0800
+++ b/schemes/gradation-horizontal.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -22,9 +22,10 @@
             'stc/reset.css',
             'stc/jquery/jquery.ui.theme.dark-hive.css',
             'stc/lj_base-app.css',
+            'stc/base-colors-dark.css',
             'stc/gradation/gradation.css');
         dw_scheme.need_res({ group => 'jquery' }, 'js/nav-jquery.js' );
         dw_scheme.need_res({ group => 'default' }, 'js/nav.js' ); -%]
 [%- END -%]
 
-[%- canvas_opts='class="horizontal-nav"' -%]
\ No newline at end of file
+[%- canvas_opts='class="horizontal-nav"' -%]
diff -r 139a07b97016 -r 63e3af5ee420 schemes/gradation-vertical.tt
--- a/schemes/gradation-vertical.tt	Mon Oct 31 01:22:31 2011 +0800
+++ b/schemes/gradation-vertical.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -22,7 +22,8 @@
             'stc/reset.css',
             'stc/jquery/jquery.ui.theme.dark-hive.css',
             'stc/lj_base-app.css',
+            'stc/base-colors-dark.css',
             'stc/gradation/gradation.css'); -%]
 [%- END -%]
 
-[%- canvas_opts='class="vertical-nav"' -%]
\ No newline at end of file
+[%- canvas_opts='class="vertical-nav"' -%]
diff -r 139a07b97016 -r 63e3af5ee420 schemes/lynx.tt
--- a/schemes/lynx.tt	Mon Oct 31 01:22:31 2011 +0800
+++ b/schemes/lynx.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -8,6 +8,7 @@
 [% dw_scheme.need_res(
     'stc/jquery/jquery.ui.theme.smoothness.css',
     'stc/lj_base-app.css',
+    'stc/base-colors-light.css',
     'stc/lynx/lynx.css' ) %]
 <style>
     #Comments q { padding-left: 2.5em; font-style: italic; }
@@ -35,4 +36,4 @@
 [% dw_scheme.final_body_html %]
 </body>
 </html>
-[%- END -%]
\ No newline at end of file
+[%- END -%]
diff -r 139a07b97016 -r 63e3af5ee420 t/post.t
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/t/post.t	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,586 @@
+# -*-perl-*-
+
+use strict;
+use warnings;
+use Test::More;
+use lib "$ENV{LJHOME}/cgi-bin";
+require 'ljlib.pl';
+use LJ::Test qw( temp_user temp_comm );
+
+use DW::Routing::CallInfo;
+use DW::Controller::Entry;
+
+use LJ::Community;
+use LJ::Entry;
+
+use Hash::MultiValue;
+
+use FindBin qw($Bin);
+chdir "$Bin/data/userpics" or die "Failed to chdir to t/data/userpics";
+
+# preload userpics so we don't have to read the file hundreds of times
+open (my $fh, 'good.png') or die $!;
+my $ICON1 = do { local $/; <$fh> };
+
+open (my $fh2, 'good.jpg') or die $!;
+my $ICON2 = do { local $/; <$fh2> };
+
+$LJ::CAP{$_}->{moodthemecreate} = 1
+    foreach( 0.. 15 );
+
+note( "Not logged in - init" );
+{
+    my $vars = DW::Controller::Entry::init();
+
+    # user
+    ok( ! $vars->{remote} );
+
+    # icon
+    ok( ! @{$vars->{icons}}, "No icons." );
+    ok( ! $vars->{defaulticon}, "No default icon." );
+
+    # mood theme
+    ok( ! keys %{$vars->{moodtheme}}, "No mood theme." );
+
+    TODO: {
+        local $TODO = "usejournal";
+    }
+}
+
+note( "Logged in - init" );
+{
+    my $u = temp_user();
+    LJ::set_remote( $u );
+
+    my $vars;
+    $vars = DW::Controller::Entry::init();
+
+    ok( $u->equals( $vars->{remote} ), "Post done as currently logged in user." );
+
+    note( "# Icons " );
+    note( "  no icons" );
+    ok( ! @{$vars->{icons}}, "No icons." );
+    ok( ! $vars->{defaulticon}, "No default icon." );
+
+    note( "  no default icon" );
+    my $icon1 = LJ::Userpic->create( $u, data => \$ICON1 );
+    my $icon2 = LJ::Userpic->create( $u, data => \$ICON2 );
+    $icon1->set_keywords( "b, z" );
+    $icon2->set_keywords( "a, c, y" );
+
+    $vars = DW::Controller::Entry::init();
+    is( @{$vars->{icons}}, 6, "Has icons (including a blank one in the list for default)" );
+    ok( ! $vars->{defaulticon}, "No default icon." );
+
+    my @icon_order = (
+        # keyword, userpic object
+        [ undef, undef ],
+        [ "a", $icon2 ],
+        [ "b", $icon1 ],
+        [ "c", $icon2 ],
+        [ "y", $icon2 ],
+        [ "z", $icon1 ],
+    );
+    my $count = 0;
+    foreach my $icon ( @{$vars->{icons}} ) {
+        if ( $count == 0 ) {
+            is( $icon->{keyword}, undef, "No default icon; no keyword.");
+            is( $icon->{userpic}, undef, "No default icon.");
+        } else {
+            is( $icon->{keyword}, $icon_order[$count]->[0], "Keyword is in proper order." );
+            is( $icon->{userpic}->id, $icon_order[$count]->[1]->id, "Icon is proper icon." );
+        }
+        $count++;
+    }
+
+    note( "  with default icon" );
+    $icon1->make_default;
+    $vars = DW::Controller::Entry::init();
+    ok( $vars->{defaulticon}, "Has default icon." );
+
+    $icon_order[0] = [ undef, $icon1 ];
+    $count = 0;
+    foreach my $icon ( @{$vars->{icons}} ) {
+        is( $icon->{keyword}, $icon_order[$count]->[0], "Keyword is in proper order." );
+        is( $icon->{userpic}->id, $icon_order[$count]->[1]->id, "Icon is proper icon." );
+        $count++;
+    }
+
+
+    note( "# Moodtheme" );
+    note( "  default mood theme" );
+    $vars = DW::Controller::Entry::init();
+    my $moods = DW::Mood->get_moods;
+
+    ok( $vars->{moodtheme}->{id} == $LJ::USER_INIT{moodthemeid}, "Default mood theme." );
+    is( scalar keys %{$vars->{moodtheme}->{pics}}, scalar keys %$moods, "Complete mood theme." );
+
+    note( "  no mood theme" );
+    $u->update_self( { moodthemeid => undef } );
+    $u = LJ::load_user($u->user, 'force');
+
+    $vars = DW::Controller::Entry::init();
+    ok( ! %{ $vars->{moodtheme} }, "No mood theme." );
+
+    note( "  custom mood theme with incomplete moods" );
+    my $themeid = $u->create_moodtheme( "testing", "testing a custom mood theme with missing moods." );
+    my $customtheme = DW::Mood->new( $themeid );
+    $u->update_self( { moodthemeid => $customtheme->id } );
+    $u = LJ::load_user($u->user, 'force');
+
+    # pick a mood, any mood
+    my $testmoodid = (keys %$moods)[0];
+    my $err;
+    $customtheme->set_picture( $testmoodid, { picurl => "http://example.com/moodpic", width => 10, height => 20 }, \$err );
+
+    $vars = DW::Controller::Entry::init();
+    is( $vars->{moodtheme}->{id}, $customtheme->id, "Custom mood theme." );
+    is( scalar keys %{$vars->{moodtheme}->{pics}}, 1, "Only provide picture information for moods with valid pictures." );
+    is( $vars->{moodtheme}->{pics}->{$testmoodid}->{pic}, "http://example.com/moodpic", "Confirm picture URL matches." );
+    is( $vars->{moodtheme}->{pics}->{$testmoodid}->{width}, 10, "Confirm picture width matches." );
+    is( $vars->{moodtheme}->{pics}->{$testmoodid}->{height}, 20, "Confirm picture height matches." );
+    is( $vars->{moodtheme}->{pics}->{$testmoodid}->{name}, $moods->{$testmoodid}->{name}, "Confirm mood name matches.");
+
+    note( "Security levels ");
+    $vars = DW::Controller::Entry::init();
+    is( scalar @{$vars->{security}}, 3, "Basic security levels" );
+    is( $vars->{security}->[0]->{label}, LJ::Lang::ml( 'label.security.public2' ), "Public security" );
+    is( $vars->{security}->[0]->{value}, "public", "Public security" );
+    is( $vars->{security}->[1]->{label}, LJ::Lang::ml( 'label.security.accesslist' ), "Access-only security" );
+    is( $vars->{security}->[1]->{value}, "access", "Access-only security" );
+    is( $vars->{security}->[2]->{label}, LJ::Lang::ml( 'label.security.private2' ), "Private security" );
+    is( $vars->{security}->[2]->{value}, "private", "Private security" );
+
+    $u->create_trust_group( groupname => "test" );
+    $vars = DW::Controller::Entry::init();
+    is( scalar @{$vars->{security}}, 4, "Security with custom groups" );
+    is( $vars->{security}->[0]->{label}, LJ::Lang::ml( 'label.security.public2' ), "Public security" );
+    is( $vars->{security}->[0]->{value}, "public", "Public security" );
+    is( $vars->{security}->[1]->{label}, LJ::Lang::ml( 'label.security.accesslist' ), "Access-only security" );
+    is( $vars->{security}->[1]->{value}, "access", "Access-only security" );
+    is( $vars->{security}->[2]->{label}, LJ::Lang::ml( 'label.security.private2' ), "Private security" );
+    is( $vars->{security}->[2]->{value}, "private", "Private security" );
+    is( $vars->{security}->[3]->{label}, LJ::Lang::ml( 'label.security.custom' ), "Custom security" );
+    is( $vars->{security}->[3]->{value}, "custom", "Custom security" );
+    is( @{$vars->{customgroups}}, 1, "Custom group list");
+    is( $vars->{customgroups}->[0]->{label}, "test" );
+    is( $vars->{customgroups}->[0]->{value}, 1 );
+
+    note( "# Usejournal" );
+    note( "  No communities." );
+    $vars = DW::Controller::Entry::init();
+    is( scalar @{$vars->{journallist}}, 1,  "One journal (yourself)" );
+    ok( $vars->{journallist}->[0]->equals( $u ), "First journal in the list is yourself." );
+
+
+    my $comm_canpost = temp_comm();
+    my $comm_nopost = temp_comm();
+    $u->join_community( $comm_canpost, 1, 1 );
+    $u->join_community( $comm_nopost, 1, 0 );
+
+    note( "  With communities." );
+    $vars = DW::Controller::Entry::init();
+    is( scalar @{$vars->{journallist}}, 2,  "Yourself and one community." );
+    ok( $vars->{journallist}->[0]->equals( $u ), "First journal in the list is yourself." );
+    ok( $vars->{journallist}->[1]->equals( $comm_canpost ), "Second journal in the list is a community you can post to." );
+    ok( ! $vars->{usejournal}, "No usejournal argument." );
+}
+
+note( " Usejournal - init" );
+{
+    my $u = temp_user();
+    LJ::set_remote( $u );
+    my $comm_canpost = temp_comm();
+    my $comm_nopost = temp_comm();
+    $u->join_community( $comm_canpost, 1, 1 );
+    $u->join_community( $comm_nopost, 1, 0 );
+
+    note( "  With usejournal argument (can post)" );
+    my $vars = DW::Controller::Entry::init( { usejournal => $comm_canpost->user } );
+    is( scalar @{$vars->{journallist}}, 1,  "Usejournal." );
+    ok( $vars->{journallist}->[0]->equals( $comm_canpost ), "Only item in the list is usejournal value." );
+    ok( $vars->{usejournal}->equals( $comm_canpost ), "Usejournal argument." );
+
+    note( " checking community security levels ");
+    is( scalar @{$vars->{security}}, 3, "Basic security levels" );
+    is( $vars->{security}->[0]->{label}, LJ::Lang::ml( 'label.security.public2' ), "Public security" );
+    is( $vars->{security}->[0]->{value}, "public", "Public security" );
+    is( $vars->{security}->[1]->{label}, LJ::Lang::ml( 'label.security.members' ), "Members-only security" );
+    is( $vars->{security}->[1]->{value}, "access", "Access-only security" );
+    is( $vars->{security}->[2]->{label}, LJ::Lang::ml( 'label.security.maintainers' ), "Admin-only security" );
+    is( $vars->{security}->[2]->{value}, "private", "Private security" );
+
+    # TODO:
+    # tags ( fetched by JS )
+    # mood, icons, comments, age restriction don't change
+
+    # crosspost: shouldn't show up? or is that another thing to be hidden by JS?
+
+
+    # allow this, because the user can still log in as another user in order to complete the post
+    note( "  With usejournal argument (cannot post)" );
+    $vars = DW::Controller::Entry::init( { usejournal => $comm_nopost->user } );
+    is( scalar @{$vars->{journallist}}, 1,  "Usejournal." );
+    ok( $vars->{journallist}->[0]->equals( $comm_nopost ), "Only item in the list is usejournal value." );
+    ok( $vars->{usejournal}->equals( $comm_nopost ), "Usejournal argument." );
+}
+
+note( "Altlogin - init" );
+{
+    my $u = temp_user();
+    my $alt = temp_user();
+    LJ::set_remote( $u );
+
+    my $vars = DW::Controller::Entry::init( { altlogin => 1 } );
+
+    ok( ! $vars->{remote}, "Altlogin means form has no remote" );
+    ok( ! $vars->{poster}, "\$alt doesn't show up in the form on init" );
+
+    ok( ! $vars->{tags}, "No tags" );
+    ok( ! keys %{$vars->{moodtheme}}, "No mood theme." );
+    ok( ! @{$vars->{icons}}, "No icons." );
+    ok( ! $vars->{defaulticon}, "No default icon." );
+    is( @{$vars->{security}}, 3, "Default security dropdown" );
+    ok( ! @{$vars->{journallist}}, "No journal dropdown" );
+
+    # TODO:
+    # comments
+    # age restriction
+    # crosspost
+    # scheduled
+}
+
+
+my $postdata = {
+    subject => "here is a subject",
+    event   => "here is some event data",
+};
+
+my $postdecoded_bare = {
+    event   => $postdata->{event},
+    subject => undef,
+
+    security    => 'public',
+    allowmask   => 0,
+
+    crosspost_entry => 0,
+
+    props => {
+        taglist => "",
+
+        opt_nocomments      => 0,
+        opt_noemail         => 0,
+        opt_screening       => '',
+
+        opt_preformatted    => 0,
+        opt_backdated       => 0,
+
+        adult_content        => '',
+        adult_content_reason => '',
+    }
+};
+
+note( "Not logged in - post" );
+TODO: {
+    local $TODO = "Handle not logged in (post)";
+}
+
+note( "Not logged in - post (community)" );
+TODO: {
+    local $TODO = "post to a community while not logged in";
+}
+
+sub post_with {
+    my %opts = @_;
+
+    my $remote = temp_user();
+    LJ::set_remote( $remote );
+
+    $opts{event} = $postdata->{event} unless exists $opts{event};
+
+    # if we'd been in a handler, this would have been put into $vars->{formdata}
+    # and automatically converted to Hash::MultiValue. We're not, though, so fake it
+    my $post = Hash::MultiValue->from_mixed( \%opts );
+
+    my %flags;
+    my %auth;
+    %auth = DW::Controller::Entry::_auth( \%flags, $post, $remote, $LJ::SITEROOT );
+
+    my %req;
+    my %decode_status;
+    %decode_status = DW::Controller::Entry::_decode( \%req, $post );
+
+    my $res = DW::Controller::Entry::_save_new_entry( \%req, \%flags, \%auth );
+    delete $req{props}->{unknown8bit}; # TODO: remove this from protocol at some point
+
+    return ( \%req, $res, $remote, \%decode_status );
+}
+
+note( "Logged in - post (bare minimum)" );
+{
+    # only have the event
+    my ( $req, $res, $u, $decode ) = post_with( undef => undef );
+    is_deeply( $req, $postdecoded_bare, "decoded entry form" );
+    is_deeply( $decode, {}, "no errors" );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->subject_orig, '', "subject" );
+    is( $entry->event_orig, $postdata->{event}, "event text" );
+}
+
+note( "Post - subject" );
+{
+    my ( $req, $res, $u, $decode ) = post_with( subject => $postdata->{subject} );
+    is_deeply( $req, { %$postdecoded_bare,
+        subject => $postdata->{subject},
+    } );
+    is_deeply( $decode, {} );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->subject_orig, $postdata->{subject} );
+    is( $entry->event_orig, $postdata->{event} );
+}
+
+note( "Post - lacking required info" );
+{
+    my ( $req, $res, $u, $decode ) = post_with( subject => $postdata->{subject}, event => undef );
+    is_deeply( $req, { %$postdecoded_bare,
+        subject => $postdata->{subject},
+        event   => "",
+    }, "decoded entry form" );
+    is_deeply( $decode,
+        { errors => [ LJ::Lang::ml( "/update.bml.error.noentry" ) ] },
+        "no entry text"
+    );
+
+    is_deeply( $res, {
+        errors  => LJ::Protocol::error_message( 200 ),
+    }, "failed, lacking required arguments" );
+}
+
+note( "Post - security:public" );
+{
+    my ( $req, $res, $u ) = post_with( security => "" );
+    is_deeply( $req, { %$postdecoded_bare,
+        security => "public",
+    }, "decoded entry form" );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->{security}, "public", "Public security" );
+
+
+    ( $req, $res, $u ) = post_with( security => "public" );
+    is_deeply( $req, { %$postdecoded_bare,
+        security => "public",
+    }, "decoded entry form" );
+
+    $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->{security}, "public", "Public security" );
+}
+
+note( "Post - security:access" );
+{
+    my ( $req, $res, $u ) = post_with( security => "access" );
+    is_deeply( $req, { %$postdecoded_bare,
+        security  => "usemask",
+        allowmask => 1,
+    }, "decoded entry form for access-locked entry" );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->{security}, "usemask", "Locked security" );
+}
+
+note( "Post - security:private" );
+{
+    my ( $req, $res, $u ) = post_with( security => "private" );
+    is_deeply( $req, { %$postdecoded_bare,
+        security => "private"
+    }, "decoded entry form for private entry" );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->security, "private", "Private security" );
+}
+
+note( "Post - security:custom" );
+{
+    my ( $req, $res, $u ) = post_with( security => "custom", custom_bit => [1, 2] );
+    is_deeply( $req, { %$postdecoded_bare,
+        security => "usemask",
+        allowmask => 6,
+    }, "decoded entry form for entry with custom security" );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->security, "usemask", "Custom security" );
+    is( $entry->allowmask, 6, "Custom security allowmask" );
+}
+
+note( "Post - security:custom no allowmask" );
+{
+    my ( $req, $res, $u ) = post_with( security => "custom" );
+    is_deeply( $req, { %$postdecoded_bare,
+        security => "usemask",
+        allowmask => 0,
+    }, "decoded entry form for entry with custom security" );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->security, "usemask", "Custom security" );
+    is( $entry->allowmask, 0, "Custom security allowmask" );
+}
+
+note( "Post - security:public, but with allowmask (probably changed their mind)" );
+{
+    my ( $req, $res, $u ) = post_with( security => "public", custom_bit => [1, 2] );
+    is_deeply( $req, { %$postdecoded_bare,
+        security => "public",
+        allowmask => 0,
+    }, "decoded entry form" );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->security, "public", "Public security, not custom" );
+    is( $entry->allowmask, 0, "No custom security allowmask" );
+}
+
+note( "Post - currents" );
+{
+    my ( $req, $res, $u ) = post_with(
+        current_mood => 1,
+        current_mood_other => "etc",
+        current_location => "beside the thing",
+        current_music => "things that go bump in the night"
+    );
+    is_deeply( $req, { %$postdecoded_bare,
+        props => {
+            %{$postdecoded_bare->{props}},
+            current_moodid     => 1,
+            current_mood       => "etc",
+            current_music      => "things that go bump in the night",
+            current_location   => "beside the thing",
+        }
+    }, "decoded entry form with metadata" );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->prop( "current_moodid" ), 1  );
+    is( $entry->prop( "current_mood" ), "etc" );
+    is( $entry->prop( "current_music" ), "things that go bump in the night" );
+    is( $entry->prop( "current_location" ), "beside the thing" );
+    is( $entry->prop( "current_coords" ), undef );
+}
+
+note( "Post - mood_other matches mood with a moodid" );
+{
+    my $moodid = 1;
+    my $mood_other_id = 2;
+    my $mood_other_name = DW::Mood->mood_name( $mood_other_id );
+
+    my ( $req, $res, $u ) = post_with(
+        current_moodid => $moodid,
+        current_mood_other => $mood_other_name
+    );
+    is_deeply( $req, { %$postdecoded_bare,
+        props => { %{$postdecoded_bare->{props}}, current_moodid     => $mood_other_id }
+    }, "decoded entry form with metadata" );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->prop( "current_moodid" ), $mood_other_id );
+    is( $entry->prop( "current_mood" ), undef );
+}
+
+note( "Post - with tags, nothing fancy" );
+{
+    my ( $req, $res, $u ) = post_with(
+        taglist => "foo, bar, baz",
+    );
+    is_deeply( $req, { %$postdecoded_bare,
+        props => { %{$postdecoded_bare->{props}}, taglist     => "foo, bar, baz" }
+    }, "decoded entry form with metadata" );
+
+    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
+    is( $entry->prop( "taglist" ), "foo, bar, baz", "tag list as string prop" );
+    is_deeply( { map { $_ => 1 } $entry->tags }, { map { $_ => 1 } qw( bar baz foo ) },
+            "tag list as parsed array (order doesn't matter)" );
+}
+
+note( "Logged in - post (community)" );
+TODO: {
+    local $TODO = "post to a community";
+}
+
+note( "Altlogin - post" );
+{
+    my $alt = temp_user();
+    my $alt_pass = "abc123!" . rand();
+    $alt->set_password( $alt_pass );
+
+    my ( $req, $res, $remote, $decode ) = post_with(
+        post_as  => "other",
+        username => $alt->user,
+        password => $alt_pass
+    );
+
+    ok( ! $remote->equals( $alt ), "Remote and altlogin aren't the same user" );
+
+    my $entry = LJ::Entry->new( $alt, jitemid => $res->{itemid} );
+    ok( $entry->valid, "valid entry posted to alt (not remote)" );
+
+    my $no_entry = LJ::Entry->new( $remote, jitemid => $res->{itemid} );
+    ok( ! $no_entry->valid, "no such entry (was posted to alt)" );
+
+
+    # TODO: altlogin + usejournal
+    # mix usejournal and postas_usejournal
+}
+
+note( "Altlogin - but changed mind" );
+{
+    my $alt = temp_user();
+    my $alt_pass = "abc123!" . rand();
+    $alt->set_password( $alt_pass );
+
+    # filled in username and password (or perhaps browser autofill)
+    # but selected the "post_as_remote"...
+    my ( $req, $res, $remote, $decode ) = post_with(
+        post_as  => "remote",
+        username => $alt->user,
+        password => $alt_pass
+    );
+
+    ok( ! $remote->equals( $alt ), "Remote and altlogin aren't the same user" );
+
+    my $entry = LJ::Entry->new( $alt, jitemid => $res->{itemid} );
+    ok( ! $entry->valid, "didn't post to alt" );
+
+    my $no_entry = LJ::Entry->new( $remote, jitemid => $res->{itemid} );
+    ok( $no_entry->valid, "posted to remote instead" );
+
+}
+
+
+note( "Editing a draft" );
+TODO: {
+    local $TODO = "Editing a draft"
+}
+
+note( "Editing an existing entry" );
+TODO: {
+    local $TODO = "Editing an existing entry.";
+}
+
+note( "openid - post" );
+TODO: {
+    my $u = temp_user( journaltype => "I" );
+    LJ::set_remote( $u );
+
+    my $vars;
+    $vars = DW::Controller::Entry::init();
+    is( $vars->{abort}, "/update.bml.error.nonusercantpost" );
+}
+
+note( "openid - edit" );
+TODO: {
+    local $TODO = "Editing an existing entry as openid";
+}
+
+
+done_testing();
+
+1;
diff -r 139a07b97016 -r 63e3af5ee420 views/dev/classes.tt
--- a/views/dev/classes.tt	Mon Oct 31 01:22:31 2011 +0800
+++ b/views/dev/classes.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -66,6 +66,7 @@
 <h3>Forms</h3>
 <h4>simple-form</h4>
 <form class='simple-form'>
+<div class='inner'>
     <fieldset>
         <legend><span>Group 1</span></legend>
         <ul>
@@ -158,6 +159,10 @@
     <fieldset class="submit">   
         <input type="submit" value="Submit" />
     </fieldset>
+    <fieldset class="submit destructive">
+        <input type="submit" value="Cancel/Delete/Destroy" />
+    </fieldset>
+</div>
 </form>
 
 <h4>table-form</h4>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry-preview.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry-preview.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,56 @@
+[%# post-preview.tt
+
+Page to preview entries in site skin
+
+Authors:
+    Afuna <coder.dw@afunamatata.com>
+
+This program is free software; you may redistribute it and/or modify it under
+the same terms as Perl itself.  For a copy of the license, please reference
+'perldoc perlartistic' or 'perldoc perlgpl'.
+%]
+
+[%- dw.need_res( "stc/talkpage.css" ) -%]
+
+[%- sections.windowtitle = '.title' | ml( sitenameshort = site.nameshort ) -%]
+
+[%- IF journal -%]
+    <table summary=''><tr valign='middle'>
+    [%- IF icon -%]
+        <td>[% icon %]</td>
+    [%- END -%]
+
+    <td>
+        [%- postername = poster.name | html -%]
+
+        [%- IF journal.is_community -%]
+            [%- "talk.somebodywrote_comm" | ml( realname = postername
+                                                userlink = poster.ljuser_display
+                                                commlink = journal.ljuser_display )
+            -%]
+        [%- ELSE -%]
+            [%- "talk.somebodywrote" | ml(  realname = postername
+                                            userlink = poster.ljuser_display )
+            -%]
+        [%- END -%]
+
+        <br /><span class='time'>@ [% displaydate %]</span>
+    </td>
+
+    </tr></table>
+[%- END -%]
+
+<div id='entry' class='usercontent' style='margin-left: 30px'>
+    [%- currents -%]
+
+    [%- security -%]
+    <div id='entrysubj'>[% subject %]</div>
+    [%- IF security OR subject -%]<br />[%- END -%]
+
+    [%- event -%]
+</div>
+
+<br clear='all' /><hr width='100%' size='2' align='center' />
+
+<div class='highlight-box'><p>[% '.entry.preview_warn_text' | ml %]</p></div>
+
diff -r 139a07b97016 -r 63e3af5ee420 views/entry-preview.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry-preview.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,5 @@
+;; -*- coding: utf-8 -*-
+.entry.preview_warn_text=This is a preview only. To save this entry, close this popup and return to your main browser window.
+
+.title=[[sitenameshort]]: Entry Preview (Unsaved)
+
diff -r 139a07b97016 -r 63e3af5ee420 views/entry-success.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry-success.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,28 @@
+[%- sections.title = 'success' | ml -%]
+
+[%- IF warnings.size > 0 -%]
+    [%- FOREACH warning IN warnings -%]
+        <div class="message-box [% warning.type %]-box">[%- warning.message -%]</div>
+    [%- END -%]
+[%- END -%]
+
+<p>[% poststatus %]</p>
+
+[%- IF crossposts.size > 0 -%]
+<ul>
+    [%- FOREACH crosspost IN crossposts -%]
+        <li [% IF crosspost.status == "error" -%] class="error-box" [%- END -%]>
+            [%- crosspost.text -%]
+        </li>
+    [%- END -%]
+</ul>
+[%- END -%]
+
+[%- IF links.size > 0 -%]
+<p>[%- "/update.bml.success.links" | ml -%]</p>
+<ul>
+    [%- FOREACH link IN links -%]
+        <li><a href="[% link.url %]">[% link.text %]</a></li>
+    [%- END -%]
+</ul>
+[%- END -%]
diff -r 139a07b97016 -r 63e3af5ee420 views/entry.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,286 @@
+[%# entry.tt
+
+Page to post and edit entries
+
+Authors:
+    Afuna <coder.dw@afunamatata.com>
+
+This program is free software; you may redistribute it and/or modify it under
+the same terms as Perl itself.  For a copy of the license, please reference
+'perldoc perlartistic' or 'perldoc perlgpl'.
+%]
+
+[%- CALL dw.active_resource_group( "jquery" ) -%]
+
+[% dw.need_res(
+    "stc/postform.css",
+    "stc/postform-resize.css"
+) %]
+
+[% dw.need_res( { group => "jquery" },
+    # jquery ui
+    "js/jquery/jquery.ui.core.js"
+    "js/jquery/jquery.ui.widget.js"
+
+    "js/jquery/jquery.ui.datepicker.js"
+    "js/jquery/jquery.ui.dialog.js"
+
+    "stc/jquery/jquery.ui.core.css"
+    "stc/jquery/jquery.ui.datepicker.css"
+    "stc/jquery/jquery.ui.dialog.css"
+
+
+    # jquery utility plugins
+    #"js/jquery.ultrafocus.js"
+    "js/jquery.radioreveal.js"
+
+    # icons
+    "js/jquery.iconrandom.js"
+
+    # tags
+    "js/jquery.vertigro.js"
+    "js/jquery.tagselector.js"
+    "js/jquery/jquery.ui.autocomplete.js"
+    "js/jquery.autocompletewithunknown.js"
+    "stc/jquery.tagselector.css"
+    "stc/jquery/jquery.ui.autocomplete.css"
+    "stc/jquery.autocompletewithunknown.css"
+
+    # crosspost
+    "js/md5.js"
+    "js/jquery.crosspost.js"
+
+    # page-specific
+    "js/jquery.postform.js"
+) %]
+
+[% IF remote && remote.can_use_userpic_select;
+dw.need_res( { group => "jquery" },
+    "js/jquery.iconselector.js"
+    "stc/jquery.iconselector.css"
+); END %]
+
+[% sections.title = '.title' | ml %]
+[% sections.contentopts = '' %]
+
+[% sections.head = BLOCK %]
+<meta name="viewport" content="width=device-width" />
+
+[% dw.need_res( "stc/postform-minimal.css" ) %]
+
+[% IF show_unimplemented %]
+<style type="text/css">
+.unimplemented {
+  outline: 2px orange solid;
+  opacity: 0.6;
+}
+</style>
+[% ELSE %]
+<style type="text/css">
+.unimplemented {
+    opacity: 0.2;
+    display: none;
+}
+.unimplemented:hover {
+    opacity: 1;
+}
+</style>
+[% END %]
+
+[%- chalresp_js -%]
+
+<script type="text/javascript">
+var postFormInitData = new Object();
+postFormInitData.icons = [
+    [%- FOREACH icon = icons %]
+        { 'src': '[% icon.userpic.url %]', 'alt': [% icon.userpic.description | js %] }
+        [%- UNLESS loop.last %],[% END -%]
+    [% END %]
+];
+
+postFormInitData.moodpics = {
+    [%- FOREACH mood = moodtheme.pics.pairs %]
+        [% mood.key | js %] : [ [% mood.value.name | js %], [% mood.value.pic | js %], [% mood.value.width | js %], [% mood.value.height | js %] ]
+        [%- UNLESS loop.last %],[% END -%]
+    [% END %]
+};
+
+postFormInitData.panels = {
+    "show" : {
+        [%- FOREACH panel = panels.show.pairs -%]
+            [%- panel.key | js -%] : [%- panel.value ? "true" : "false" -%]
+            [%- UNLESS loop.last %],[% END -%]
+        [%- END -%]
+    }
+};
+
+postFormInitData.minAnimation = [% min_animation ? "true" : "false" %];
+</script>
+[% END %]
+
+<div class="message-box ui-state-highlight">[% ".beta.on" | ml( aopts = "href='$LJ::SITEROOT/betafeatures'", user = betacommunity.ljuser_display ) %]</div>
+
+[%- IF warnings.size > 0 -%]
+    [%- FOREACH warning IN warnings -%]
+        <div class="message-box [% warning.type %]-box">[%- warning.message -%]</div>
+    [%- END -%]
+[%- END -%]
+
+[% IF error_list %]
+<div class='error-box message-box'>
+<div class='title'>[% '.error.header' | ml %]</div>
+<ul class='error-list'>
+    [% FOREACH error = error_list %]
+        <li>[% error %] </li>
+    [% END %]
+</ul>
+</div>
+[% END %]
+
+[%- IF spellcheck.did_spellcheck -%]
+    <h2>[% 'entryform.spellchecked' | ml %]</h2>
+    [%-
+    IF spellcheck.results;
+         spellcheck.results;
+    ELSE;
+        'entryform.spellcheck.noerrors' | ml;
+    END -%]
+
+    [%# indicate where the spellcheck bit ends, and the entry form begins #%]
+    <h2>[% 'entryform.form' | ml %]</h2>
+[%- END -%]
+
+<form method="POST" id="post_entry" action="" class="
+        [%- formwidth == "narrow" ? "entry-partial-width" : "entry-full-width" -%]
+        [%- " $vclass" -%]">
+    <input type="hidden" id="nojs" value="1" name="nojs" />
+
+    [% IF login_chal %]
+        <input type="hidden" id="login_chal" name="chal" value="[%login_chal%]" />
+        <input type="hidden" id="login_response" name="response" value="" />
+    [% END %]
+
+    [%- dw.form_auth -%]
+
+    <div id="primary"><!-- Start main column sub & entry -->
+        <div id="current_entry">
+            <fieldset>
+                <legend></legend>
+
+                <!-- FIXME: TODO
+                <div class="permalink unimplemented">permalink: <span class="url">http://long-username-is-long.dreamwidth.org/12345.html</span></div>
+                -->
+
+                <!-- TODO make this only take up one tab area? -->
+                <div class="toolbar">
+                    <div id="main-tools" class='subtoolbar'>
+                        <a href="http://www.dreamwidth.org/poll/create">Create Poll</a>
+                        [%- preview_label = 'talk.btn.preview' | ml;
+                            form.submit( value = preview_label
+                                         name = "action:preview"
+                                         id = "preview_button" )
+                        %]
+
+                        [%- IF can_spellcheck;
+                            spellcheck_label = 'entryform.spellcheck' | ml;
+                            form.submit( value = spellcheck_label
+                                         name = "action:spellcheck"
+                                         id = "spellcheck_button" );
+                        END -%]
+
+                        <a href="#" class="unimplemented"><img src="[% site.imgroot%]/silk/site/help.png" alt="Help for valid HTML tags" title="Help for valid HTML tags" width="16" height="16"></a>
+                        <a href="[%site.root%]/entry/options" id="post_options"><img src="[% site.imgroot%]/silk/site/cog.png" alt="Edit entry form settings" title="Edit entry form settings" width="16" height="16"></a>
+                    </div>
+
+                    <div id="settings-tools" class='subtoolbar' aria-live="polite">
+                    <!-- TODO:
+                    autoformat
+                    supported html...
+                    -->
+                    </div>
+
+                    <div id="plaintext-tools" class='subtoolbar'>
+                        <!-- TODO: needs to be JS only -->
+                        <input type='button' value='Insert image' class='unimplemented' />
+                        <input type='button' value='Embed media' class='unimplemented' />
+                        <input type="button" value="Use Rich Text Mode" class='unimplemented' />
+                    </div>
+                </div>
+
+                <div class='subject-container'>
+                    [%- placeholder = ".subject.placeholder" | ml;
+                        title = ".subject.label" | ml;
+                        form.textbox( label = title
+                            id = "subject"
+                            name = "subject"
+
+                            maxlength = "255"
+                            size = "50"
+
+                            labelclass = "invisible"
+
+                            placeholder = placeholder
+                            title = title
+                    ) -%]
+                </div>
+
+                <div class='event-container'>
+                    [%- placeholder = ".event.placeholder" | ml -%]
+                    [%- title = ".event.label" | ml -%]
+                    [%- form.textarea( label = title
+                        id = "event"
+                        name = "event"
+
+                        cols = "50"
+                        rows = "20"
+                        wrap = "soft"
+
+                        labelclass = "invisible"
+
+                        placeholder = placeholder
+                        title = title
+                    ) -%]
+                </div>
+            </fieldset>
+        </div>
+
+    </div> <!-- End main column sub & entry -->
+
+    [%- BLOCK column %]
+        [% FOREACH component = components %]
+        <div class='component [% UNLESS panels.show.$component -%] inactive_component [%- END -%]' id='[% component %]_component'>
+        [%- CALL dw.ml_scope( "/entry/${component}.tt" );
+            INCLUDE "entry/${component}.tt";
+            CALL dw.ml_scope( "/entry.tt" )
+         %]
+        </div>
+        [%- END -%]
+    [% END -%]
+
+    <div id="secondary" class='column'> <!-- Start column of components -->
+        [% PROCESS column components = panels.order.shift %]
+    </div> <!-- end second column of components -->
+
+    <div id="tertiary"> <!-- Start components below entry field -->
+        <div class="column">
+            [% PROCESS column components = panels.order.shift %]
+        </div>
+
+        <div class="column tertiary-right">
+            [% PROCESS column components = panels.order.shift %]
+        </div>
+    </div>
+
+    <div class="submit action-bar">
+        <span id="actions">
+            <input type="submit" name="action:post" id="submit_entry" value="Post Entry" />
+        </span>
+
+<!--    TODO:
+        <span class="otheractions unimplemented">
+            <input type='submit' name="action:delete" value='Delete' />
+        </span>
+-->
+    </div>
+</form>
+
diff -r 139a07b97016 -r 63e3af5ee420 views/entry.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,16 @@
+;; -*- coding: utf-8 -*-
+.beta.on=You are beta-testing the new Create Entries page. If you notice any problems, please report them in [[user]]. To turn off beta testing,  visit the <a [[aopts]]>beta features</a> page.
+
+.beta.off=You need to enable beta testing to use the new Create Entries page. <a [[aopts]]>Enable beta testing?</a>.
+
+.error.header=Error
+
+.event.label=Entry
+
+.event.placeholder=Your entry text...
+
+.subject.label=Subject
+
+.subject.placeholder=Subject
+
+.title=Create Entries
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/access.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/access.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,39 @@
+    <fieldset>
+        <h3>[% ".header" | ml %]</h3>
+        <div class="inner">
+        <div id="security_group">
+            [%- securitylist = [];
+                FOREACH level IN security;
+                    securitylist.push( level.value, level.label );
+                END;
+            -%]
+            [%- label = ".label" | ml;
+            form.select( label = "$label:"
+                name = "security"
+                id = "security"
+
+                class = "select"
+
+                items = securitylist
+            ) -%]
+        </div>
+
+        [% IF customgroups.size > 0 %]
+        <div id="custom_access_groups">
+          <h4>[% ".header.custom" | ml %]</h4>
+          <ul>
+            [% FOREACH group IN customgroups %]
+                <li>[%- form.checkbox( label = group.label
+                            name = "custom_bit"
+                            id = "custom_bit_$group.value"
+
+                            labelclass = "checkboxlabel"
+
+                            value = group.value
+                ) -%]</li>
+            [% END %]
+          </ul>
+        </div>
+        [% END %]
+        </div>
+    </fieldset>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/access.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/access.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,6 @@
+;; -*- coding: utf-8 -*-
+.header=Security Settings
+
+.header.custom=Custom Access Groups
+
+.label=Security Level
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/age_restriction.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/age_restriction.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,42 @@
+  <fieldset>
+    <h3>[% ".header" | ml %]</h3>
+  <div class='inner'>
+  <div class="age_level_reason">
+    <p>
+        [%- levelselect = [];
+            FOREACH opt IN [
+                ""              ".option.adultcontent.default"
+                "none"          ".option.adultcontent.none"
+                "discretion"    ".option.adultcontent.discretion"
+                "restricted"    ".option.adultcontent.restricted" ];
+
+                IF loop.count % 2 == 0;
+                    opt = opt | ml;
+                END;
+
+                levelselect.push( opt );
+            END
+        -%]
+        [%- label = ".label.age_restriction" | ml;
+        form.select( label = "$label:"
+            name = "age_restriction"
+            id = "age_restriction"
+
+            class = "select"
+
+            items = levelselect
+        ) -%]
+    </p>
+    <p>
+        [%- label = ".label.age_restriction_reason" | ml;
+        form.textbox( label = "$label:"
+            name = "age_restriction_reason"
+            id = "age_restriction_reason"
+
+            size = "30"
+            maxlength = "255"
+        ) -%]
+    </p>
+  </div>
+  </div>
+  </fieldset>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/age_restriction.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/age_restriction.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,14 @@
+;; -*- coding: utf-8 -*-
+.header=Age Restriction
+
+.label.age_restriction=Age Restriction
+
+.label.age_restriction_reason=Reason
+
+.option.adultcontent.default=Journal Default
+
+.option.adultcontent.discretion=Viewer Discretion Advised
+
+.option.adultcontent.none=No Age Restriction
+
+.option.adultcontent.restricted=Age 18+
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/comments-new.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/comments-new.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,42 @@
+  <fieldset class="comments_settings">
+    <h3>Comment Settings</h3>
+  <div class='inner'>
+  <p>
+    <label class='unimplemented' for="comment_email">Email Comments:</label>
+    <select class='unimplemented select' name="comment_email" id="comment_email" class="select">
+      <option value="" selected="selected">Journal Default</option>
+      <option value="yes">Yes</option>
+      <option value="no">No</option>
+    </select>
+  </p>
+  <p>
+    <!-- TODO: also tweak "Enable comments" under the privacy tab: remove no one, rename to "Commenting Security" -->
+    <label class='unimplemented' for="comment_permissions">Allow from:</label>
+    <select class='unimplemented select' name="comment_permissions" id="comment_permissions" class="select">
+      <option value="" selected="selected">Journal Default</option>
+      <option value="anyone">Anyone</option>
+      <option value="registered">Registered Users</option>
+      <option value="access">Access List Only</option>
+    </select>
+  </p>
+  <p>
+    <label class='unimplemented' for="comment_enabled">Commenting is:</label>
+    <select class='unimplemented select' name="comment_enabled" id="comment_enabled" class="select">
+      <option value="" selected="selected">Journal Default</option>
+      <option value="enabled">Enabled</option>
+      <option value="closed">Closed</option>
+      <option value="hidden">Hidden</option>
+    </select>
+  </p>
+  <p>
+    <label class='unimplemented' for="comment_screening">Screen from:</label>
+    <select class='unimplemented select' name="comment_screening" id="comment_screening" class="select">
+      <option value="" selected="selected">Journal Default</option>
+      <option value="none">No Screening</option>
+      <option value="anonymous">Anonymous Only</option>
+      <option value="nonaccess">Non-Access List</option>
+      <option value="all">All Comments</option>
+    </select>
+  </p>
+  </div>
+  </fieldset>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/comments.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/comments.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,37 @@
+[%# old implementation for comments just to show people where things are
+; will be phased out for new implementation %]
+  <fieldset class="comments_settings">
+    <h3>[% ".header" | ml %]</h3>
+  <div class='inner'>
+  <p>
+    [%- form.select( label = "Comment Settings:"
+        name = "comment_settings"
+        id = "comment_settings"
+
+        class ="select"
+
+        items = [
+            ""              "Journal Default"
+            "nocomments"    "Disabled"
+            "noemail"       "Don't Email"
+        ]
+    ) -%]
+  </p>
+  <p>
+    [%- form.select( label = "Screening:"
+        name = "opt_screening"
+        id = "opt_screening"
+
+        class = "select"
+
+        items = [
+            ""      "Journal Default"
+            "N"     "Disabled"
+            "R"     "Anonymous Only"
+            "F"     "Non-access List"
+            "A"     "All Comments"
+        ]
+    ) -%]
+  </p>
+  </div>
+  </fieldset>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/comments.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/comments.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,1 @@
+.header=Comment Settings
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/crosspost.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/crosspost.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,62 @@
+[% IF remote %]
+  <fieldset>
+    <h3>[% ".header" | ml %]</h3>
+    <div class="inner">
+
+    <span class="crosspost-settings">
+        <a href="[% crosspost_url %]">[% ".settings.link" | ml %]</a>
+    </span>
+
+    [% IF crosspostlist.size > 0 %]
+
+        [%- crosspost_entry_label = ".label.crosspost_entry" | ml;
+        form.checkbox( label = crosspost_entry_label
+            name ="crosspost_entry"
+            id = "crosspost_entry"
+
+            labelclass = "checkboxlabel"
+
+            value = 1
+            default = crosspost_entry
+        ) -%]
+
+        <h4>[% ".header.accounts" | ml %]</h4>
+        <input type="text" name="" value="chrome autocomplete fix; ignore" style="display: none"/>
+        <ul id="crosspost_accounts">
+            [% crosspost_password_label = ".label.password" | ml %]
+
+            [% FOREACH account IN crosspostlist %]
+                <li>
+                [%- form.checkbox( label = account.name
+                    name = "crosspost"
+                    id = "crosspost_$account.id"
+
+                    labelclass = "checkboxlabel"
+
+                    value = account.id
+                    default = account.selected
+                ) -%]
+
+                [% IF account.need_password %]
+                    <div class="crosspost_password_container" id="crosspost_password_container_[% account.id %]">
+                    [%- form.password( label = "$crosspost_password_label:"
+                            name = "crosspost_password_$account.id"
+                            id = "crosspost_password_$account.id"
+
+                            class = "crosspost_password"
+                            labelclass="checkboxlabel"
+                    ) -%]
+
+                        <div class="crosspost_password_status" aria-live="imperative"></div>
+                        [%# we don't want these to be carried over between posts %]
+                        <input type="hidden" name="crosspost_chal_[%account.id%]" id="crosspost_chal_[%account.id%]" class="crosspost_chal" />
+                        <input type="hidden" name="crosspost_resp_[%account.id%]" id="crosspost_resp_[%account.id%]" class="crosspost_resp" />
+                    </div>
+                [% END %]
+                </li>
+              [% END %]
+        </ul>
+      [% END %]
+      </div>
+    </fieldset>
+[% END %]
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/crosspost.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/crosspost.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,10 @@
+;; -*- coding: utf-8 -*-
+.header=Crosspost
+
+.header.accounts=Crosspost Accounts
+
+.label.crosspost_entry=Crosspost this entry
+
+.label.password=Password
+
+.settings.link=go to settings
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/currents.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/currents.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,51 @@
+  <fieldset>
+    <h3>[% ".header" | ml %]</h3>
+    <div class='inner'>
+      <p>
+        [%- moodselect = [];
+            FOREACH mood IN moods;
+                moodselect.push( mood.id, mood.name );
+            END
+        -%]
+        [%- label = ".label.current_mood" | ml;
+        form.select( label = "$label:"
+            name = "current_mood"
+            id = "current_mood"
+
+            class = "select"
+
+            items = moodselect
+        ) -%]
+      </p>
+      <p>
+        [%- label = ".label.current_mood_other" | ml;
+        form.textbox( label = "$label:"
+            name = "current_mood_other"
+            id = "current_mood_other"
+
+            size = "20"
+            maxlength = "30"
+         ) %]
+      </p>
+      <p>
+        [%- label = ".label.current_music" | ml;
+        form.textbox( label = "$label:"
+            name = "current_music"
+            id = "current_music"
+
+            size="20"
+            maxlength="80"
+        ) %]
+      </p>
+      <p>
+        [%- label = ".label.current_location" | ml;
+        form.textbox( label = "$label:"
+            name = "current_location"
+            id = "current_location"
+
+            size = "20"
+            maxlength = "60"
+        ) %]
+      </p>
+    </div>
+  </fieldset>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/currents.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/currents.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,10 @@
+;; -*- coding: utf-8 -*-
+.header=Currents
+
+.label.current_location=Location
+
+.label.current_mood=Mood
+
+.label.current_mood_other=Custom Mood
+
+.label.current_music=Music
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/displaydate.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/displaydate.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,93 @@
+
+  <fieldset>
+    <h3>[% ".header" | ml %]</h3>
+    <div class='inner'>
+      <div class='time_container' id="entrytime_container">
+        [%- form.hidden(
+            name  = "trust_datetime"
+            id    = "trust_datetime"
+            value = displaydate.trust_initial   # FIXME: do this in the controller, rather than here?
+        ) -%]
+
+        [%- entrytime_title = ".title.entrytime" | ml( example = "$displaydate.year-01-30" );
+        form.textbox(
+            name = "entrytime"
+            id = "entrytime"
+
+            maxlength = "10"
+            size = "10"
+
+            title = entrytime_title
+            default = "$displaydate.year-$displaydate.month-$displaydate.day"
+        ) -%]
+
+        [%- entrytime_title = ".title.entrytime_hr" | ml;
+        form.textbox(
+            name = "entrytime_hr"
+            id = "entrytime_hr"
+
+            maxlength = "2"
+            size = "2"
+            class = "time_hr"
+
+            title = entrytime_title
+            default = displaydate.hour
+        ) ~%]
+        :
+        [%- entrytime_title = ".title.entrytime_min" | ml;
+        form.textbox(
+            name = "entrytime_min"
+            id = "entrytime_min"
+
+            maxlength = "2"
+            size = "2"
+            class = "time_min"
+
+            title = entrytime_title
+            default = displaydate.minute
+        ) -%]
+
+        [% # update year so it doesn't look dated
+           # keep the month/hours, etc static because the example was chosen to avoid ambiguity %]
+        <div class="dateformat">(e.g. [% displaydate.year %]-01-30 23:59)</div>
+      </div>
+
+      <div class="displaydate_options">
+        <p>
+        [%- autoupdate_label = ".label.autoupdate" | ml;
+        form.checkbox( label = autoupdate_label
+            name ="update_displaydate"
+            id = "entrytime_auto_update"
+
+            labelclass = "radiolabel"
+
+            value = "1"
+        ) -%]
+        </p>
+        <p>
+        [%- stickyentry_label = ".label.sticky" | ml;
+        form.checkbox( label = stickyentry_label
+            name ="sticky_entry"
+            id = "sticky_entry"
+
+            class = "unimplemented"
+            labelclass = "unimplemented radiolabel"
+
+            value = "1"
+        ) -%]
+        </p>
+        <p>
+        [%- dateoutoforder_label = ".label.dateoutoforder" | ml;
+        form.checkbox( label = dateoutoforder_label
+            name ="entrytime_outoforder"
+            id = "entrytime_outoforder"
+
+            labelclass = "radiolabel"
+
+            value = "1"
+        ) -%]
+        </p>
+      </div>
+
+    </div>
+  </fieldset>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/displaydate.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/displaydate.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,14 @@
+;; -*- coding: utf-8 -*-
+.header=Display Date
+
+.label.autoupdate=Use the time when entry is posted
+
+.label.dateoutoforder=Don't show on Reading Pages (allows dating out of order)
+
+.label.sticky=Make sticky (future option?)
+
+.title.entrytime=displayed entry date; example [[example]]
+
+.title.entrytime_hr=hours; 24-hour time
+
+.title.entrytime_min=minutes
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/icons.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/icons.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,40 @@
+[% IF remote %]
+<fieldset>
+  <h3>[% ".header" | ml %]</h3>
+
+  <div class='inner'>
+    <div id='icon_preview'>
+      <div class="icon [% IF icons.size==0 %] noicon [% END %]">
+        [% IF icons.size > 0 %]
+            [% IF defaulticon %]
+                <img id="icon_preview_image" src="[% defaulticon.url | url %]" alt="[% defaulticon.description | html %]" />
+            [% END %]
+        [% ELSE %]
+            <a href='[% site.root %]/editicons'>[% 'entryform.userpic.upload' | ml %]</a>
+        [% END %]
+        </div>
+    </div>
+
+    [%- IF icons.size > 0 -%]
+        [%- iconselect = [] -%]
+        [%- FOREACH icon IN icons -%]
+            [%- IF icon.keyword.defined -%]
+                [%- iconselect.push( icon.keyword, icon.keyword ) -%]
+            [%- ELSE -%]
+                [%- defaulttext = ".keyword.default" | ml -%]
+                [%- iconselect.push( "", defaulttext ) -%]
+            [% END %]
+        [% END %]
+
+        [%- form.select(
+            name = "icon"
+            id = "iconselect"
+
+            class = "select"
+
+            items = iconselect
+        ) -%]
+    [% END %]
+  </div>
+</fieldset>
+[% END %]
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/icons.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/icons.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,4 @@
+;; -*- coding: utf-8 -*-
+.header=Icon
+
+.keyword.default=(default)
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/journal.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/journal.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,99 @@
+ <fieldset>
+   <h3>[% ".header" | ml %]</h3>
+   <div class="inner">
+
+   <div id="post_as">
+   [% IF remote %]
+    <fieldset>
+    <legend><span>[% ".label.post_as" | ml %]:</span></legend>
+        [%-
+        form.radio( label = remote.user
+            name = "post_as"
+            id = "post_as_remote"
+
+            labelclass = "radiolabel"
+
+            value = "remote"
+            default = ( post_as == "remote" )
+        ) -%]
+        [%- post_as_other_label = ".label.post_as_other" | ml;
+        form.radio( label = post_as_other_label
+            name = "post_as"
+            id = "post_as_other"
+
+            labelclass = "radiolabel"
+
+            value = "other"
+            default = ( post_as == "other" )
+        ) -%]
+    </fieldset>
+   [% ELSE %]
+        [%- form.hidden(
+            name = "post_as"
+            id = "post_as_other"
+
+            value = "other"
+        ) -%]
+   [% END %]
+   </div>
+
+   [% IF remote %]
+   <div id="post_to"  class="posting_settings">
+       <label for="usejournal">[%- ".label.post_to" | ml %]:</label>
+        [%- IF journallist.size > 1 %]
+            [%-
+                journalselect = [];
+                FOREACH journal IN journallist;
+                    IF journal.equals( remote );
+                        journalselect.push( "", journal.user );
+                    ELSE;
+                        journalselect.push( journal.user, journal.user );
+                    END;
+                END
+            -%]
+
+            [% form.select(
+                name = "usejournal"
+                id = "usejournal"
+
+                class = "select"
+
+                items = journalselect
+            ) -%]
+        [% ELSE %]
+            [% journallist.first.ljuser_display%]</span>
+            [% form.hidden( name = "usejournal", id = "usejournal", value = journallist.first.user ) %]
+        [% END %]
+   </div>
+   [% END %]
+
+   <fieldset id="post_login" class="posting_settings">
+    <legend>[% ".header.post_as" | ml %]</legend>
+       <ul>
+       <li>
+        [%- postas_username_label = ".label.post_as_username" | ml;
+        form.textbox( label = "$postas_username_label:"
+            name = "username"
+            id = "post_username"
+        ) -%]
+       </li>
+       <li>
+        [%- postas_password_label = ".label.post_as_password" | ml;
+        form.password( label = "$postas_password_label:"
+            name = "password"
+            id = "password"
+        ) -%]
+       </li>
+       <li>
+            <label for="postas_usejournal">[% ".label.post_to" | ml %]:</label>
+            [% IF usejournal %]
+                [% usejournal.ljuser_display %]
+            [% ELSE %]
+                [% form.textbox( name = "postas_usejournal", id = "postas_usejournal" ) %]
+            [% END %]
+       </li>
+       </ul>
+   </fieldset>
+
+   </div>
+ </fieldset>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/journal.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/journal.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,14 @@
+;; -*- coding: utf-8 -*-
+.header=Journal
+
+.header.post_as=Posting as User
+
+.label.post_as=Post as
+
+.label.post_as_other=another user
+
+.label.post_as_password=Password
+
+.label.post_as_username=Username
+
+.label.post_to= Post to
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/options.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/options.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,118 @@
+[%# post/options.tt
+
+Page to edit options for the post entry page
+
+Authors:
+    Afuna <coder.dw@afunamatata.com>
+
+This program is free software; you may redistribute it and/or modify it under
+the same terms as Perl itself.  For a copy of the license, please reference
+'perldoc perlartistic' or 'perldoc perlgpl'.
+%]
+
+[%- sections.windowtitle = ".title" | ml -%]
+
+[% dw.need_res( "stc/simple-form.css" ) %]
+
+<form action="/entry/options" method="post" id="post-options" class="simple-form">
+
+[% IF error_list %]
+<div class='error-box message-box'>
+<div class='title'>[% '.error.header' | ml %]</div>
+<ul class='error-list'>
+    [% FOREACH error = error_list %]
+        <li>[% error %] </li>
+    [% END %]
+</ul>
+</div>
+[% END %]
+
+[% dw.form_auth %]
+
+<div class='inner'>
+<fieldset>
+    <legend><span>[%sections.windowtitle%]</span></legend>
+
+    <ul>
+    <li class="odd">
+        <fieldset class="radio-group" id='entry_field_width'>
+            <legend><span>[% '.width.header' | ml %]</span></legend>
+            <ul>
+                <li>
+                    [%- width_full_label = '.width.label.full' | ml;
+                        form.radio(
+                            label = width_full_label
+                            id = 'entry_field_full_width'
+                            name = 'entry_field_width'
+                            value = 'F'
+                        )
+                    -%]
+                </li>
+                <li>
+                    [%- width_partial_label = '.width.label.partial' | ml;
+                        form.radio(
+                            label = width_partial_label
+                            id = 'entry_field_partial_width'
+                            name = 'entry_field_width'
+                            value = 'P'
+                        )
+                    -%]
+                <p class='note'>[% '.width.note' | ml %]</p>
+                </li>
+            </ul>
+        </fieldset>
+    </li>
+    <li class="even">
+        <fieldset class="checkbox-group">
+            <legend><span>[% '.panels.header' | ml %]</span></legend>
+            [%- IF panels.size > 0 -%]
+            <ul class='panels-list'>
+                [%- FOREACH panel IN panels -%]
+                <li>
+                    [% panel_label = panel.label_ml | ml;
+                        form.checkbox(
+                            label = panel_label
+                            name = panel.name
+                            id = panel.id
+                            value = panel.panel_name
+                        ) %]
+                </li>
+                [%- END -%]
+            </ul>
+            [%- END -%]
+        </fieldset>
+    </li>
+    <li class="odd">
+        <fieldset>
+            <legend><span>[% '.animations.header' | ml %]</span></legend>
+            <ul>
+                <li>
+                [%- animations_label = '.animations.label' | ml;
+                    form.checkbox(
+                        label = animations_label
+                        id = 'minimal_animations'
+                        name = 'minimal_animations'
+                        value = '1'
+                    ) -%]
+                </li>
+            </ul>
+        </fieldset>
+    </li>
+    </ul>
+</fieldset>
+
+<fieldset class='submit'>[% submit_label = ".submit" | ml; form.submit( value = submit_label ) %]</fieldset>
+
+</div>
+</form>
+
+
+[%- IF use_js -%]
+<link rel="stylesheet" type="text/css" href="[%site.statroot%]/simple-form.css"">
+<link rel="stylesheet" type="text/css" href="[%site.statroot%]/jquery.postoptions.css"">
+
+<script type="text/javascript" src="[%site.jsroot%]/jquery/jquery.ui.mouse.min.js"></script>
+<script type="text/javascript" src="[%site.jsroot%]/jquery/jquery.ui.sortable.min.js"></script>
+
+<script type="text/javascript" src="[%site.jsroot%]/jquery.postoptions.js"></script>
+[%- END -%]
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/options.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/options.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,19 @@
+.animations.header=Simplify effects
+
+.animations.label=Use minimal animations for the panels as they expand/collapse
+
+.error.header=Error
+
+.panels.header=Show panels
+
+.submit=Save
+
+.title=Entry Form Options
+
+.width.header=Entry field
+
+.width.label.full=Entry text takes up full page width
+
+.width.label.partial=Column alongside entry text
+
+.width.note=Side column only available in wider windows.
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/scheduled.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/scheduled.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,20 @@
+<fieldset>
+  <h3>Scheduled Publishing</h3>
+    <div class="inner">
+    <div class='time_container' id="publishingtime_container">
+      <input type="text" name="publishingtime" id="publishingtime" value="2010-04-10" maxlength="10" size="10" />
+      <input type="text" name="publishingtime_hr" id="publishingtime_hr" value="03" maxlength="2" size="2" class='time_hr' />:<input type="text" name="publishingtime_min" id="publishingtime_min" value="15" maxlength="2" size="2" class='time_min' />
+      <div class="dateformat">(e.g. 2010-01-30 23:45)</div>
+    </div>
+
+    <div class="recurring_container">
+      <label for="recurring_period">Recurring:</label>
+      <select name="recurring_period" class="select" id="recurring_period">
+        <option value="never">never</option>
+        <option value="day">every day</option>
+        <option value="week">every week</option>
+        <option value="month">every month</option>
+      </select>
+    </div>
+  </div>
+</fieldset>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/status.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/status.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,17 @@
+  <fieldset>
+    <h3>Status</h3>
+
+    <div class='inner'>
+      <div class="status-notice">
+        <span class="status-label">Status:</span> Published
+         <div class="revert"><a href="#">Revert to draft</a></div>
+      </div>
+      <div class="savedraft">
+       <input type='submit' name="action:save" value='Save' class="save" />
+       <input type='submit' name="action:preview" value='Preview' class="preview"/>
+      </div>
+
+
+    </div>
+
+  </fieldset>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/tags.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/tags.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,17 @@
+  <fieldset>
+    <h3>[% ".header" | ml %]</h3>
+    <div class="inner">
+        [%- label = ".label.tags" | ml;
+        form.textarea( label = "$label:"
+            id = "taglist"
+            name = "taglist"
+
+            cols = "20"
+            rows = "1"
+        ) -%]
+
+      [% IF journalu %]
+          <a id="taglist_link" href="[% journalu.journal_base %]/tag/">[% ".link.tagspage" | ml %] </a>
+      [% END %]
+    </div>
+  </fieldset>
diff -r 139a07b97016 -r 63e3af5ee420 views/entry/tags.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/entry/tags.tt.text	Mon Oct 31 13:23:18 2011 +0800
@@ -0,0 +1,6 @@
+;; -*- coding: utf-8 -*-
+.header=Tags
+
+.label.tags=Tags (comma separated)
+
+.link.tagspage=go to journal tags
diff -r 139a07b97016 -r 63e3af5ee420 views/success.tt
--- a/views/success.tt	Mon Oct 31 01:22:31 2011 +0800
+++ b/views/success.tt	Mon Oct 31 13:23:18 2011 +0800
@@ -1,2 +1,10 @@
 [%- sections.title = 'success' | ml -%]
 <p>[% message %]</p>
+
+[% IF links %]
+<ul>
+    [% FOREACH link IN links %]
+        <li><a href="[% link.url %]">[% link.text %]</a></li>
+    [% END %]
+</ul>
+[% END %]
--------------------------------------------------------------------------------
mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)

[staff profile] mark 2011-10-31 07:11 am (UTC)(link)
I cannot tell you how excited I am.
denise: Image: Me, facing away from camera, on top of the Castel Sant'Angelo in Rome (Default)

[staff profile] denise 2011-10-31 07:15 am (UTC)(link)
EVEN BETTER NEWS: we're pushing it in an hour or two.

:) :) :) :) :) :) :) :) :) :) :) :) :)