monologue 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +73 -0
  3. data/Rakefile +32 -0
  4. data/app/assets/javascripts/monologue/admin/application.js +13 -0
  5. data/app/assets/javascripts/monologue/admin/tinymce-config.js +21 -0
  6. data/app/assets/javascripts/monologue/blog/application.js +9 -0
  7. data/app/assets/stylesheets/monologue/admin/application.css +10 -0
  8. data/app/assets/stylesheets/monologue/blog/application.css +13 -0
  9. data/app/assets/stylesheets/monologue/blog/custom.css +1 -0
  10. data/app/assets/stylesheets/monologue/blog/fonts.css +1 -0
  11. data/app/assets/stylesheets/monologue/blog/monologue.css +37 -0
  12. data/app/assets/stylesheets/monologue/blog/skeleton/base.css +343 -0
  13. data/app/assets/stylesheets/monologue/blog/skeleton/layout.css +58 -0
  14. data/app/assets/stylesheets/monologue/blog/skeleton/skeleton.css +242 -0
  15. data/app/controllers/monologue/admin/base_controller.rb +12 -0
  16. data/app/controllers/monologue/admin/posts_controller.rb +54 -0
  17. data/app/controllers/monologue/admin/sessions_controller.rb +22 -0
  18. data/app/controllers/monologue/application_controller.rb +22 -0
  19. data/app/controllers/monologue/posts_controller.rb +25 -0
  20. data/app/form_builders/monologue_admin_form_builder.rb +61 -0
  21. data/app/helpers/monologue/admin/admin_helper.rb +4 -0
  22. data/app/helpers/monologue/application_helper.rb +8 -0
  23. data/app/helpers/monologue/posts_helper.rb +4 -0
  24. data/app/helpers/monologue/sessions_helper.rb +6 -0
  25. data/app/models/monologue/post.rb +40 -0
  26. data/app/models/monologue/posts_revision.rb +34 -0
  27. data/app/models/monologue/user.rb +6 -0
  28. data/app/sweepers/monologue/posts_sweeper.rb +28 -0
  29. data/app/views/layouts/monologue/_google_analytics.html.erb +15 -0
  30. data/app/views/layouts/monologue/admin.html.erb +21 -0
  31. data/app/views/layouts/monologue/admin/_nav_bar.html.erb +24 -0
  32. data/app/views/layouts/monologue/application.html.erb +60 -0
  33. data/app/views/monologue/admin/posts/_form.html.erb +16 -0
  34. data/app/views/monologue/admin/posts/edit.html.erb +4 -0
  35. data/app/views/monologue/admin/posts/index.html.erb +20 -0
  36. data/app/views/monologue/admin/posts/new.html.erb +4 -0
  37. data/app/views/monologue/admin/sessions/new.html.erb +13 -0
  38. data/app/views/monologue/posts/404.html.erb +9 -0
  39. data/app/views/monologue/posts/_pagination.html.erb +9 -0
  40. data/app/views/monologue/posts/_social_sharing.html.erb +42 -0
  41. data/app/views/monologue/posts/feed.rss.builder +19 -0
  42. data/app/views/monologue/posts/index.html.erb +18 -0
  43. data/app/views/monologue/posts/show.html.erb +38 -0
  44. data/config/locales/en.yml +106 -0
  45. data/config/locales/fr.yml +106 -0
  46. data/config/routes.rb +15 -0
  47. data/db/migrate/20120114001001_create_monologue_users.rb +11 -0
  48. data/db/migrate/20120120193858_create_monologue_posts_revisions.rb +18 -0
  49. data/db/migrate/20120120193907_create_monologue_posts.rb +10 -0
  50. data/db/seeds.rb +1 -0
  51. data/lib/monologue.rb +17 -0
  52. data/lib/monologue/engine.rb +11 -0
  53. data/lib/monologue/version.rb +3 -0
  54. data/lib/tasks/monologue_tasks.rake +4 -0
  55. data/vendor/assets/images/monologue/bootstrap/glyphicons-halflings-white.png +0 -0
  56. data/vendor/assets/images/monologue/bootstrap/glyphicons-halflings.png +0 -0
  57. data/vendor/assets/javascripts/monologue/bootstrap/bootstrap-datepicker-fr.js +9 -0
  58. data/vendor/assets/javascripts/monologue/bootstrap/bootstrap-datepicker.js +338 -0
  59. data/vendor/assets/javascripts/monologue/bootstrap/bootstrap.min.js +1 -0
  60. data/vendor/assets/stylesheets/monologue/bootstrap/bootstrap-datepicker.css +130 -0
  61. data/vendor/assets/stylesheets/monologue/bootstrap/bootstrap-responsive.min.css +3 -0
  62. data/vendor/assets/stylesheets/monologue/bootstrap/bootstrap.min.css +610 -0
  63. metadata +285 -0
