[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
hope who
transformed the original very rough drafts 90% of the way into the version
seen here today.
Patch by
fu.
Files modified:
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
transformed the original very rough drafts 90% of the way into the version
seen here today.
Patch by
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 %]
--------------------------------------------------------------------------------

no subject
no subject
:) :) :) :) :) :) :) :) :) :) :) :) :)