[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
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
transformed the original very rough drafts 90% of the way into the version
seen here today.
Patch by
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
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
:) :) :) :) :) :) :) :) :) :) :) :) :)