@@ -0,0 +1,38 @@
1
+
2
+ <% content_for :title do %>
3
+ <%= @revision.title %>
4
+ <% end %>
5
+
6
+ <% content_for :meta_description do %>
7
+ <%= truncate(strip_tags(@revision.content), :length => 155) %>
8
+ <% end %>
9
+
10
+ <article>
11
+ <header><h1><%= link_to @revision.title, @revision.url %></h1></header>
12
+ <div class="posted">
13
+ <time datetime="<%= @revision.published_at %>">
14
+ <%= @revision.published_at.to_date.to_formatted_s(:long_ordinal) %>
15
+ </time>&nbsp;&nbsp;|&nbsp;&nbsp;<%= @revision.user.name %>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="<%= @revision.url + "#disqus_thread" %>"></a>
16
+ </div>
17
+ <p><%= raw @revision.content %></p>
18
+
19
+ <%= render "social_sharing" %>
20
+
21
+ <div id="disqus_thread"></div>
22
+ <script type="text/javascript">
23
+ /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
24
+ var disqus_shortname = '<%= Monologue.disqus_shortname%>'; // required: replace example with your forum shortname
25
+
26
+ <% if Rails.env.development? %>
27
+ var disqus_developer = 1; // developer mode is on
28
+ <% end %>
29
+
30
+ /* * * DON'T EDIT BELOW THIS LINE * * */
31
+ (function() {
32
+ var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
33
+ dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
34
+ (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
35
+ })();
36
+ </script>
37
+ <noscript>Please enable JavaScript to view the comments.</a></noscript>
38
+ </article>
@@ -0,0 +1,106 @@
1
+ en:
2
+ monologue:
3
+ posts:
4
+ pagination:
5
+ older_posts:
6
+ "Older posts"
7
+ newer_posts:
8
+ "Newer posts"
9
+ social_sharing:
10
+ tagline:
11
+ "Loved what you just read? Share it!"
12
+ index:
13
+ readmore:
14
+ "Read more"
15
+ "404":
16
+ title:
17
+ "The page you were looking for doesn't exist."
18
+ message:
19
+ "You may have mistyped the address or the page may have moved."
20
+ admin:
21
+ posts:
22
+ index:
23
+ title:
24
+ "Title"
25
+ edit:
26
+ "Edit"
27
+ delete:
28
+ "Delete"
29
+ published:
30
+ "Yes"
31
+ not_published:
32
+ "Not published"
33
+ status:
34
+ "Published ?"
35
+ new:
36
+ header:
37
+ "New monologue"
38
+ edit:
39
+ header:
40
+ "Edit"
41
+ form:
42
+ title:
43
+ "Title"
44
+ content:
45
+ "Content"
46
+ url:
47
+ before_generated_url:
48
+ "URL <br /><i> This will be filled by default with "
49
+ after_generated_url:
50
+ ". You can choose your own URL. </i>"
51
+ generated_title:
52
+ "your-post-title"
53
+ published_at:
54
+ "Published at"
55
+ published:
56
+ "Published"
57
+ save:
58
+ "Save"
59
+ preview:
60
+ "Preview"
61
+ sessions:
62
+ new:
63
+ title:
64
+ "Sign in"
65
+ email:
66
+ "Email"
67
+ password:
68
+ "Password"
69
+ button:
70
+ "Log in"
71
+ messages:
72
+ invalid:
73
+ "Invalid email or password"
74
+ logged_in:
75
+ "Logged in!"
76
+ logged_out:
77
+ "Logged out!"
78
+ layouts:
79
+ monologue:
80
+ admin:
81
+ nav_bar:
82
+ add_a_monologue:
83
+ "Add a monologue"
84
+ list_monologues:
85
+ "List of monologues"
86
+ comments:
87
+ "Comments"
88
+ logged_in_as:
89
+ "Logged in as"
90
+ log_out:
91
+ "Log out"
92
+ activerecord:
93
+ errors:
94
+ format: "%{message}"
95
+ errors:
96
+ full_messages: "%{message}"
97
+ errors:
98
+ models:
99
+ full_messages: "%{message}"
100
+ monologue/posts_revision:
101
+ blank:
102
+ "%{attribute} is required"
103
+ attributes:
104
+ published_at:
105
+ blank:
106
+ "'Published at' is required"
@@ -0,0 +1,106 @@
1
+ fr:
2
+ monologue:
3
+ posts:
4
+ pagination:
5
+ older_posts:
6
+ "Articles précédents"
7
+ newer_posts:
8
+ "Articles plus récents"
9
+ social_sharing:
10
+ tagline:
11
+ "Vous avez ce que vous avez lu? Partagez le!"
12
+ index:
13
+ readmore:
14
+ "Lire"
15
+ "404":
16
+ title:
17
+ "La page que vous cherchiez n'existe pas."
18
+ message:
19
+ "You pourriez avoir mal tappé l'adresse ou la page pourrait avoir été déplacée."
20
+ admin:
21
+ posts:
22
+ index:
23
+ title:
24
+ "Titre"
25
+ edit:
26
+ "Modifier"
27
+ delete:
28
+ "Effacer"
29
+ published:
30
+ "Oui"
31
+ not_published:
32
+ "Non publié"
33
+ status:
34
+ "Publié ?"
35
+ new:
36
+ header:
37
+ "Nouveau monologue"
38
+ edit:
39
+ header:
40
+ "Modifier"
41
+ form:
42
+ title:
43
+ "Titre"
44
+ content:
45
+ "Contenu"
46
+ url:
47
+ before_generated_url:
48
+ "Adresse URL <br /><i> Ce sera rempli par défaut avec "
49
+ after_generated_url:
50
+ ". Vous pouvez aussi choisir votre propre adresse URL. </i>"
51
+ generated_title:
52
+ "nom-de-votre-article"
53
+ published_at:
54
+ "Publié le"
55
+ published:
56
+ "Publié"
57
+ save:
58
+ "Sauvegarder"
59
+ preview:
60
+ "Aperçu"
61
+ sessions:
62
+ new:
63
+ title:
64
+ "Authentification"
65
+ email:
66
+ "Courriel"
67
+ password:
68
+ "Mot de passe"
69
+ button:
70
+ "Connexion"
71
+ messages:
72
+ invalid:
73
+ "Courriel ou mot de passe invalide"
74
+ logged_in:
75
+ "Connecté!"
76
+ logged_out:
77
+ "Déconnecté!"
78
+ layouts:
79
+ monologue:
80
+ admin:
81
+ nav_bar:
82
+ add_a_monologue:
83
+ "Ajouter un monologue"
84
+ list_monologues:
85
+ "Liste des monologues"
86
+ comments:
87
+ "Commentaires"
88
+ logged_in_as:
89
+ "Connecté en tant que"
90
+ log_out:
91
+ "Déconnexion"
92
+ activerecord:
93
+ errors:
94
+ format: "%{message}"
95
+ errors:
96
+ full_messages: "%{message}"
97
+ errors:
98
+ models:
99
+ full_messages: "%{message}"
100
+ monologue/posts_revision:
101
+ blank:
102
+ "%{attribute} est requis"
103
+ attributes:
104
+ published_at:
105
+ blank:
106
+ "'Publié le' est requis"
data/config/routes.rb ADDED
@@ -0,0 +1,15 @@
1
+ Monologue::Engine.routes.draw do
2
+ root :to => "posts#index"
3
+ match "/page/:page", :to => "posts#index", :as => "posts_page"
4
+ match "/feed" => "posts#feed", :as => "feed", :defaults => {:format => :rss}
5
+
6
+ namespace :admin, :path => "monologue" do
7
+ get "/" => "posts#index", :as => "" # responds to admin_url and admin_path
8
+ get "logout" => "sessions#destroy"
9
+ get "login" => "sessions#new"
10
+ resources :sessions
11
+ resources :posts
12
+ end
13
+
14
+ match "*post_url" => "posts#show", :as => "post"
15
+ end
@@ -0,0 +1,11 @@
1
+ class CreateMonologueUsers < ActiveRecord::Migration
2
+ def change
3
+ create_table :monologue_users do |t|
4
+ t.string :name
5
+ t.string :email
6
+ t.string :password_digest
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ class CreateMonologuePostsRevisions < ActiveRecord::Migration
2
+ def change
3
+ create_table :monologue_posts_revisions do |t|
4
+ t.string :title
5
+ t.text :content
6
+ t.string :url
7
+ t.integer :user_id
8
+ t.integer :post_id
9
+ t.datetime :published_at
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :monologue_posts_revisions, :id, :unique => true
15
+ add_index :monologue_posts_revisions, :published_at
16
+ add_index :monologue_posts_revisions, :post_id
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ class CreateMonologuePosts < ActiveRecord::Migration
2
+ def change
3
+ create_table :monologue_posts do |t|
4
+ t.integer :posts_revision_id
5
+ t.boolean :published
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
data/db/seeds.rb ADDED
@@ -0,0 +1 @@
1
+ Monologue::User.create!({:name => "Monologue", :email => "monologue@example.com", :password => "monologue", :password_confirmation => "monologue"})
data/lib/monologue.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "monologue/engine"
2
+
3
+ module Monologue
4
+ mattr_accessor :disqus_shortname,
5
+ :site_name,
6
+ :site_subtitle,
7
+ :site_url,
8
+ :meta_description,
9
+ :meta_keyword,
10
+ :twitter_username,
11
+ :twitter_locale,
12
+ :facebook_like_locale,
13
+ :google_plusone_locale,
14
+ :admin_force_ssl,
15
+ :posts_per_page,
16
+ :google_analytics_id
17
+ end
@@ -0,0 +1,11 @@
1
+ require "tinymce-rails"
2
+ module Monologue
3
+ class Engine < Rails::Engine
4
+ isolate_namespace Monologue
5
+
6
+ config.generators.test_framework :rspec, :view_specs => false, :fixture => false
7
+ config.generators.stylesheets false
8
+ config.generators.fixture_replacement :factory_girl
9
+ config.generators.integration_tool :rspec
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Monologue
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :monologue do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,9 @@
1
+ // French localized datepicker for bootstrap
2
+ $.fn.datepicker.defaults_fr = {
3
+ monthNames: ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin",
4
+ "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Decembre"]
5
+ , shortDayNames: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"]
6
+ , startOfWeek: 1
7
+ };
8
+
9
+ $.fn.datepicker.defaults_fr_CA = $.fn.datepicker.defaults_fr; // For french-canadian locale
@@ -0,0 +1,338 @@
1
+ /* ===========================================================
2
+ * bootstrap-datepicker.js v1.3.0
3
+ * http://twitter.github.com/bootstrap/javascript.html#datepicker
4
+ * ===========================================================
5
+ * Copyright 2011 Twitter, Inc.
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ *
19
+ * Contributed by Scott Torborg - github.com/storborg
20
+ * Loosely based on jquery.date_input.js by Jon Leighton, heavily updated and
21
+ * rewritten to match bootstrap javascript approach and add UI features.
22
+ * =========================================================== */
23
+
24
+
25
+ !function ( $ ) {
26
+
27
+ var selector = '[data-datepicker]',
28
+ all = [];
29
+
30
+ function clearDatePickers(except) {
31
+ var ii;
32
+ for(ii = 0; ii < all.length; ii++) {
33
+ if(all[ii] != except) {
34
+ all[ii].hide();
35
+ }
36
+ }
37
+ }
38
+
39
+ function DatePicker( element, options ) {
40
+ this.$el = $(element);
41
+ this.proxy('show').proxy('ahead').proxy('hide').proxy('keyHandler').proxy('selectDate');
42
+
43
+ var options = $.extend({}, $.fn.datepicker.defaults, options );
44
+
45
+ if((!!options.parse) || (!!options.format) || !this.detectNative()) {
46
+ $.extend(this, options);
47
+ this.$el.data('datepicker', this);
48
+ all.push(this);
49
+ this.init();
50
+ }
51
+ }
52
+
53
+ DatePicker.prototype = {
54
+
55
+ detectNative: function(el) {
56
+ // Attempt to activate the native datepicker, if there is a known good
57
+ // one. If successful, return true. Note that input type="date"
58
+ // requires that the string be RFC3339, so if the format/parse methods
59
+ // have been overridden, this won't be used.
60
+ if(navigator.userAgent.match(/(iPad|iPhone); CPU(\ iPhone)? OS 5_\d/i)) {
61
+ // jQuery will only change the input type of a detached element.
62
+ var $marker = $('<span>').insertBefore(this.$el);
63
+ this.$el.detach().attr('type', 'date').insertAfter($marker);
64
+ $marker.remove();
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+
70
+ , init: function() {
71
+ var $months = this.nav('months', 1);
72
+ var $years = this.nav('years', 12);
73
+
74
+ var $nav = $('<div>').addClass('nav').append($months, $years);
75
+
76
+ this.$month = $('.name', $months);
77
+ this.$year = $('.name', $years);
78
+
79
+ $calendar = $("<div>").addClass('calendar');
80
+
81
+ // Populate day of week headers, realigned by startOfWeek.
82
+ for (var i = 0; i < this.shortDayNames.length; i++) {
83
+ $calendar.append('<div class="dow">' + this.shortDayNames[(i + this.startOfWeek) % 7] + '</div>');
84
+ };
85
+
86
+ this.$days = $('<div>').addClass('days');
87
+ $calendar.append(this.$days);
88
+
89
+ this.$picker = $('<div>')
90
+ .click(function(e) { e.stopPropagation() })
91
+ // Use this to prevent accidental text selection.
92
+ .mousedown(function(e) { e.preventDefault() })
93
+ .addClass('datepicker')
94
+ .append($nav, $calendar)
95
+ .insertAfter(this.$el);
96
+
97
+ this.$el
98
+ .focus(this.show)
99
+ .click(this.show)
100
+ .change($.proxy(function() { this.selectDate(); }, this));
101
+
102
+ this.selectDate();
103
+ this.hide();
104
+ }
105
+
106
+ , nav: function( c, months ) {
107
+ var $subnav = $('<div>' +
108
+ '<span class="prev button">&larr;</span>' +
109
+ '<span class="name"></span>' +
110
+ '<span class="next button">&rarr;</span>' +
111
+ '</div>').addClass(c)
112
+ $('.prev', $subnav).click($.proxy(function() { this.ahead(-months, 0) }, this));
113
+ $('.next', $subnav).click($.proxy(function() { this.ahead(months, 0) }, this));
114
+ return $subnav;
115
+
116
+ }
117
+
118
+ , updateName: function($area, s) {
119
+ // Update either the month or year field, with a background flash
120
+ // animation.
121
+ var cur = $area.find('.fg').text(),
122
+ $fg = $('<div>').addClass('fg').append(s);
123
+ $area.empty();
124
+ if(cur != s) {
125
+ var $bg = $('<div>').addClass('bg');
126
+ $area.append($bg, $fg);
127
+ $bg.fadeOut('slow', function() {
128
+ $(this).remove();
129
+ });
130
+ } else {
131
+ $area.append($fg);
132
+ }
133
+ }
134
+
135
+ , selectMonth: function(date) {
136
+ var newMonth = new Date(date.getFullYear(), date.getMonth(), 1);
137
+
138
+ if (!this.curMonth || !(this.curMonth.getFullYear() == newMonth.getFullYear() &&
139
+ this.curMonth.getMonth() == newMonth.getMonth())) {
140
+
141
+ this.curMonth = newMonth;
142
+
143
+ var rangeStart = this.rangeStart(date), rangeEnd = this.rangeEnd(date);
144
+ var num_days = this.daysBetween(rangeStart, rangeEnd);
145
+ this.$days.empty();
146
+
147
+ for (var ii = 0; ii <= num_days; ii++) {
148
+ var thisDay = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + ii, 12, 00);
149
+ var $day = $('<div>').attr('date', this.format(thisDay));
150
+ $day.text(thisDay.getDate());
151
+
152
+ if (thisDay.getMonth() != date.getMonth()) {
153
+ $day.addClass('overlap');
154
+ };
155
+
156
+ this.$days.append($day);
157
+ };
158
+
159
+ this.updateName(this.$month, this.monthNames[date.getMonth()]);
160
+ this.updateName(this.$year, this.curMonth.getFullYear());
161
+
162
+ $('div', this.$days).click($.proxy(function(e) {
163
+ var $targ = $(e.target);
164
+
165
+ // The date= attribute is used here to provide relatively fast
166
+ // selectors for setting certain date cells.
167
+ this.update($targ.get(0).getAttribute("date"));
168
+
169
+ // Don't consider this selection final if we're just going to an
170
+ // adjacent month.
171
+ if(!$targ.hasClass('overlap')) {
172
+ this.hide();
173
+ }
174
+
175
+ }, this));
176
+
177
+ $("[date='" + this.format(new Date()) + "']", this.$days).addClass('today');
178
+
179
+ };
180
+
181
+ $('.selected', this.$days).removeClass('selected');
182
+ $('[date="' + this.selectedDateStr + '"]', this.$days).addClass('selected');
183
+ }
184
+
185
+ , selectDate: function(date) {
186
+ if (typeof(date) == "undefined") {
187
+ date = this.parse(this.$el.val());
188
+ };
189
+ if (!date) date = new Date();
190
+
191
+ this.selectedDate = date;
192
+ this.selectedDateStr = this.format(this.selectedDate);
193
+ this.selectMonth(this.selectedDate);
194
+ }
195
+
196
+ , update: function(s) {
197
+ this.$el.val(s).change();
198
+ }
199
+
200
+ , show: function(e) {
201
+ e && e.stopPropagation();
202
+
203
+ // Hide all other datepickers.
204
+ clearDatePickers(this);
205
+
206
+ var offset = this.$el.offset();
207
+
208
+ this.$picker.css({
209
+ top: offset.top + this.$el.outerHeight() + 2,
210
+ left: offset.left
211
+ }).show();
212
+
213
+ $('html').on('keydown', this.keyHandler);
214
+ }
215
+
216
+ , hide: function() {
217
+ this.$picker.hide();
218
+ $('html').off('keydown', this.keyHandler);
219
+ }
220
+
221
+ , keyHandler: function(e) {
222
+ // Keyboard navigation shortcuts.
223
+ switch (e.keyCode)
224
+ {
225
+ case 9:
226
+ case 27:
227
+ // Tab or escape hides the datepicker. In this case, just return
228
+ // instead of breaking, so that the e doesn't get stopped.
229
+ this.hide(); return;
230
+ case 13:
231
+ // Enter selects the currently highlighted date.
232
+ this.update(this.selectedDateStr); this.hide(); break;
233
+ case 38:
234
+ // Arrow up goes to prev week.
235
+ this.ahead(0, -7); break;
236
+ case 40:
237
+ // Arrow down goes to next week.
238
+ this.ahead(0, 7); break;
239
+ case 37:
240
+ // Arrow left goes to prev day.
241
+ this.ahead(0, -1); break;
242
+ case 39:
243
+ // Arrow right goes to next day.
244
+ this.ahead(0, 1); break;
245
+ default:
246
+ return;
247
+ }
248
+ e.preventDefault();
249
+ }
250
+
251
+ , parse: function(s) {
252
+ // Parse a partial RFC 3339 string into a Date.
253
+ var m;
254
+ if ((m = s.match(/^(\d{4,4})-(\d{2,2})-(\d{2,2})$/))) {
255
+ return new Date(m[1], m[2] - 1, m[3]);
256
+ } else {
257
+ return null;
258
+ }
259
+ }
260
+
261
+ , format: function(date) {
262
+ // Format a Date into a string as specified by RFC 3339.
263
+ var month = (date.getMonth() + 1).toString(),
264
+ dom = date.getDate().toString();
265
+ if (month.length === 1) {
266
+ month = '0' + month;
267
+ }
268
+ if (dom.length === 1) {
269
+ dom = '0' + dom;
270
+ }
271
+ return date.getFullYear() + '-' + month + "-" + dom;
272
+ }
273
+
274
+ , ahead: function(months, days) {
275
+ // Move ahead ``months`` months and ``days`` days, both integers, can be
276
+ // negative.
277
+ this.selectDate(new Date(this.selectedDate.getFullYear(),
278
+ this.selectedDate.getMonth() + months,
279
+ this.selectedDate.getDate() + days));
280
+ }
281
+
282
+ , proxy: function(meth) {
283
+ // Bind a method so that it always gets the datepicker instance for
284
+ // ``this``. Return ``this`` so chaining calls works.
285
+ this[meth] = $.proxy(this[meth], this);
286
+ return this;
287
+ }
288
+
289
+ , daysBetween: function(start, end) {
290
+ // Return number of days between ``start`` Date object and ``end``.
291
+ var start = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());
292
+ var end = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate());
293
+ return (end - start) / 86400000;
294
+ }
295
+
296
+ , findClosest: function(dow, date, direction) {
297
+ // From a starting date, find the first day ahead of behind it that is
298
+ // a given day of the week.
299
+ var difference = direction * (Math.abs(date.getDay() - dow - (direction * 7)) % 7);
300
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + difference);
301
+ }
302
+
303
+ , rangeStart: function(date) {
304
+ // Get the first day to show in the current calendar view.
305
+ return this.findClosest(this.startOfWeek,
306
+ new Date(date.getFullYear(), date.getMonth()),
307
+ -1);
308
+ }
309
+
310
+ , rangeEnd: function(date) {
311
+ // Get the last day to show in the current calendar view.
312
+ return this.findClosest((this.startOfWeek - 1) % 7,
313
+ new Date(date.getFullYear(), date.getMonth() + 1, 0),
314
+ 1);
315
+ }
316
+ };
317
+
318
+ /* DATEPICKER PLUGIN DEFINITION
319
+ * ============================ */
320
+
321
+ $.fn.datepicker = function( options ) {
322
+ return this.each(function() { new DatePicker(this, options); });
323
+ };
324
+
325
+ $(function() {
326
+ $(selector).datepicker();
327
+ $('html').click(clearDatePickers);
328
+ });
329
+
330
+ $.fn.datepicker.DatePicker = DatePicker;
331
+
332
+ $.fn.datepicker.defaults = {
333
+ monthNames: ["January", "February", "March", "April", "May", "June",
334
+ "July", "August", "September", "October", "November", "December"]
335
+ , shortDayNames: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
336
+ , startOfWeek: 1
337
+ };
338
+ }( window.jQuery || window.ender );