[Jifty-commit] r4198 - in apps/Skedjul: . bin doc etc lib lib/Skedjul lib/Skedjul/Action lib/Skedjul/Model lib/Skedjul/Notification log t var var/mason var/mason/cache var/mason/obj var/session var/sessions web web/static web/static/css web/templates web/templates/_elements web/templates/calendar web/templates/fragments web/templates/fragments/calendar

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Wed Oct 3 12:15:48 EDT 2007


Author: jesse
Date: Wed Oct  3 12:15:46 2007
New Revision: 4198

Added:
   apps/Skedjul/Makefile.PL
   apps/Skedjul/bin/
   apps/Skedjul/bin/create-user   (contents, props changed)
   apps/Skedjul/bin/jifty   (contents, props changed)
   apps/Skedjul/doc/
   apps/Skedjul/doc/features
   apps/Skedjul/etc/
   apps/Skedjul/etc/config.yml
   apps/Skedjul/lib/
   apps/Skedjul/lib/Skedjul/
   apps/Skedjul/lib/Skedjul.pm
   apps/Skedjul/lib/Skedjul/Action/
   apps/Skedjul/lib/Skedjul/Action/AcceptInvitation.pm
   apps/Skedjul/lib/Skedjul/Action/CreateEvent.pm
   apps/Skedjul/lib/Skedjul/Action/DeclineInvitation.pm
   apps/Skedjul/lib/Skedjul/Action/Login.pm
   apps/Skedjul/lib/Skedjul/Bootstrap.pm
   apps/Skedjul/lib/Skedjul/CurrentUser.pm
   apps/Skedjul/lib/Skedjul/Dispatcher.pm
   apps/Skedjul/lib/Skedjul/Model/
   apps/Skedjul/lib/Skedjul/Model/Attendee.pm
   apps/Skedjul/lib/Skedjul/Model/Event.pm
   apps/Skedjul/lib/Skedjul/Model/EventCollection.pm
   apps/Skedjul/lib/Skedjul/Model/User.pm
   apps/Skedjul/lib/Skedjul/Notification/
   apps/Skedjul/lib/Skedjul/Notification.pm
   apps/Skedjul/lib/Skedjul/Notification/Invited.pm
   apps/Skedjul/lib/Skedjul/Record.pm
   apps/Skedjul/log/
   apps/Skedjul/t/
   apps/Skedjul/t/00-model-Attendee.t
   apps/Skedjul/t/00-model-Event.t
   apps/Skedjul/t/00-model-User.t
   apps/Skedjul/var/   (props changed)
   apps/Skedjul/var/mason/
   apps/Skedjul/var/mason/cache/
   apps/Skedjul/var/mason/obj/
   apps/Skedjul/var/mason/obj/.__obj_create_marker
   apps/Skedjul/var/session/
   apps/Skedjul/var/sessions/
   apps/Skedjul/web/
   apps/Skedjul/web/static/
   apps/Skedjul/web/static/css/
   apps/Skedjul/web/static/css/app.css
   apps/Skedjul/web/static/css/hcalendar.css   (contents, props changed)
   apps/Skedjul/web/static/css/minicalendar.css   (contents, props changed)
   apps/Skedjul/web/static/js/
   apps/Skedjul/web/static/js/hcalendar.js
   apps/Skedjul/web/templates/
   apps/Skedjul/web/templates/_elements/
   apps/Skedjul/web/templates/_elements/accept_invites
   apps/Skedjul/web/templates/_elements/braindump_events
   apps/Skedjul/web/templates/_elements/javascript
   apps/Skedjul/web/templates/_elements/nav
   apps/Skedjul/web/templates/_elements/new_event
   apps/Skedjul/web/templates/_elements/this_week
   apps/Skedjul/web/templates/_elements/today
   apps/Skedjul/web/templates/calendar/
   apps/Skedjul/web/templates/calendar/day
   apps/Skedjul/web/templates/calendar/list
   apps/Skedjul/web/templates/calendar/month
   apps/Skedjul/web/templates/calendar/two_weeks
   apps/Skedjul/web/templates/calendar/week
   apps/Skedjul/web/templates/create_event
   apps/Skedjul/web/templates/event
   apps/Skedjul/web/templates/fragments/
   apps/Skedjul/web/templates/fragments/calendar/
   apps/Skedjul/web/templates/fragments/calendar/day
   apps/Skedjul/web/templates/home
   apps/Skedjul/web/templates/login
Modified:
   apps/Skedjul/   (props changed)

Log:
 r8563 at hualien (orig r3017):  jesse | 2006-03-05 17:28:59 -0500
 
 r8564 at hualien (orig r3018):  jesse | 2006-03-05 18:15:38 -0500
  r25613 at truegrounds:  jesse | 2006-03-05 15:15:20 -0800
  * Getting ourselvs into trouble
 
 r8565 at hualien (orig r3019):  jesse | 2006-03-05 20:43:24 -0500
  r25615 at truegrounds:  jesse | 2006-03-05 17:42:56 -0800
  * Checkpoint. further trouble
 
 r8567 at hualien (orig r3021):  jesse | 2006-03-06 22:34:10 -0500
  r25617 at truegrounds:  jesse | 2006-03-06 19:33:26 -0800
  * Checkpoint
 
 r8568 at hualien (orig r3022):  alexmv | 2006-03-06 23:10:19 -0500
  r8418 at zoq-fot-pik:  chmrr | 2006-03-06 20:09:08 -0800
   * CurrentUser fixes.
 
 r8569 at hualien (orig r3023):  jesse | 2006-03-06 23:10:55 -0500
  r25620 at truegrounds:  jesse | 2006-03-06 20:08:53 -0800
  * Create events that show up on my "today" list
 
 r8570 at hualien (orig r3024):  alexmv | 2006-03-06 23:21:27 -0500
  r8420 at zoq-fot-pik:  chmrr | 2006-03-06 20:21:05 -0800
   * Dispatcher fixes (missing semicolons)
   * Ignore sessions
 
 r8571 at hualien (orig r3025):  alexmv | 2006-03-06 23:21:37 -0500
 
 r8572 at hualien (orig r3026):  alexmv | 2006-03-06 23:48:45 -0500
  r8426 at zoq-fot-pik:  chmrr | 2006-03-06 20:48:25 -0800
   * create-user script
 
 r8573 at hualien (orig r3027):  jesse | 2006-03-07 02:40:15 -0500
  r25631 at truegrounds:  jesse | 2006-03-06 23:39:07 -0800
  * Editable events. renamed Attendee.user to Attendee.attendee
 
 r8574 at hualien (orig r3028):  jesse | 2006-03-07 03:17:57 -0500
  r25634 at truegrounds:  jesse | 2006-03-07 00:14:58 -0800
  * basic daily calendars
 
 r8575 at hualien (orig r3029):  alexmv | 2006-03-07 03:24:09 -0500
  r8436 at zoq-fot-pik:  chmrr | 2006-03-07 00:23:54 -0800
   * Accepting invitations
 
 r8576 at hualien (orig r3030):  alexmv | 2006-03-07 03:26:09 -0500
  r8438 at zoq-fot-pik:  chmrr | 2006-03-07 00:26:03 -0800
   * user -> attendee
 
 r8577 at hualien (orig r3031):  alexmv | 2006-03-07 16:04:08 -0500
  r8442 at zoq-fot-pik:  chmrr | 2006-03-07 13:04:00 -0800
   * Notification sketch
   * Completion on dates
 
 r8579 at hualien (orig r3033):  jesse | 2006-03-08 01:53:16 -0500
  r25823 at truegrounds:  jesse | 2006-03-07 22:53:01 -0800
  * We can schedule events. 
  * Autocompletion of dates works. 
  
  * Daily and weekly calendars work
  * creating a calendar entry "on" a day works.
  * Menus work
  * Fixes to invitation notifications from alex.
 
 r11096 at hualien (orig r3131):  alexmv | 2006-04-04 20:17:50 -0400
  r12146 at zoq-fot-pik:  chmrr | 2006-04-04 20:17:34 -0400
   * New allow and deny actions API
 
 r15858 at hualien (orig r5179):  jesse | 2006-08-31 14:46:33 -0400
 


