monologue 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +73 -0
- data/Rakefile +32 -0
- data/app/assets/javascripts/monologue/admin/application.js +13 -0
- data/app/assets/javascripts/monologue/admin/tinymce-config.js +21 -0
- data/app/assets/javascripts/monologue/blog/application.js +9 -0
- data/app/assets/stylesheets/monologue/admin/application.css +10 -0
- data/app/assets/stylesheets/monologue/blog/application.css +13 -0
- data/app/assets/stylesheets/monologue/blog/custom.css +1 -0
- data/app/assets/stylesheets/monologue/blog/fonts.css +1 -0
- data/app/assets/stylesheets/monologue/blog/monologue.css +37 -0
- data/app/assets/stylesheets/monologue/blog/skeleton/base.css +343 -0
- data/app/assets/stylesheets/monologue/blog/skeleton/layout.css +58 -0
- data/app/assets/stylesheets/monologue/blog/skeleton/skeleton.css +242 -0
- data/app/controllers/monologue/admin/base_controller.rb +12 -0
- data/app/controllers/monologue/admin/posts_controller.rb +54 -0
- data/app/controllers/monologue/admin/sessions_controller.rb +22 -0
- data/app/controllers/monologue/application_controller.rb +22 -0
- data/app/controllers/monologue/posts_controller.rb +25 -0
- data/app/form_builders/monologue_admin_form_builder.rb +61 -0
- data/app/helpers/monologue/admin/admin_helper.rb +4 -0
- data/app/helpers/monologue/application_helper.rb +8 -0
- data/app/helpers/monologue/posts_helper.rb +4 -0
- data/app/helpers/monologue/sessions_helper.rb +6 -0
- data/app/models/monologue/post.rb +40 -0
- data/app/models/monologue/posts_revision.rb +34 -0
- data/app/models/monologue/user.rb +6 -0
- data/app/sweepers/monologue/posts_sweeper.rb +28 -0
- data/app/views/layouts/monologue/_google_analytics.html.erb +15 -0
- data/app/views/layouts/monologue/admin.html.erb +21 -0
- data/app/views/layouts/monologue/admin/_nav_bar.html.erb +24 -0
- data/app/views/layouts/monologue/application.html.erb +60 -0
- data/app/views/monologue/admin/posts/_form.html.erb +16 -0
- data/app/views/monologue/admin/posts/edit.html.erb +4 -0
- data/app/views/monologue/admin/posts/index.html.erb +20 -0
- data/app/views/monologue/admin/posts/new.html.erb +4 -0
- data/app/views/monologue/admin/sessions/new.html.erb +13 -0
- data/app/views/monologue/posts/404.html.erb +9 -0
- data/app/views/monologue/posts/_pagination.html.erb +9 -0
- data/app/views/monologue/posts/_social_sharing.html.erb +42 -0
- data/app/views/monologue/posts/feed.rss.builder +19 -0
- data/app/views/monologue/posts/index.html.erb +18 -0
- data/app/views/monologue/posts/show.html.erb +38 -0
- data/config/locales/en.yml +106 -0
- data/config/locales/fr.yml +106 -0
- data/config/routes.rb +15 -0
- data/db/migrate/20120114001001_create_monologue_users.rb +11 -0
- data/db/migrate/20120120193858_create_monologue_posts_revisions.rb +18 -0
- data/db/migrate/20120120193907_create_monologue_posts.rb +10 -0
- data/db/seeds.rb +1 -0
- data/lib/monologue.rb +17 -0
- data/lib/monologue/engine.rb +11 -0
- data/lib/monologue/version.rb +3 -0
- data/lib/tasks/monologue_tasks.rake +4 -0
- data/vendor/assets/images/monologue/bootstrap/glyphicons-halflings-white.png +0 -0
- data/vendor/assets/images/monologue/bootstrap/glyphicons-halflings.png +0 -0
- data/vendor/assets/javascripts/monologue/bootstrap/bootstrap-datepicker-fr.js +9 -0
- data/vendor/assets/javascripts/monologue/bootstrap/bootstrap-datepicker.js +338 -0
- data/vendor/assets/javascripts/monologue/bootstrap/bootstrap.min.js +1 -0
- data/vendor/assets/stylesheets/monologue/bootstrap/bootstrap-datepicker.css +130 -0
- data/vendor/assets/stylesheets/monologue/bootstrap/bootstrap-responsive.min.css +3 -0
- data/vendor/assets/stylesheets/monologue/bootstrap/bootstrap.min.css +610 -0
- 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> | <%= @revision.user.name %> | <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,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
|
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
|
Binary file
|
@@ -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">←</span>' +
|
109
|
+
'<span class="name"></span>' +
|
110
|
+
'<span class="next button">→</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 );
|