Added: apps/Skedjul/Makefile.PL
==============================================================================
--- (empty file)
+++ apps/Skedjul/Makefile.PL	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,6 @@
+use inc::Module::Install;
+name('Skedjul');
+version('0.01');
+requires('Jifty' => '');
+
+WriteAll;

Added: apps/Skedjul/bin/create-user
==============================================================================
--- (empty file)
+++ apps/Skedjul/bin/create-user	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+
+use lib '../Jifty/lib';
+use lib 'lib';
+
+use Jifty::Everything;
+use Skedjul::CurrentUser;
+use Skedjul::Model::User;
+use Getopt::Long;
+Jifty->new();
+
+my($name, $email);
+GetOptions('email=s' => \$email, 'name=s', \$name);
+die "Provide a --email and --name for the user\n" unless $email;
+
+$email =~ /(.*)@/;
+$name ||= $1;
+
+my $u = Skedjul::Model::User->new(current_user => Skedjul::CurrentUser->superuser);
+
+my ($id,$msg) = $u->create(
+    name => $name,
+    email => $email,
+    password => 'password', 
+);
+
+print $msg ."\n";
+if ($id) {
+    print "The password for this user is currently 'password'. Please change it. This may involve writing code\n";    
+};
+
+1;

Added: apps/Skedjul/bin/jifty
==============================================================================
--- (empty file)
+++ apps/Skedjul/bin/jifty	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
+    if 0; # not running under some shell
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+BEGIN {
+    Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+    my $root = Jifty::Util->app_root;
+    unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty::Script;
+Jifty::Script->dispatch();

Added: apps/Skedjul/doc/features
==============================================================================
--- (empty file)
+++ apps/Skedjul/doc/features	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,68 @@
+Vision:
+
+* Let BPS staff schedule
+
+    * meetings
+    * travel
+    * deadlines
+
+* Allow people to accept or decline invitations
+
+* Display external feeds
+
+* Export to ical/rss/atom (later)
+
+* Send email reminders (later)
+
+* Events should have:
+
+    * start
+    * end
+    * timezone
+    * people 
+    * location
+    * public or "only attendees and invitees" 
+    * notes
+
+* Views
+
+    * my upcoming events
+    * my recent events
+
+    * someone else's events
+
+* Logins
+    * email
+    * password
+
+* IF WE HAVE TIME
+    Create by mail. smart parser
+
+---------------------------- FEATURE FREEZE ------------------------
+
+
+Users
+    Name
+    Email
+    Current Timezone
+    Password
+
+
+Events
+    Timezone    - Defaults to US/Eastern
+    Type        - meeting, deadline
+    Privacy     - public, private
+    Start
+    End
+    Location    - string. autocomplete from my previous locations
+    Title
+    Description
+
+
+Attendees
+    User
+    Event
+    Status
+        invited, accepted, declined, tentative
+
+

Added: apps/Skedjul/etc/config.yml
==============================================================================
--- (empty file)
+++ apps/Skedjul/etc/config.yml	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,17 @@
+---
+framework:
+  Database:
+    Database: skedjul
+    Driver: Pg
+    Host: localhost
+    User: postgres
+    Version: 0.0.1
+    Password: ''
+    RequireSSL: 0
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+  Mailer: IO
+  MailerArgs:
+    - %log/mail.log%
+  SiteConfig: etc/site_config.yml
+application: 
+  MaxWurbles: 9

Added: apps/Skedjul/lib/Skedjul.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,8 @@
+use strict;
+use warnings;
+
+package Skedjul;
+use DateTime::Format::ISO8601;
+
+1;
+

Added: apps/Skedjul/lib/Skedjul/Action/AcceptInvitation.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Action/AcceptInvitation.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,22 @@
+use warnings;
+use strict;
+
+package Skedjul::Action::AcceptInvitation;
+use base qw/Skedjul::Action/;
+
+sub arguments {
+    return { id => { render_as => 'Hidden' } };
+}
+
+sub take_action {
+    my $self = shift;
+
+    my $invite = Skedjul::Model::Attendee->new();
+    $invite->load_by_cols( user => Jifty->web->current_user->id, event => $self->argument_value("id") );
+    $self->result->error("Can't find that inviation"), return unless $invite->id;
+    $self->result->message("Already accepted that invitation"), return if $invite->status eq "accepted";
+    $invite->set_status("accepted");
+    $self->result->message("Accpeted.");
+}
+
+1;

Added: apps/Skedjul/lib/Skedjul/Action/CreateEvent.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Action/CreateEvent.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,28 @@
+use warnings;
+use strict;
+
+package Skedjul::Action::CreateEvent;
+use base qw/Jifty::Action::Record::Create Skedjul::Action/;
+
+sub record_class { 'Skedjul::Model::Event' }
+sub arguments {
+    my $self = shift;
+    my $arguments = $self->SUPER::arguments();
+
+    $arguments->{'attendees'} = {
+        render_as => 'Textarea',
+        label     => 'Attendees',
+        type      => 'text',
+        default_value => Jifty->web->current_user->user_object->email,
+        autocompleter => sub {
+            my $value = shift;
+            return $self->record->autocomplete_attendees($value);
+        }
+
+    };
+
+    return $arguments;
+}
+
+
+1;

Added: apps/Skedjul/lib/Skedjul/Action/DeclineInvitation.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Action/DeclineInvitation.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,22 @@
+use warnings;
+use strict;
+
+package Skedjul::Action::DeclineInvitation;
+use base qw/Skedjul::Action/;
+
+sub arguments {
+    return { id => { render_as => 'Hidden' } };
+}
+
+sub take_action {
+    my $self = shift;
+
+    my $invite = Skedjul::Model::Attendee->new();
+    $invite->load_by_cols( user => Jifty->web->current_user->id, event => $self->argument_value("id") );
+    $self->result->error("Can't find that inviation"), return unless $invite->id;
+    $self->result->message("Already declineed that invitation"), return if $invite->status eq "declined";
+    $invite->set_status("declined");
+    $self->result->message("Declined.");
+}
+
+1;

Added: apps/Skedjul/lib/Skedjul/Action/Login.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Action/Login.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,91 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Skedjul::Action::Login
+
+=cut
+
+package Skedjul::Action::Login;
+use base qw/Skedjul::Action Jifty::Action/;
+use Skedjul::CurrentUser;
+use Skedjul::Model::User;
+
+=head2 arguments
+
+Return the address and password form fields
+
+=cut
+
+sub arguments { 
+    return( { email => { label => 'Email address',
+                           mandatory => 1,
+                           ajax_validates => 1,
+                            }  ,
+
+              password => { type => 'password',
+                            label => 'Password',
+                            mandatory => 1
+                        },
+              remember => { type => 'checkbox',
+                            label => 'Remember me?',
+                            hints => 'If you want, your browser can remember your Hiveminder login for you',
+                            default => 0,
+                          }
+          });
+
+}
+
+=head2 validate_email ADDRESS
+
+Makes sure that the address submitted is a legal email address and that there's a user in the database with it.
+
+
+=cut
+
+sub validate_email {
+    my $self  = shift;
+    my $email = shift;
+
+    unless ( $email =~ /\S\@\S/ ) {
+        return $self->validation_error(email => "That doesn't look like an email email." );
+    }
+
+    my $u = Skedjul::Model::User->new(current_user => Skedjul::CurrentUser->superuser);
+    $u->load_by_cols( email => $email );
+    return $self->validation_error(email => 'No account has that email email.') unless ($u->id);
+
+
+    return $self->validation_ok('address');
+}
+
+=head2 take_action
+
+Actually check the user's password. If it's right, log them in.
+Otherwise, throw an error.
+
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    my $user = Skedjul::CurrentUser->new( email => $self->argument_value('email'));
+
+    unless ( $user->id  && $user->password_is($self->argument_value('password'))) {
+        $self->result->error( 'You may have mistyped your email address or password. Give it another shot?' );
+        return;
+    }
+
+    # Set up our login message
+    $self->result->message("Welcome back, " . $user->user_object->name . "." );
+
+    # Actually do the signin thing.
+    Jifty->web->current_user($user);
+    Jifty->web->session->expires($self->argument_value('remember') ? '+1y' : undef);
+    Jifty->web->session->set_cookie;
+
+    return 1;
+}
+
+1;

Added: apps/Skedjul/lib/Skedjul/Bootstrap.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Bootstrap.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,44 @@
+use warnings;
+use strict;
+
+package Skedjul::Bootstrap;
+
+use base qw/Jifty::Bootstrap/;
+
+use Skedjul::CurrentUser;
+use Skedjul::Model::User;
+
+=head1 NAME
+
+Skedjul::Bootstrap
+
+=cut
+
+=head2 run
+
+C<run> is the workhorse method for the Bootstrap class. This takes care of setting up 
+internal datastrutures and initializing things. In the case of Skedjul, it creates a system
+user.
+
+=cut
+
+sub run {
+
+    my $bootstrap_currentuser = Skedjul::CurrentUser->new(_bootstrap => '1');
+    my ($id, $msg);
+
+    my $superuser = Skedjul::Model::User->new(current_user => $bootstrap_currentuser);
+    ($id, $msg)   = $superuser->create( email => 'superuser at localhost', access_level => 'administrator', password => 'I ahte you' );
+    unless ($superuser->id) {
+        Jifty->log->error("Couldn't create a superuser $id: $msg");
+    }
+
+    my $nobody  = Skedjul::Model::User->new(current_user => $bootstrap_currentuser);
+    ($id, $msg) = $nobody->create( email => 'nobody at localhost', access_level => 'guest', name => 'Nobody', password => 'I love you');
+    unless ($nobody->id) {
+        Jifty->log->error("Couldn't create a nobody $id: $msg");
+    }
+
+}
+
+1;

Added: apps/Skedjul/lib/Skedjul/CurrentUser.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/CurrentUser.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,82 @@
+use warnings;
+use strict;
+
+
+package Skedjul::CurrentUser;
+
+use base qw(Jifty::CurrentUser);
+
+=head2 new PARAMHASH
+
+Instantiate a new current user object, loading the user by paramhash:
+
+   my $task = Skedjul::Model::Task->new( Skedjul::CurrentUser->new(email => 'system at localhost'));
+
+if you give the param 
+    _bootstrap => 1
+
+your object will be marked as a 'bootstrap' user.
+You can use that to do an endrun around acls
+
+=cut
+
+
+
+sub _init {
+    my $self = shift;
+    my %args = (@_);
+
+    if (delete $args{'_superuser'} ) {
+        $self->is_superuser(1);
+    }
+    if (delete $args{'_bootstrap'} ) {
+        $self->is_bootstrap_user(1);
+    } elsif (keys %args) {
+        $self->user_object(Skedjul::Model::User->new(current_user => $self));
+        $self->user_object->load_by_cols(%args);
+    }
+    $self->SUPER::_init(%args);
+}
+
+
+=head2 nobody
+
+Returns the "nobody" current user, whose email address is C<nobody at localhost>.
+
+=cut
+
+sub nobody {
+  my $class = shift;
+  return $class->new( email => 'nobody at localhost' );
+}
+
+=head2 superuser
+
+Returns the "superuser" current user, whose email address is C<superuser at localhost>.
+
+=cut
+
+sub superuser {
+  my $class = shift;
+  return $class->new( email => 'superuser at localhost', _superuser => 1 );
+}
+
+=head2 access_level
+
+Returns the current user's access_level. (From the user_object).
+If there's no user_object, returns undef
+
+=cut
+
+sub access_level {
+    my $self = shift;
+    if ($self->user_object) {
+        return ($self->user_object->access_level(@_));
+    } else {
+        return undef;
+    }
+
+}
+
+
+1;

Added: apps/Skedjul/lib/Skedjul/Dispatcher.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Dispatcher.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,65 @@
+package Skedjul::Dispatcher;
+use Jifty::Dispatcher -base;
+
+## Auth and login
+before '*' => run {
+    if (Jifty->web->request->path =~ m|/_elements/|) {
+    # Requesting an internal component by hand -- naughty
+        redirect("/errors/requested_private_component");
+    } elsif (not Jifty->web->current_user->id and Jifty->web->request->path !~ m{^/(?:login|feeds|let|errors|dhandler|css|js|images|__jifty)} ) {
+        # Not logged in, trying to access a protected page
+        Jifty->web->tangent(url => '/login');
+    }
+};
+
+
+## LetMes
+before qr'^/let/(.*)' => run {
+    Jifty->api->deny(qr/^Skedjul::Action/);
+
+    my $let_me = Jifty::LetMe->new();
+    $let_me->from_token($1);
+    redirect '/error/let_me/invalid_token' unless $let_me->validate;
+
+    Jifty->web->temporary_current_user($let_me->validated_current_user);
+
+    Jifty->api->allow('GoLegit') if $let_me->path eq "activate_account";
+    Jifty->api->allow('ConfirmLostPassword') if $let_me->path eq "reset_password";
+    Jifty->api->allow('UpdateTask') if $let_me->path eq "update_task";
+
+    my %args = %{$let_me->args};
+    set $_ => $args{$_} for keys %args;
+    set let_me => $let_me;
+};
+
+on qr'^/let/' => run {
+    my $let_me = get 'let_me';
+    show '/let/' . $let_me->path;
+};
+
+on qr{^/event/(\d+)$} => run{
+    my $id = $1;
+    my $event = Skedjul::Model::Event->new();
+    $event->load($id);
+    set event => $event;
+    set action => Jifty->web->new_action(
+        class   => 'UpdateEvent',
+        moniker => 'update',
+        record  => $event
+    );
+    show 'event';
+};
+
+on qr{^/event/create$} => run{
+    set action => Jifty->web->new_action(
+        class   => 'CreateEvent',
+        moniker => 'create',
+    );
+    show 'create_event';
+};
+
+on qr{^/$} => run {
+    redirect '/home';
+};
+
+1;

Added: apps/Skedjul/lib/Skedjul/Model/Attendee.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Model/Attendee.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,40 @@
+package Skedjul::Model::Attendee::Schema;
+use Jifty::DBI::Schema;
+
+# Your column definitions go here.  See L<Jifty::DBI::Schema> for
+# documentation about how to write column definitions.
+
+column event =>
+    refers_to Skedjul::Model::Event;
+
+column attendee => 
+    refers_to Skedjul::Model::User;
+
+column status =>
+    valid_values are qw(invited accepted declined tentative);
+
+
+package Skedjul::Model::Attendee;
+use base qw/Skedjul::Record/;
+
+# Your model-specific methods go here.
+
+
+sub create {
+    my $self = shift;
+    my %args = (
+                status => 'unconfirmed',
+            @_);
+    my $user = Skedjul::Model::User->new();
+    my $email = delete $args{'email'};
+    if ($email) {
+       $user->load_by_cols(email => $email); 
+    }
+    $args{attendee} = $user->id;
+
+    $self->SUPER::create(%args);
+
+}
+
+1;
+

Added: apps/Skedjul/lib/Skedjul/Model/Event.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Model/Event.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,141 @@
+use strict;
+use warnings;
+
+package Skedjul::Model::Event::Schema;
+use Jifty::DBI::Schema;
+
+# Your column definitions go here.  See L<Jifty::DBI::Schema> for
+# documentation about how to write column definitions.
+
+column timezone =>
+    type is 'text';
+
+column event_type =>
+    type is 'text',
+    valid_values are qw(meeting deadline);
+column privacy =>
+    valid_values are qw(public private);
+
+column starts =>
+    type is 'timestamp',
+    filters( are qw/Jifty::DBI::Filter::DateTime/);
+column ends => 
+    type is 'timestamp',
+    filters( are qw/Jifty::DBI::Filter::DateTime/);
+
+
+column location =>
+    type is 'text';
+
+column title =>
+    type is 'text', 
+    default is q{A boring meeting. Couldn't you go do something fun instead?};
+
+column description =>
+    type is 'text';
+
+
+package Skedjul::Model::Event;
+use base qw/Skedjul::Record/;
+use Email::Address;
+use Time::ParseDate;
+
+# Your model-specific methods go here.
+
+sub create {
+    my $self = shift;
+    my %args = (@_);
+
+    my $attendees = delete $args{'attendees'};
+
+    my ($id) = $self->SUPER::create(%args);
+
+    my @attendees = Email::Address->parse($attendees);
+    foreach my $attendee (@attendees) {
+        $self->invite_attendee($attendee->address);
+    }
+
+    foreach my $attendee (@attendees) {
+        Skedjul::Notification::Invited->new(
+            to    => $attendee,
+            event => $self,
+        )->send;
+    }
+
+    return $id;
+}
+
+sub invite_attendee {
+    my $self = shift;
+    my $email = shift;
+    my $attendee = Skedjul::Model::Attendee->new();
+    my $status = 'unconfirmed';
+    if ($email eq $self->current_user->user_object->email)  {
+        $status = 'confirmed'; 
+    }
+
+    $attendee->create(  email => $email,
+                        status => $status,
+                        event => $self->id);
+
+}
+
+sub autocomplete_location {
+    my $self = shift;
+    return ({'autocomplete stubbed'});
+
+}
+
+sub autocomplete_attendees {
+    my $self = shift;
+    my $field = shift;
+    $field =~ /^(.*\s+|)(\S+)$/;
+    my $prefix = $1;
+    my $search = $2;
+    my $users = Skedjul::Model::UserCollection->new();
+    return undef unless $search;
+    $users->limit( column => 'email', operator => 'MATCHES', value => $search);
+    my @ret  ;
+    while (my $u = $users->next) {
+
+        push @ret, { label => $u->email,
+                     value => $prefix.$u->email}
+    
+    }
+    return @ret;
+}
+
+sub canonicalize_starts {
+    return canonicalize_date(@_);
+}
+
+sub canonicalize_ends {
+    return canonicalize_date(@_);
+}
+
+sub canonicalize_date {
+    my $self = shift;
+    my ($time) = @_;
+    my ($epoch, $extra) = parsedate($time, FUZZY => 1, PREFER_FUTURE => 1);
+    return $epoch ? DateTime->from_epoch(epoch => $epoch) : undef;
+}
+
+sub autocomplete_starts {
+    return autocomplete_date(@_);
+}
+
+sub autocomplete_ends {
+    return autocomplete_date(@_);
+}
+
+sub autocomplete_date {
+    my $self = shift;
+    my ($value) = @_;
+    my $time = $self->canonicalize_date($value);
+    $time = scalar localtime($time->epoch) if $time;
+    return $time ? { label => $time, value => $time }
+                 : { label => "Keep typing...", value => $value };
+}
+
+1;
+

Added: apps/Skedjul/lib/Skedjul/Model/EventCollection.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Model/EventCollection.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,84 @@
+package Skedjul::Model::EventCollection;
+
+use base 'Skedjul::Collection';
+
+
+sub today {
+    my $self = shift;
+    my $now = $self->current_user->user_object->localtime;
+    $self->on($now);
+}
+
+sub on {
+    my $self = shift;
+    my $day = shift;
+    $day->truncate(to => "day");
+    $day->set_time_zone( "GMT" );
+
+    my $prev_midnight = $day->clone->iso8601;
+    $day->add(days => 1);
+    my $next_midnight = $day->clone->iso8601;
+
+    # XXX HACK for datetime 2005-01-01t02:32
+    $prev_midnight =~ s/t/ /gi;
+    $next_midnight =~ s/t/ /gi;
+   
+    $self->between($prev_midnight => $next_midnight);
+}
+
+sub between {
+    my $self = shift;
+    my $period_start = shift;
+    my $period_end = shift;
+
+    # End after the start of our period AND
+    # start before the end of our period
+    
+    $self->limit(
+        case_sensitive => 1,
+        subclause => 'daterange',
+        column    => 'ends',
+        operator  => '>',
+        value     => $period_start,
+        entry_aggregator => 'AND'
+    );
+    $self->limit(
+        case_sensitive => 1,
+        subclause => 'daterange',
+        column    => 'starts',
+        operator  => '<',
+        value     => $period_end,
+        entry_aggregator => 'AND'
+    );
+}
+
+sub unconfirmed {
+    my $self = shift;
+    my $user_obj = shift;
+    my $attendees = $self->join( alias1=>'main', column1 => 'id',
+                                 table2=>'attendees', column2 => 'event');
+    $self->limit(alias => $attendees,
+                 column => 'attendee',
+                 value => $user_obj->id);
+    $self->limit(alias => $attendees,
+                 column => 'status',
+                 operator => '=',
+                 value => 'unconfirmed');
+}
+
+
+sub attendee {
+    my $self = shift;
+    my $user_obj = shift;
+    my $attendees = $self->join( alias1=>'main', column1 => 'id',
+                                 table2=>'attendees', column2 => 'event');
+    $self->limit(alias => $attendees,
+                 column => 'attendee',
+                 value => $user_obj->id);
+    $self->limit(alias => $attendees,
+                 column => 'status',
+                 operator => '!=',
+                 value => 'declined');
+}
+
+1;

Added: apps/Skedjul/lib/Skedjul/Model/User.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Model/User.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,51 @@
+package Skedjul::Model::User::Schema;
+use Jifty::DBI::Schema;
+
+# Your column definitions go here.  See L<Jifty::DBI::Schema> for
+# documentation about how to write column definitions.
+
+column name =>
+    type is 'text';
+column email => 
+    type is 'text',
+    is distinct;
+column timezone => 
+    type is 'text';
+
+column password =>
+  is mandatory,
+  is unreadable,
+  label is 'Password',
+  type is 'varchar',
+  hints is 'Your password should be at least six characters',
+  render_as 'password';
+
+column access_level =>
+    type is 'text';
+
+package Skedjul::Model::User;
+use base qw/Skedjul::Record/;
+use DateTime;
+
+# Your model-specific methods go here.
+
+sub password_is {
+    my $self = shift;
+    my $val= shift;
+    return 1 if ($self->__value('password')  eq $val);
+    return 0;
+
+}
+
+=head2 localtime
+
+=cut
+
+sub localtime {
+    my $self = shift;
+    # XXX FIXME
+    return DateTime->now( time_zone => 'US/Eastern' );
+}
+
+1;
+

Added: apps/Skedjul/lib/Skedjul/Notification.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Notification.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,27 @@
+use warnings;
+use strict;
+
+package Skedjul::Notification;
+
+use base qw/Jifty::Notification/;
+
+=head1 NAME
+
+Skedjul::Notification 
+
+=head1 DESCRIPTION
+
+Abstract base class for any notifications that Skedjul sends.
+
+=head2 setup
+
+Sets the sender to C<skedjul at bestpractical.com>.
+
+=cut
+
+sub setup {
+    my $self = shift;
+    $self->from('skedjul at bestpractical.com');
+}
+
+1;

Added: apps/Skedjul/lib/Skedjul/Notification/Invited.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Notification/Invited.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,39 @@
+use warnings;
+use strict;
+
+package Skedjul::Notification::Invited;
+use base qw/Skedjul::Notification/;
+
+=head1 NAME
+
+Skedjul::Notificication::Invited - Notification that you've been
+invited to an event.
+
+=cut
+
+__PACKAGE__->mk_accessors( qw/event/ );
+
+sub setup {
+    my $self = shift;
+    $self->SUPER::setup(@_);
+
+    unless(UNIVERSAL::isa($self->event, "Skedjul::Model::Event")) {
+        $self->log->error((ref $self) . " called with invalid event argument: ".YAML::Dump($self->event));
+        return;
+    }
+
+    $self->body(<<"END_BODY");
+
+You've been invited to an event:
+  Title:    @{[$self->event->title]}
+  Starts:   @{[$self->event->starts]}
+  Ends:     @{[$self->event->ends]}
+  Invitees: @{[map {$_->name} $self->recipients]}
+  Location: @{[$self->event->location]}
+  Description: @{[$self->event->description]}
+
+END_BODY
+
+}
+
+1;

Added: apps/Skedjul/lib/Skedjul/Record.pm
==============================================================================
--- (empty file)
+++ apps/Skedjul/lib/Skedjul/Record.pm	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,13 @@
+use warnings;
+use strict;
+
+package Skedjul::Record;
+use base qw/Jifty::Record/;
+
+sub current_user_can {
+    my $self = shift;
+    return 1 if $self->current_user and $self->current_user->id;
+    return $self->SUPER::current_user_can(@_);
+}
+
+1;

Added: apps/Skedjul/t/00-model-Attendee.t
==============================================================================
--- (empty file)
+++ apps/Skedjul/t/00-model-Attendee.t	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the Attendee model.
+
+=cut
+
+use Jifty::Test tests => 11;
+
+# Make sure we can load the model
+use_ok('Skedjul::Model::Attendee');
+
+# Grab a system use
+my $system_user = Skedjul::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = Skedjul::Model::Attendee->new(current_user => $system_user);
+my ($id) = $o->create();
+ok($id, "Attendee create returned success");
+ok($o->id, "New Attendee has valid id set");
+is($o->id, $id, "Create returned the right id");
+
+# And another
+$o->create();
+ok($o->id, "Attendee create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection =  Skedjul::Model::AttendeeCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 2, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+

Added: apps/Skedjul/t/00-model-Event.t
==============================================================================
--- (empty file)
+++ apps/Skedjul/t/00-model-Event.t	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the Event model.
+
+=cut
+
+use Jifty::Test tests => 13;
+
+# Make sure we can load the model
+use_ok('Skedjul::Model::Event');
+
+# Grab a system use
+my $system_user = Skedjul::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = Skedjul::Model::Event->new(current_user => $system_user);
+Jifty->handle->log_sql_statements(1);
+my ($id, $msg) = $o->create(starts => "2006-03-09", ends => "2006-03-10", title => "moose");
+ok($id, "Event create returned success");
+ok($o->id, "New Event has valid id set");
+is($o->id, $id, "Create returned the right id");
+like($o->starts, qr/2006-03-09/, "Parsed time correctly: ");
+isa_ok($o->starts, "DateTime", "Got object out");
+
+# And another
+$o->create(title => "moose");
+ok($o->id, "Event create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection =  Skedjul::Model::EventCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 2, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+

Added: apps/Skedjul/t/00-model-User.t
==============================================================================
--- (empty file)
+++ apps/Skedjul/t/00-model-User.t	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the User model.
+
+=cut
+
+use Jifty::Test tests => 11;
+
+# Make sure we can load the model
+use_ok('Skedjul::Model::User');
+
+# Grab a system use
+my $system_user = Skedjul::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = Skedjul::Model::User->new(current_user => $system_user);
+my ($id) = $o->create( email => 'jesse at example.com', password => 'test');
+ok($id, "User create returned success");
+ok($o->id, "New User has valid id set");
+is($o->id, $id, "Create returned the right id");
+
+# And another
+$o->create( email => 'j2 at example.com', password => 'test1');
+ok($o->id, "User create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection =  Skedjul::Model::UserCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 4, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 3, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+

Added: apps/Skedjul/var/mason/obj/.__obj_create_marker
==============================================================================

Added: apps/Skedjul/web/static/css/app.css
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/static/css/app.css	Wed Oct  3 12:15:46 2007
@@ -0,0 +1 @@
+ at import "hcalendar.css";

Added: apps/Skedjul/web/static/css/hcalendar.css
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/static/css/hcalendar.css	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,50 @@
+//td {
+//  border: solid 2px;
+//}
+
+//table {
+//  border: dashed 1px;
+//}
+
+.calHeader {
+  font-size:16px;
+  font-family:arial, helvetica; 
+  font-weight:bold;
+  text-align: center;
+}
+
+
+.calDay {
+  background-color:#EDECD8; 
+  height:80px; 
+  width: 90px;
+  vertical-align: top;
+}
+.calDayToday {
+  background-color:#F5F4ED; 
+}
+.calEmptyDay {
+  background-color:#EEEEEE; 
+}
+
+.calDayLabel { 
+  font-size:10px; 
+  font-family:verdana, arial, helvetica; 
+  color:#00F;
+  text-align: right;
+}
+
+.calEvent				{ font-size:10px; line-height:16px; font-family:verdana, arial, helvetica; color:#000 }
+
+td.calColumnHeader {
+  background-color:#72747A; 
+  font-size:12px; 
+  font-family:arial, helvetica; 
+  color:#FFFFFF; 
+  font-weight:bold;
+  text-align: center;
+}
+
+tr.calAbbrevColumnHeader {
+  display: none;
+}

Added: apps/Skedjul/web/static/css/minicalendar.css
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/static/css/minicalendar.css	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,56 @@
+//td {
+//  border: solid 2px;
+//}
+
+//table {
+//  border: dashed 1px;
+//}
+
+.calHeader {
+  font-size:16px;
+  font-family:arial, helvetica; 
+  font-weight:bold;
+  text-align: center;
+}
+
+
+.calDay {
+  background-color:#EDECD8; 
+//  height:80px; 
+//  width: 90px;
+  vertical-align: top;
+}
+.calDayToday {
+  background-color:#F5F4ED; 
+}
+.calEmptyDay {
+  background-color:#EEEEEE; 
+}
+
+.calEventDay {
+  border: dashed 1px;
+}
+
+.calDayLabel { 
+  font-size:10px; 
+  font-family:verdana, arial, helvetica; 
+  color:#00F;
+  text-align: right;
+}
+
+.calEvent				{
+  display: none;
+}
+
+td.calAbbrevColumnHeader {
+  background-color:#72747A; 
+  font-size:12px; 
+  font-family:arial, helvetica; 
+  color:#FFFFFF; 
+  font-weight:bold;
+  text-align: center;
+}
+
+tr.calColumnHeader {
+  display: none;
+}

Added: apps/Skedjul/web/static/js/hcalendar.js
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/static/js/hcalendar.js	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,259 @@
+/* JSCalendar. 5/17/2005
+
+MIT license:
+
+Copyright (c) 2005 David Glasser
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+/* CONFIG */
+
+var dayNames = new Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday");
+var dayAbbrevs = new Array("S", "M", "T", "W", "T", "F", "S");
+var firstDayOfWeek = 0;
+var DEBUG = 1;
+
+/* stolen from somebody's blog */
+
+function addLoadEvent(func) {
+  var oldonload = window.onload;
+  if (typeof window.onload != 'function') {
+    window.onload = func;
+  } else {
+    window.onload = function() {
+      oldonload();
+      func();
+    }
+  }
+}
+
+/* this function borrowed from prototype.js */
+document.getElementsByClassName = function(className) {
+  var children = document.getElementsByTagName('*') || document.all;
+  var elements = new Array();
+
+  for (var i = 0; i < children.length; i++) {
+    var child = children[i];
+    if (hasClass(child, className)) {
+      elements.push(child);
+    }
+  }
+
+  return elements;
+}
+
+function hasClass (elt, cls) {
+  var classNames = elt.className.split(' ');
+  for (var j = 0; j < classNames.length; j++) {
+    if (classNames[j] == cls) {
+      return true;
+    }
+  }
+  return false;
+}
+
+function debug(message) {
+  if (DEBUG)
+    alert(message);
+}
+
+var globalDateHash = new Object();
+
+function findHCalendarEvents () {
+  if (!(document.getElementById && document.createElement)) {
+    debug("No DOM!");
+    return;
+  }
+  var outputDiv = document.getElementById('jhCalendar');
+  
+  if (outputDiv == null) {
+    debug("jhCalendar not found!")
+    return;
+  }
+    
+  var events = document.getElementsByClassName("vevent");
+  for (var i = 0; i < events.length; i++) {
+    processEvent(events[i]);
+  }
+  
+
+  
+  for (year in globalDateHash) {
+    for (month in globalDateHash[year]) {
+      var mt = makeMonthTable(globalDateHash[year][month], year, month);
+      outputDiv.appendChild(mt);
+    }
+  }
+}
+
+function processEvent(ev) {
+  var kids = ev.getElementsByTagName('*') || ev.all;
+  var eventHash = {};
+  var dateInfo;
+  for (var i = 0; i < kids.length; i++) {
+    var kid = kids[i];
+    if (hasClass(kid, 'summary')) {
+      eventHash.summary = kid.cloneNode(true); // true means a "deep" cloning
+    } else if (hasClass(kid, 'dtstart')) {
+      dateInfo = parseDT(kid);
+    }
+  }
+  if (dateInfo == null) {
+    debug("didn't find dtstart for " + ev);
+    return;
+  }
+  if (globalDateHash[ dateInfo[0] ] == null)
+    globalDateHash[ dateInfo[0] ] = new Object();
+  if (globalDateHash[ dateInfo[0] ][ dateInfo[1] ] == null)
+    globalDateHash[ dateInfo[0] ][ dateInfo[1] ] = new Object();
+  if (globalDateHash[ dateInfo[0] ][ dateInfo[1] ][ dateInfo[2] ] == null)
+    globalDateHash[ dateInfo[0] ][ dateInfo[1] ][ dateInfo[2] ] = new Array();
+    
+  globalDateHash[ dateInfo[0] ][ dateInfo[1] ][ dateInfo[2] ].push(eventHash);
+  return;
+}
+
+function parseDT(dt) {
+  var dtText;
+  if (dt.nodeName == 'ABBR' && dt.title) {
+    dtText = dt.title;
+  } else {
+    dtText = dt.firstChild.data;
+  }
+  var result = dtText.match( /^(\d{4})(\d{2})(\d{2})/ );
+  if (result == null) {
+    debug("didn't recognize DT: " + dtText);
+  }
+  
+  return [result[1], result[2], result[3]];
+}
+
+var monthLength = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
+
+// TODO deal with leap year
+getMonthLength = function(year, month) {
+  return monthLength[month - 1];
+}
+
+function makeMonthTable (monthHash, year, month) {
+  currentMonthLength = getMonthLength(year, month);
+  
+  var today = new Date;
+  var todayYear = today.getFullYear();
+  var todayMonth = today.getMonth() + 1;
+  var todayDay = today.getDate();
+    
+  var days = new Array(currentMonthLength+1); // We are going to index this array starting at 1.  Because I said so.
+  for (var i = 1; i <= currentMonthLength; i++) {
+    days[i] = document.createElement('TD');
+    days[i].className = 'calDay';
+    if (todayYear == year && todayMonth == month && todayDay == i) {
+      days[i].className += ' calDayToday';
+    }
+    var dayLabel = document.createElement('DIV');
+    dayLabel.className = 'calDayLabel';
+    days[i].appendChild(dayLabel);
+    dayLabel.appendChild(document.createTextNode(i));
+  }
+  
+  // populate days here
+
+  for (var day in monthHash) {
+    var dayEvents = monthHash[day];
+    for (var i = 0; i < dayEvents.length; i++) {
+      var eventDiv = document.createElement('DIV');
+      eventDiv.className = 'calEvent';
+      eventDiv.appendChild(dayEvents[i].summary);
+      dayTD = days[day-0];
+      dayTD.appendChild(eventDiv);
+      if (! hasClass(dayTD, 'calEventDay')) {
+        dayTD.className += ' calEventDay';
+      }
+      
+    }
+  }
+    
+  var monthTable = document.createElement('TABLE');
+  monthTable.className = 'calTable';
+  var tbody = monthTable.appendChild(document.createElement("TBODY"));
+  
+  var titleRow = document.createElement('TR');
+  tbody.appendChild(titleRow);
+  var titleTD = document.createElement('TD');
+  titleTD.setAttribute("colspan", 7);
+  titleTD.className = 'calHeader';
+  
+  titleRow.appendChild(titleTD);
+  titleTD.appendChild(document.createTextNode(month + "/" + year));
+  
+  var headerRow = document.createElement('TR');
+  headerRow.className = 'calColumnHeader';
+  tbody.appendChild(headerRow);
+  for (var i = 0; i <= 6; i++) {
+    var headerElement = document.createElement('TD');
+    headerElement.className = 'calColumnHeader';
+    headerElement.appendChild(document.createTextNode(dayNames[(i+firstDayOfWeek) % 7]));
+    headerRow.appendChild(headerElement);
+  }
+
+  var miniHeaderRow = document.createElement('TR');
+  miniHeaderRow.className = 'calAbbrevColumnHeader';
+  tbody.appendChild(miniHeaderRow);
+  for (var i = 0; i <= 6; i++) {
+    var headerElement = document.createElement('TD');
+    headerElement.className = 'calAbbrevColumnHeader';
+    headerElement.appendChild(document.createTextNode(dayAbbrevs[(i+firstDayOfWeek) % 7]));
+    miniHeaderRow.appendChild(headerElement);
+  }
+  
+  var dateToCheck = new Date();
+  dateToCheck.setYear(year);
+  dateToCheck.setMonth(month-1);
+  dateToCheck.setDate(1);
+  var dayOfFirstOfMonth = dateToCheck.getDay();
+
+  var row = tbody.appendChild(document.createElement("TR"));
+  // We add 14 (could have gotten away with 7) so that we don't get (-3) % 7 == -3
+  for (var i = 0; i < (dayOfFirstOfMonth - firstDayOfWeek + 14) % 7; i++) {
+    var emptyDay = document.createElement('TD');
+    emptyDay.className = 'calDay calEmptyDay';
+    row.appendChild(emptyDay);
+  }
+  
+  for (var i = 1; i <= currentMonthLength; i++) {
+    if (row.childNodes.length == 7) {
+      row = tbody.appendChild(document.createElement("TR"));
+    }
+    row.appendChild(days[i]);
+  }
+  
+  while (row.childNodes.length < 7) {
+      var emptyDay = document.createElement('TD');
+      emptyDay.className = 'calDay calEmptyDay';
+      row.appendChild(emptyDay);
+  }
+  
+  return monthTable;
+}
+
+
+
+addLoadEvent(findHCalendarEvents);

Added: apps/Skedjul/web/templates/_elements/accept_invites
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/_elements/accept_invites	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,20 @@
+<% Jifty->web->form->start %>
+<h2>Unconfirmed invitations</h2>
+<dl>
+% while (my $event = $invites->next()) {
+% my $accept  = Jifty->web->new_action( class => 'AcceptInvitation', arguments => { id => $event->id } );
+% my $decline = Jifty->web->form->add_action( class => 'DeclineInvitation', arguments => { id => $event->id } );
+<% $accept->form_field("id") %><% $decline->form_field("id") %>
+<dt><%$event->starts %> - <%$event->ends%></dt>
+<dd><% Jifty->web->link( label =>$event->title, url => '/event/'.$event->id)  %>
+[ <% Jifty->web->link( label => "Accept",  submit => $accept ) %>
+| <% Jifty->web->link( label => "Decline", submit => $decline ) %> ]
+</dd>
+% }
+</dl>
+<% Jifty->web->form->end %>
+<%init>
+my $invites = Skedjul::Model::EventCollection->new();
+$invites->unconfirmed(Jifty->web->current_user);
+return unless $invites->count;
+</%init>

Added: apps/Skedjul/web/templates/_elements/braindump_events
==============================================================================

Added: apps/Skedjul/web/templates/_elements/javascript
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/_elements/javascript	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,10 @@
+  <script type="text/javascript" src="/js/json.js"></script>
+  <script type="text/javascript" src="/js/prototype.js"></script>
+  <script type="text/javascript" src="/js/behaviour.js"></script>
+  <script type="text/javascript" src="/js/scriptaculous/scriptaculous.js"></script>
+  <script type="text/javascript" src="/js/jifty.js"></script>
+  <script type="text/javascript" src="/js/combobox.js"></script>
+  <script type="text/javascript" src="/js/key_bindings.js"></script>
+  <script type="text/javascript" src="/js/bps_util.js"></script>
+  <script type="text/javascript" src="/js/rico.js"></script>
+  <script type="text/javascript" src="/js/app_behaviour.js"></script>

Added: apps/Skedjul/web/templates/_elements/nav
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/_elements/nav	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,13 @@
+<%init>
+my $top = Jifty->web->navigation;
+$top->child(Home         => url => "/" );
+$top->child(Today        => url => "/calendar/day" );
+$top->child("This week"  => url => "/calendar/week" );
+$top->child("This month" => url => "/calendar/month" );
+
+$top->child("My invites" => url => "/invites" );
+$top->child("Search"     => url => "/events" );
+$top->child("People"     => url => "/people" );
+
+$top->child("About"      => url => "/about" );
+</%init>
\ No newline at end of file

Added: apps/Skedjul/web/templates/_elements/new_event
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/_elements/new_event	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,10 @@
+<%init>
+my $action = Jifty->web->new_action( class =>'CreateEvent', moniker => 'create_task');
+</%init>
+<h2>Who? What? When? Where? New event!</h2>
+% Jifty->web->form->start;
+% foreach my $arg ($action->argument_names) { 
+<%$action->form_field($arg)%>
+%}
+<%Jifty->web->form->submit(label =>'Create')%>
+% Jifty->web->form->end;

Added: apps/Skedjul/web/templates/_elements/this_week
==============================================================================

Added: apps/Skedjul/web/templates/_elements/today
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/_elements/today	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,2 @@
+<% Jifty->web->region( name => 'today', path => '/fragments/calendar/day') %>
+

Added: apps/Skedjul/web/templates/calendar/day
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/calendar/day	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,3 @@
+<&|/_elements/wrapper, title => "Daily calendar"&>
+<% Jifty->web->region( name => 'day_calendar', path => '/fragments/calendar/day') %>
+</&>

Added: apps/Skedjul/web/templates/calendar/list
==============================================================================

Added: apps/Skedjul/web/templates/calendar/month
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/calendar/month	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,29 @@
+<%args>
+$year => undef
+$month => undef
+</%args>
+<%init>
+my $events = Skedjul::Model::EventCollection->new();
+my $start_date = DateTime->new( year => $year,
+                                month => $month,
+                                day => 1
+                              );
+
+my $end_date = $start_date->clone;
+$end_date->add(months => 1);
+$end_date->subtract(seconds => 1);
+
+
+
+$events->attendee(Jifty->web->current_user);
+$events->between( $start_date->ymd." ".$start_date->hms => $end_date->ymd." ". $end_date->hms);
+</%init>
+<&|/_elements/wrapper, title => "Monthly calendar" &>
+% while (my $event = $events->next) {
+<div class="vevent">
+    <span class="summary"><%$event->title%></span>
+    <abbr class="dtstart" title="<%$event->starts->ymd('')%>"><%$event->starts%></abbr>
+</div>
+% }
+<div id="jhCalendar"/>
+</&>

Added: apps/Skedjul/web/templates/calendar/two_weeks
==============================================================================

Added: apps/Skedjul/web/templates/calendar/week
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/calendar/week	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,46 @@
+<&|/_elements/wrapper, title => "Weekly calendar"&>
+
+<%perl>
+for my $week (0..1) {
+for my $day (0..6) {
+  my $today = $starts->clone->add( days => $week*7 + $day );
+  my $tomorrow = $today->clone->add( days => 1 );
+
+
+  my $events = Skedjul::Model::EventCollection->new();
+  $events->between($today => $tomorrow);
+  $events->attendee(Jifty->web->current_user);
+</%perl>
+<div style="width: 12%; border: 1px solid black; float: left">
+<b><% $today->day_name %>, <% $today->month_name %> <% $today->day_of_month %></b>
+<dl>
+%   while (my $e = $events->next) {
+<dt>
+%     if ($e->starts < $today) {
+        (cont'd)
+%     } else {
+        <% $e->starts->strftime("%H:%M") %>
+%     }
+      -
+%     if ($e->ends > $tomorrow) {
+        (cont'd)
+%     } else {
+        <% $e->ends->day == $today->day ? $e->ends->strftime("%H:%M") : "midnight" %>
+%     }
+</dt><dd><% Jifty->web->tangent( label => $e->title, url => "/event/".$e->id ) %></dd>
+%   }
+</dl>
+<% Jifty->web->tangent( label => "Add an event", url => "/event/create", parameters => {day => $today->ymd}) %>
+</div>
+% }
+<br style="clear: left" />
+% }
+
+</&>
+<%args>
+$starts => undef
+</%args>
+<%init>
+$starts = $starts ? DateTime::Format::ISO8601->parse_datetime($starts) : DateTime->now;
+$starts->truncate( to => "week" );
+</%init>
\ No newline at end of file

Added: apps/Skedjul/web/templates/create_event
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/create_event	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,15 @@
+<%args>
+$action
+$day => undef
+</%args>
+<%init>
+$action->form_field($_)->default_value($day) for qw/starts ends/;
+</%init>
+<&|/_elements/wrapper, title => "New event" &>
+% Jifty->web->form->start;
+% foreach my $arg ($action->argument_names) { 
+<%$action->form_field($arg)%>
+%}
+<%Jifty->web->return(submit => $action, label =>'Create')%>
+% Jifty->web->form->end;
+</&>

Added: apps/Skedjul/web/templates/event
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/event	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,14 @@
+<%args>
+$action
+$event
+</%args>
+<%init>
+</%init>
+<&|/_elements/wrapper, title => $event->title &>
+% Jifty->web->form->start;
+% foreach my $arg ($action->argument_names) { 
+<%$action->form_field($arg)%>
+%}
+<%Jifty->web->return(submit => $action, label =>'Save changes')%>
+% Jifty->web->form->end;
+</&>

Added: apps/Skedjul/web/templates/fragments/calendar/day
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/fragments/calendar/day	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,24 @@
+<% Jifty->web->form->start %>
+<h1>What's on tap for <%$datetime%></h1>
+<dl>
+% while (my $e = $events->next) {
+<dt><% $e->starts%> - <%$e->ends%></dt>
+<dd><% Jifty->web->link( label => $e->title || "Untitled event", url => "/event/".$e->id) %></dd>
+% }
+</dl>
+  <% Jifty->web->link( label => "Previous day", onclick => { args => { date => $datetime->clone->subtract(days=>1)."" }})%>
+  <% Jifty->web->link( label => "Next day", onclick => { args => { date => $datetime->clone->add(days=>1)."" }})%>
+<% Jifty->web->form->end %>
+</div>
+<%args>
+$date => undef
+</%args>
+<%init>
+
+$date ||= DateTime->now;
+my $datetime = DateTime::Format::ISO8601->parse_datetime($date);
+my $events = Skedjul::Model::EventCollection->new();
+$events->attendee(Jifty->web->current_user);
+$events->between($datetime => $datetime->clone->add(days=>1));
+</%init>
+

Added: apps/Skedjul/web/templates/home
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/home	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,7 @@
+<&| /_elements/wrapper, title => 'Skedjul for Best Practical'&>
+<& /_elements/accept_invites &>
+<& /_elements/braindump_events &>
+<& /_elements/today &>
+<& /_elements/this_week &>
+<& /_elements/new_event &>
+</&>

Added: apps/Skedjul/web/templates/login
==============================================================================
--- (empty file)
+++ apps/Skedjul/web/templates/login	Wed Oct  3 12:15:46 2007
@@ -0,0 +1,34 @@
+<&| /_elements/wrapper, title => "Welcome!" &>
+<h2>What is this thing?</h2>
+
+<div id="overview">
+<p>We're still in a "friends and family" preview phase. Not everything here works like we want it to. Some of it because it's not done yet, some of it because we broke it while making other stuff work right.</p>
+<p><b>These are not the droids you're looking for.</b> 
+%# But they will help you keep track of everything you're working on, either alone or with a team.  Sign up, log in, and start getting crossing things off your list.
+</p></div>
+
+<h2>Getting started</h2>
+<div id="signup">
+<p>Welcome to Best Practical's private calendaring server. It's basic</p>
+%#<p>It only takes a few clicks to set up a free account. Until we're in beta, <i>everything</i> is free. <%Jifty->web->link( url => "/welcome/signup", label => "Join us now.")%></p>
+</div>
+<div id="login-box">
+<h2>Login</h2>
+<% Jifty->web->form->start(call => $next, name => "loginbox") %>
+<% $action->form_field('email') %>
+<% $action->form_field('password') %>
+<% $action->form_field('remember') %>
+<% Jifty->web->form->submit(label => 'Login', submit => $action) %>
+<% Jifty->web->form->end %>
+</div>
+%#<p><%Jifty->web->link( url => "/welcome/lostpass.html", label =>"Forgot your password?")%></p>
+
+</&>
+<%init> 
+$m->redirect('/home') if Jifty->web->current_user->id;
+
+my $action = Jifty->web->new_action(class => 'Login', moniker => 'loginbox' );
+
+my $next = Jifty->web->request->continuation
+  || Jifty::Continuation->new(request => Jifty::Request->new(path => "/home"));
+</%init>


More information about the Jifty-commit mailing list