kryptonite 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/PUBLIC_VERSION.yml +1 -1
  2. data/README.rdoc +6 -1
  3. data/Rakefile +2 -0
  4. data/app/controllers/kryptonite/kryptonite_controller.rb +27 -1
  5. data/app/controllers/kryptonite/password_resets_controller.rb +6 -6
  6. data/app/controllers/kryptonite/user_sessions_controller.rb +2 -2
  7. data/app/controllers/kryptonite/users_controller.rb +17 -17
  8. data/app/mailers/kryptonite/kryptonite_notification.rb +3 -3
  9. data/app/models/kryptonite/user.rb +2 -0
  10. data/app/views/kryptonite/kryptonite/blank.html.erb +1 -1
  11. data/app/views/kryptonite/kryptonite_notification/generate_new_password.erb +5 -5
  12. data/app/views/kryptonite/kryptonite_notification/new_user_information.erb +5 -5
  13. data/app/views/kryptonite/kryptonite_notification/password_reset_instructions.erb +4 -5
  14. data/app/views/kryptonite/password_resets/edit.html.erb +3 -3
  15. data/app/views/kryptonite/user_sessions/new.html.erb +7 -7
  16. data/app/views/kryptonite/users/index.html.erb +7 -7
  17. data/app/views/kryptonite/users/new.html.erb +6 -6
  18. data/app/views/kryptonite/users/show.html.erb +13 -13
  19. data/app/views/layouts/kryptonite_auth.html.erb +2 -3
  20. data/app/views/layouts/kryptonite_main.html.erb +4 -4
  21. data/config/locales/en.yml +81 -0
  22. data/lib/generators/kryptonite/install/templates/public/{casein → kryptonite}/javascripts/custom.js +0 -0
  23. data/lib/generators/kryptonite/install/templates/public/{casein → kryptonite}/stylesheets/custom.css +0 -0
  24. data/lib/generators/kryptonite/scaffold/scaffold_generator.rb +1 -0
  25. data/lib/generators/kryptonite/scaffold/templates/controller.rb +34 -18
  26. data/lib/generators/kryptonite/scaffold/templates/en.yml +7 -0
  27. data/lib/generators/kryptonite/scaffold/templates/views/_table.html.erb +1 -1
  28. data/lib/generators/kryptonite/scaffold/templates/views/index.html.erb +2 -2
  29. data/lib/generators/kryptonite/scaffold/templates/views/new.html.erb +4 -4
  30. data/lib/generators/kryptonite/scaffold/templates/views/show.html.erb +4 -4
  31. data/lib/generators/kryptonite/update/templates/public/kryptonite/images/kryptonite.png +0 -0
  32. data/lib/generators/kryptonite/update/templates/public/kryptonite/images/loading.gif +0 -0
  33. data/lib/generators/kryptonite/update/templates/public/kryptonite/javascripts/fileuploader.js +1247 -0
  34. data/lib/generators/kryptonite/update/templates/public/kryptonite/stylesheets/fileuploader.css +51 -0
  35. data/lib/generators/kryptonite/update/update_generator.rb +3 -0
  36. metadata +42 -5
  37. data/lib/generators/kryptonite/update/templates/public/kryptonite/images/casein.png +0 -0
@@ -1,4 +1,4 @@
1
- <h2>Add a new user</h2>
1
+ <h2><%=t :add_new_user %></h2>
2
2
 
3
3
  <%= form_for @kryptonite_user do |form| %>
4
4
 
@@ -19,28 +19,28 @@
19
19
  </div>
20
20
 
21
21
  <div class="tfContainer">
22
- <%= kryptonite_select form, @kryptonite_user, :access_level, kryptonite_get_access_level_array, {:kryptonite_label => "Status"} %>
22
+ <%= kryptonite_select form, @kryptonite_user, :access_level, kryptonite_get_access_level_array, {:kryptonite_label => t(:status)} %>
23
23
  </div>
24
24
 
25
25
  <div class="tfContainer tfContainerSecond">
26
26
  </div>
27
27
 
28
28
  <div class="tfContainer">
29
- <%= kryptonite_password_field form, @kryptonite_user, :password, {:kryptonite_label => "Password"} %>
29
+ <%= kryptonite_password_field form, @kryptonite_user, :password, {:kryptonite_label => t(:password)} %>
30
30
  </div>
31
31
 
32
32
  <div class="tfContainer tfContainerSecond">
33
- <%= kryptonite_password_field form, @kryptonite_user, :password_confirmation, {:kryptonite_label => "Repeat password"} %>
33
+ <%= kryptonite_password_field form, @kryptonite_user, :password_confirmation, {:kryptonite_label => t(:confirm_password)} %>
34
34
  </div>
35
35
 
36
36
  <div class="clearer"></div>
37
37
 
38
38
  <p class="submits">
39
- <%= form.submit "Add new user", :class => "update" %>
39
+ <%= form.submit t(:add_new_user), :class => "update" %>
40
40
  </p>
41
41
 
42
42
  <% end %>
43
43
 
44
44
  <%= content_for :sidebar do %>
45
- <li><%= link_to "#{kryptonite_show_icon('table')}Back to list".html_safe, kryptonite_users_path %></li>
45
+ <li><%= link_to "#{kryptonite_show_icon('table')}#{t(:back_to_list)}".html_safe, kryptonite_users_path %></li>
46
46
  <% end %>
@@ -20,7 +20,7 @@
20
20
 
21
21
  <% if @session_user.is_admin? && (@kryptonite_user.is_admin? == false || Kryptonite::User.has_more_than_one_admin) %>
22
22
  <div class="tfContainer">
23
- <%= kryptonite_select f, f.object, :access_level, kryptonite_get_access_level_array, {:kryptonite_label => "Status"} %>
23
+ <%= kryptonite_select f, f.object, :access_level, kryptonite_get_access_level_array, {:kryptonite_label => t(:status)} %>
24
24
  </div>
25
25
  <div class="tfContainer tfContainerSecond">
26
26
  </div>
@@ -29,41 +29,41 @@
29
29
  <div class="clearer"></div>
30
30
 
31
31
  <p class="submits">
32
- <%= link_to 'Cancel', kryptonite_users_path, :class => 'cancel' %> or
33
- <%= f.submit "Save changes", :class => "update" %>
32
+ <%= link_to t(:cancel), kryptonite_users_path, :class => 'cancel' %> or
33
+ <%= f.submit t(:save_changes), :class => "update" %>
34
34
  </p>
35
35
  <% end %>
36
36
 
37
37
  <% if @session_user.is_admin? %>
38
38
 
39
- <h2>Reset password (admin action - use carefully)</h2>
39
+ <h2><%=t :reset_password_title %></h2>
40
40
 
41
41
  <%= form_for @kryptonite_user, :url => reset_password_kryptonite_user_path(@kryptonite_user) do |f| %>
42
42
 
43
43
  <div class="tfContainer">
44
- <%= kryptonite_password_field f, f.object, :password, {:kryptonite_label => "New password"} %>
44
+ <%= kryptonite_password_field f, f.object, :password, {:kryptonite_label => t(:new_password)} %>
45
45
  </div>
46
46
 
47
47
  <div class="tfContainer tfContainerSecond">
48
- <%= kryptonite_password_field f, f.object, :password_confirmation, {:kryptonite_label => "Repeat password"} %>
48
+ <%= kryptonite_password_field f, f.object, :password_confirmation, {:kryptonite_label => t(:confirm_password)} %>
49
49
  </div>
50
50
 
51
51
  <div class="clearer"></div>
52
52
 
53
53
  <p class="submits">
54
- <%= f.submit "Reset", :class => "update" %>
54
+ <%= f.submit t(:do_reset), :class => "update" %>
55
55
  </p>
56
56
 
57
57
  <% end %>
58
58
 
59
59
  <% elsif @session_user.id == @kryptonite_user.id %>
60
60
 
61
- <h2>Change password</h2>
61
+ <h2><%=t :change_password %></h2>
62
62
 
63
63
  <%= form_for @kryptonite_user, :url => update_password_kryptonite_user_path(@kryptonite_user) do |f| %>
64
64
 
65
65
  <div class="tfContainer">
66
- <p><%= label_tag :form_current_password, "Current password" %></p>
66
+ <p><%= label_tag :form_current_password, t(:current_password) %></p>
67
67
  <p><%= password_field_tag :form_current_password, "", {:class => 'kryptoniteTextField'} %></p>
68
68
  </div>
69
69
 
@@ -71,17 +71,17 @@
71
71
  </div>
72
72
 
73
73
  <div class="tfContainer">
74
- <%= kryptonite_password_field f, f.object, :password, {:kryptonite_label => "New password"} %>
74
+ <%= kryptonite_password_field f, f.object, :password, {:kryptonite_label => t(:new_password)} %>
75
75
  </div>
76
76
 
77
77
  <div class="tfContainer tfContainerSecond">
78
- <%= kryptonite_password_field f, f.object, :password_confirmation, {:kryptonite_label => "Repeat password"} %>
78
+ <%= kryptonite_password_field f, f.object, :password_confirmation, {:kryptonite_label => t(:confirm_password)} %>
79
79
  </div>
80
80
 
81
81
  <div class="clearer"></div>
82
82
 
83
83
  <p class="submits">
84
- <%= f.submit "Change", :class => "update" %>
84
+ <%= f.submit t(:change), :class => "update" %>
85
85
  </p>
86
86
 
87
87
  <% end %>
@@ -89,6 +89,6 @@
89
89
 
90
90
  <%= content_for :sidebar do %>
91
91
  <% if @session_user.is_admin? %>
92
- <li><%= link_to "#{kryptonite_show_icon('table')}Back to list".html_safe, kryptonite_users_path %></li>
92
+ <li><%= link_to "#{kryptonite_show_icon('table')}#{t(:back_to_list)}".html_safe, kryptonite_users_path %></li>
93
93
  <% end %>
94
94
  <% end %>
@@ -4,7 +4,7 @@
4
4
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
5
  <head>
6
6
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
7
- <title>Welcome to <%= kryptonite_config_website_name %></title>
7
+ <title><%=t "welcome_title", :website_name=>kryptonite_config_website_name %></title>
8
8
  <%= stylesheet_link_tag "/kryptonite/stylesheets/login" %>
9
9
  <%= javascript_include_tag "/kryptonite/javascripts/jquery", "/kryptonite/javascripts/login", "/kryptonite/javascripts/rails" %>
10
10
  <%= csrf_meta_tag %>
@@ -17,8 +17,7 @@
17
17
  <%= yield %>
18
18
  </div>
19
19
  <div id="footer">
20
- <p>Based on <%= link_to "Kryptonite", "http://www.kryptonitecms.com" %> <%= kryptonite_get_short_version_string %> technology,
21
- an open-source project initiated by <a href="http://www.spoiledmilk.dk">Spoiled Milk</a>.</p>
20
+ <p>Based on <%= link_to "Kryptonite", "http://www.m2hero.com" %> <%= kryptonite_get_short_version_string %> technology.</p>
22
21
  </div>
23
22
  </body>
24
23
  </html>
@@ -21,10 +21,10 @@
21
21
  <div id="dashboard">
22
22
  <% if @session_user != nil %>
23
23
  <ul>
24
- <li>Hello <%= @session_user.name %></li>
24
+ <li><%=t :hello_user, :user=>@session_user.name %></li>
25
25
  <li><%= Time.zone.name %></li>
26
- <li><%= link_to 'Profile', kryptonite_user_path(@session_user) %></li>
27
- <li class="lastItem"><%= link_to 'Logout', kryptonite_user_session_path, :method => :delete %></li>
26
+ <li><%= link_to t(:profile), kryptonite_user_path(@session_user) %></li>
27
+ <li class="lastItem"><%= link_to t(:logout), kryptonite_user_session_path, :method => :delete %></li>
28
28
  </ul>
29
29
  <% end %>
30
30
  </div>
@@ -36,7 +36,7 @@
36
36
  <div id="altNavigation">
37
37
  <ul>
38
38
  <% if @session_user.is_admin? %>
39
- <li><%= link_to 'Users', kryptonite_users_path %></li>
39
+ <li><%= link_to t(:users), kryptonite_users_path %></li>
40
40
  <% end %>
41
41
  <%= render :partial => '/kryptonite/layouts/right_navigation' %>
42
42
  </ul>
@@ -0,0 +1,81 @@
1
+ en:
2
+ welcome_title: "Hi, to the KS of %{website_name}"
3
+ home_page_title: "Home"
4
+ multiple_acctounts_notice: "Multiple accounts were found. Emails have been sent to %{email} with instructions on how to reset your passwords"
5
+ email_sent_notice: "An email has been sent to %{email} with instructions on how to reset your password"
6
+ no_user_warning: "There is no user with that email"
7
+ field_empty_warning: "A field has been left empty"
8
+ password_updated_notice: "Password successfully updated"
9
+ account_not_located_warning: "Your account could not be located. Try to copy and paste the URL directly from the email."
10
+ login_successful: "Login successful"
11
+ logout_successful: "Logout successful"
12
+ users_page_title: "Users"
13
+ users_add_new: "Add a new user"
14
+ users_email_sent_with_details: "An email has been sent to %{username} with the new account details"
15
+ users_problems_saving_warning: "There were problems when trying to create a new user"
16
+ users_show: "View User"
17
+ users_update: "Update User"
18
+ users_updated_notice: "%{username} has been updated"
19
+ users_problems_updating: "There were problems when trying to update this user"
20
+ users_update_password: "Update Password"
21
+ users_password_changed_notice: "Your password has been changed"
22
+ users_problems_changing_password: "There were problems when trying to change the password"
23
+ users_password_incorrect: "The current password is incorrect"
24
+ users_reset_password: "Reset Password"
25
+ users_password_reseted: "Your password has been reset"
26
+ users_password_reseted_and_emailed: "Password has been reset and %{username} has been notified by email"
27
+ users_problems_reseting_password: "There were problems when trying to reset this user's password"
28
+ users_deleted: "%{username} has been deleted"
29
+ new_password_subject: "[%{website_name}] New password"
30
+ new_account_subject: "[%{website_name}] New user account"
31
+ password_reset_subject: "[%{website_name}] Password reset instructions"
32
+ greeting: "Dear %{name},"
33
+ password_reset_by_admin: "Your password has been reset by an administrator for your account at"
34
+ login: "Login"
35
+ new_password: "New password"
36
+ from: "From"
37
+ new_account_created: "A new user account has been created for you at"
38
+ reset_instructions: "A request to reset the password for login '%{login}' has been made.<br />If you did not make this request, simply ignore this email.<br />Otherwise, click the link below to proceed:"
39
+ password: "Password"
40
+ confirm_password: "Confirm password"
41
+ reset_password: "Reset password"
42
+ remember_me: "Remember me"
43
+ do_login: "Log in"
44
+ forgotten_password: "Forgotten password?"
45
+ email: "Email"
46
+ do_reset: "Reset"
47
+ name: "Name"
48
+ status: "Status"
49
+ delete_user_confirm: "Are you sure you want to delete %{user}?"
50
+ warning_delete_admin: "\n\nWARNING: This is your user account. You will be logged out if you proceed."
51
+ add_new_user: "Add a new user"
52
+ users: "Users"
53
+ add_new_user: "Add a new user"
54
+ back_to_list: "Back to list"
55
+ cancel: "Cancel"
56
+ save_changes: "Save changes"
57
+ reset_password_title: "Reset password (admin action - use carefully)"
58
+ change_password: "Change password"
59
+ current_password: "Current password"
60
+ change: "Change"
61
+ hello_user: "Hello %{user}"
62
+ profile: "Profile"
63
+ logout: "Logout"
64
+ scaffold_view: "View %{model}"
65
+ scaffold_add_new: "Add a new %{model}"
66
+ scaffold_created: "%{model} created"
67
+ scaffold_problems_creating: "There were problems when trying to create a new %{model}"
68
+ scaffold_update: "Update %{model}"
69
+ scaffold_updated: "%{model} has been updated"
70
+ scaffold_problems_updating: "There were problems when trying to update this %{model}"
71
+ scaffold_deleted: "%{model} has been deleted"
72
+ scaffold_destroy_confirmation: "Are you sure you want to delete this %{model}?"
73
+ scaffold_add_new: "Add a new %{model}"
74
+ scaffold_cancel: "Cancel"
75
+ scaffold_back_to_list: "Back to list"
76
+ scaffold_save_changes: "Save Changes"
77
+
78
+
79
+
80
+
81
+
@@ -20,6 +20,7 @@ module Kryptonite
20
20
  def generate_files
21
21
  @plural_route = (plural_name != singular_name) ? plural_name : "#{plural_name}_index"
22
22
 
23
+ template 'en.yml', "config/locales/kryptonite/#{plural_name}/en.yml"
23
24
  template 'controller.rb', "app/controllers/kryptonite/#{plural_name}_controller.rb"
24
25
  template 'views/index.html.erb', "app/views/kryptonite/#{plural_name}/index.html.erb"
25
26
  template 'views/show.html.erb', "app/views/kryptonite/#{plural_name}/show.html.erb"
@@ -8,43 +8,59 @@ module Kryptonite
8
8
  # before_filter :needs_admin_or_current_user, :only => [:action1, :action2]
9
9
 
10
10
  def index
11
- @kryptonite_page_title = '<%= plural_name.humanize.capitalize %>'
11
+ @kryptonite_page_title = t("kryptonite.<%= plural_name %>.plural_name_cap")
12
12
  @<%= plural_name %> = <%= class_name %>.paginate :page => params[:page]
13
13
  end
14
14
 
15
15
  def show
16
- @kryptonite_page_title = 'View <%= singular_name.humanize.downcase %>'
16
+ @kryptonite_page_title = t("scaffold_view", :model=>t("kryptonite.<%= plural_name %>.singular_name_down"))
17
17
  @<%= singular_name %> = <%= class_name %>.find params[:id]
18
18
  end
19
19
 
20
20
  def new
21
- @kryptonite_page_title = 'Add a new <%= singular_name.humanize.downcase %>'
21
+ @kryptonite_page_title = t("scaffold_add_new", :model=>t("kryptonite.<%= plural_name %>.singular_name_down"))
22
22
  @<%= singular_name %> = <%= class_name %>.new
23
23
  end
24
24
 
25
25
  def create
26
26
  @<%= singular_name %> = <%= class_name %>.new params[:<%= singular_name %>]
27
-
28
- if @<%= singular_name %>.save
29
- flash[:notice] = '<%= singular_name.humanize.capitalize %> created'
30
- redirect_to kryptonite_<%= @plural_route %>_path
31
- else
32
- flash.now[:warning] = 'There were problems when trying to create a new <%= singular_name.humanize.downcase %>'
33
- render :action => :new
27
+
28
+ respond_to do |format|
29
+ if @<%= singular_name %>.save
30
+ format.html {
31
+ flash[:notice] = t("scaffold_created", :model=>t("kryptonite.<%= plural_name %>.singular_name_cap"))
32
+ redirect_to kryptonite_<%= @plural_route %>_path
33
+ }
34
+ format.json { respond_with_bip(@<%= singular_name %>) }
35
+ else
36
+ format.html {
37
+ flash.now[:warning] = t("scaffold_problems_creating", :model=>t("kryptonite.<%= plural_name %>.singular_name_down"))
38
+ render :action => :new
39
+ }
40
+ format.json { respond_with_bip(@<%= singular_name %>) }
41
+ end
34
42
  end
35
43
  end
36
44
 
37
45
  def update
38
- @kryptonite_page_title = 'Update <%= singular_name.humanize.downcase %>'
46
+ @kryptonite_page_title = t("scaffold_update", :model=>t("kryptonite.<%= plural_name %>.singular_name_down"))
39
47
 
40
48
  @<%= singular_name %> = <%= class_name %>.find params[:id]
41
49
 
42
- if @<%= singular_name %>.update_attributes params[:<%= singular_name %>]
43
- flash[:notice] = '<%= singular_name.humanize.capitalize %> has been updated'
44
- redirect_to kryptonite_<%= @plural_route %>_path
45
- else
46
- flash.now[:warning] = 'There were problems when trying to update this <%= singular_name.humanize.downcase %>'
47
- render :action => :show
50
+ respond_to do |format|
51
+ if @<%= singular_name %>.update_attributes params[:<%= singular_name %>]
52
+ format.html {
53
+ flash[:notice] = t("scaffold_updated", :model=>t("kryptonite.<%= plural_name %>.singular_name_cap"))
54
+ redirect_to kryptonite_<%= @plural_route %>_path
55
+ }
56
+ format.json { respond_with_bip(@<%= singular_name %>) }
57
+ else
58
+ format.html {
59
+ flash.now[:warning] = t("scaffold_problems_updating", :model=>t("kryptonite.<%= plural_name %>.singular_name_down"))
60
+ render :action => :show
61
+ }
62
+ format.json { respond_with_bip(@<%= singular_name %>) }
63
+ end
48
64
  end
49
65
  end
50
66
 
@@ -52,7 +68,7 @@ module Kryptonite
52
68
  @<%= singular_name %> = <%= class_name %>.find params[:id]
53
69
 
54
70
  @<%= singular_name %>.destroy
55
- flash[:notice] = '<%= singular_name.humanize.capitalize %> has been deleted'
71
+ flash[:notice] = t("scaffold_deleted", :model=>t("kryptonite.<%= plural_name %>.singular_name_cap"))
56
72
  redirect_to kryptonite_<%= @plural_route %>_path
57
73
  end
58
74
 
@@ -0,0 +1,7 @@
1
+ en:
2
+ kryptonite:
3
+ <%= plural_name.humanize.downcase %>:
4
+ singular_name_down: <%= singular_name.humanize.downcase %>
5
+ singular_name_cap: <%= singular_name.humanize.capitalize %>
6
+ plural_name_down: <%= plural_name.humanize.downcase %>
7
+ plural_name_cap: <%= plural_name.humanize.capitalize %>
@@ -10,7 +10,7 @@
10
10
  <tr class="<%%= 'even' if index.modulo(2) == 0 %> hover">
11
11
  <% attributes.each do |attribute| %><td><%%= kryptonite_table_cell_link <%= singular_name %>.<%= attribute.name %>, kryptonite_<%= singular_name %>_path(<%= singular_name %>) %></td>
12
12
  <% end %><td class="delete">
13
- <%%= link_to(kryptonite_show_row_icon("delete"), kryptonite_<%= singular_name %>_path(<%= singular_name %>), :method => :delete, :confirm => "Are you sure you want to delete this <%= singular_name.humanize.downcase %>?") %>
13
+ <%%= link_to(kryptonite_show_row_icon("delete"), kryptonite_<%= singular_name %>_path(<%= singular_name %>), :method => :delete, :confirm => t("scaffold_destroy_confirmation", :model=>t("kryptonite.<%= plural_name %>.singular_name_down"))) %>
14
14
  </td>
15
15
  </tr>
16
16
  <%% end %>
@@ -1,11 +1,11 @@
1
1
  <!-- Scaffolding generated by Kryptonite <%= kryptonite_get_full_version_string %> -->
2
2
 
3
- <h2><%= plural_name.humanize.capitalize %></h2>
3
+ <h2><%%= t("kryptonite.<%= plural_name %>.plural_name_cap") %></h2>
4
4
 
5
5
  <%%= render :partial => 'kryptonite/<%= plural_name %>/table', :locals => { :<%= plural_name %> => @<%= plural_name %> } %>
6
6
 
7
7
  <%%= will_paginate @<%= plural_name %> %>
8
8
 
9
9
  <%%= content_for :sidebar do %>
10
- <li><%%= link_to "#{kryptonite_show_icon "add"}Add a new <%= singular_name.humanize.downcase %>".html_safe, new_kryptonite_<%= singular_name %>_path %></li>
10
+ <li><%%= link_to "#{kryptonite_show_icon "add"}#{t("scaffold_add_new", :model=>t("kryptonite.<%= plural_name %>.singular_name_down"))}".html_safe, new_kryptonite_<%= singular_name %>_path %></li>
11
11
  <%% end %>
@@ -1,18 +1,18 @@
1
1
  <!-- Scaffolding generated by Kryptonite <%= kryptonite_get_full_version_string %> -->
2
2
 
3
- <h2>Add a new <%= singular_name.humanize.downcase %></h2>
3
+ <h2><%%=t "scaffold.add_new", :model=>t("kryptonite.<%= plural_name %>.singular_name_down") %></h2>
4
4
 
5
5
  <%%= form_for @<%= file_name %>, :url => kryptonite_<%= @plural_route %>_path do |f| %>
6
6
 
7
7
  <%%= render :partial => 'kryptonite/<%= plural_name %>/form', :locals => { :f => f } %>
8
8
 
9
9
  <p class="submits">
10
- <%%= link_to 'Cancel', kryptonite_<%= @plural_route %>_path, :class => 'cancel' %> or
11
- <%%= f.submit "Add new <%= singular_name.humanize.downcase %>", :class => "update" %>
10
+ <%%= link_to t("scaffold_cancel"), kryptonite_<%= @plural_route %>_path, :class => 'cancel' %> or
11
+ <%%= f.submit t("scaffold_add_new", :model=>t("kryptonite.<%= plural_name %>.singular_name_down")), :class => "update" %>
12
12
  </p>
13
13
 
14
14
  <%% end %>
15
15
 
16
16
  <%%= content_for :sidebar do %>
17
- <li><%%= link_to "#{kryptonite_show_icon "table"}Back to list".html_safe, kryptonite_<%= @plural_route %>_path %></li>
17
+ <li><%%= link_to "#{kryptonite_show_icon "table"}#{t("scaffold_back_to_list")}".html_safe, kryptonite_<%= @plural_route %>_path %></li>
18
18
  <%% end %>
@@ -1,18 +1,18 @@
1
1
  <!-- Scaffolding generated by Kryptonite <%= kryptonite_get_full_version_string %> -->
2
2
 
3
- <h2>View <%= singular_name.humanize.downcase %></h2>
3
+ <h2><%%=t "scaffold.view", :model=>t("kryptonite.<%= plural_name %>.singular_name_down") %></h2>
4
4
 
5
5
  <%%= form_for @<%= file_name %>, :url => kryptonite_<%= singular_name %>_path(@<%= singular_name %>) do |f| %>
6
6
 
7
7
  <%%= render :partial => 'kryptonite/<%= plural_name %>/form', :locals => { :f => f } %>
8
8
 
9
9
  <p class="submits">
10
- <%%= link_to 'Cancel', kryptonite_<%= @plural_route %>_path, :class => 'cancel' %> or
11
- <%%= f.submit "Save changes", :class => "update" %>
10
+ <%%= link_to t("scaffold_cancel"), kryptonite_<%= @plural_route %>_path, :class => 'cancel' %> or
11
+ <%%= f.submit t("scaffold_save_changes"), :class => "update" %>
12
12
  </p>
13
13
 
14
14
  <%% end %>
15
15
 
16
16
  <%%= content_for :sidebar do %>
17
- <li><%%= link_to "#{kryptonite_show_icon "table"}Back to list".html_safe, kryptonite_<%= @plural_route %>_path %></li>
17
+ <li><%%= link_to "#{kryptonite_show_icon "table"}#{t("scaffold_back_to_list")}".html_safe, kryptonite_<%= @plural_route %>_path %></li>
18
18
  <%% end %>
@@ -0,0 +1,1247 @@
1
+ /**
2
+ * http://github.com/valums/file-uploader
3
+ *
4
+ * Multiple file upload component with progress-bar, drag-and-drop.
5
+ * © 2010 Andrew Valums ( andrew(at)valums.com )
6
+ *
7
+ * Licensed under GNU GPL 2 or later and GNU LGPL 2 or later, see license.txt.
8
+ */
9
+
10
+ //
11
+ // Helper functions
12
+ //
13
+
14
+ var qq = qq || {};
15
+
16
+ /**
17
+ * Adds all missing properties from second obj to first obj
18
+ */
19
+ qq.extend = function(first, second){
20
+ for (var prop in second){
21
+ first[prop] = second[prop];
22
+ }
23
+ };
24
+
25
+ /**
26
+ * Searches for a given element in the array, returns -1 if it is not present.
27
+ * @param {Number} [from] The index at which to begin the search
28
+ */
29
+ qq.indexOf = function(arr, elt, from){
30
+ if (arr.indexOf) return arr.indexOf(elt, from);
31
+
32
+ from = from || 0;
33
+ var len = arr.length;
34
+
35
+ if (from < 0) from += len;
36
+
37
+ for (; from < len; from++){
38
+ if (from in arr && arr[from] === elt){
39
+ return from;
40
+ }
41
+ }
42
+ return -1;
43
+ };
44
+
45
+ qq.getUniqueId = (function(){
46
+ var id = 0;
47
+ return function(){ return id++; };
48
+ })();
49
+
50
+ //
51
+ // Events
52
+
53
+ qq.attach = function(element, type, fn){
54
+ if (element.addEventListener){
55
+ element.addEventListener(type, fn, false);
56
+ } else if (element.attachEvent){
57
+ element.attachEvent('on' + type, fn);
58
+ }
59
+ };
60
+ qq.detach = function(element, type, fn){
61
+ if (element.removeEventListener){
62
+ element.removeEventListener(type, fn, false);
63
+ } else if (element.attachEvent){
64
+ element.detachEvent('on' + type, fn);
65
+ }
66
+ };
67
+
68
+ qq.preventDefault = function(e){
69
+ if (e.preventDefault){
70
+ e.preventDefault();
71
+ } else{
72
+ e.returnValue = false;
73
+ }
74
+ };
75
+
76
+ //
77
+ // Node manipulations
78
+
79
+ /**
80
+ * Insert node a before node b.
81
+ */
82
+ qq.insertBefore = function(a, b){
83
+ b.parentNode.insertBefore(a, b);
84
+ };
85
+ qq.remove = function(element){
86
+ element.parentNode.removeChild(element);
87
+ };
88
+
89
+ qq.contains = function(parent, descendant){
90
+ // compareposition returns false in this case
91
+ if (parent == descendant) return true;
92
+
93
+ if (parent.contains){
94
+ return parent.contains(descendant);
95
+ } else {
96
+ return !!(descendant.compareDocumentPosition(parent) & 8);
97
+ }
98
+ };
99
+
100
+ /**
101
+ * Creates and returns element from html string
102
+ * Uses innerHTML to create an element
103
+ */
104
+ qq.toElement = (function(){
105
+ var div = document.createElement('div');
106
+ return function(html){
107
+ div.innerHTML = html;
108
+ var element = div.firstChild;
109
+ div.removeChild(element);
110
+ return element;
111
+ };
112
+ })();
113
+
114
+ //
115
+ // Node properties and attributes
116
+
117
+ /**
118
+ * Sets styles for an element.
119
+ * Fixes opacity in IE6-8.
120
+ */
121
+ qq.css = function(element, styles){
122
+ if (styles.opacity != null){
123
+ if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
124
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
125
+ }
126
+ }
127
+ qq.extend(element.style, styles);
128
+ };
129
+ qq.hasClass = function(element, name){
130
+ var re = new RegExp('(^| )' + name + '( |$)');
131
+ return re.test(element.className);
132
+ };
133
+ qq.addClass = function(element, name){
134
+ if (!qq.hasClass(element, name)){
135
+ element.className += ' ' + name;
136
+ }
137
+ };
138
+ qq.removeClass = function(element, name){
139
+ var re = new RegExp('(^| )' + name + '( |$)');
140
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
141
+ };
142
+ qq.setText = function(element, text){
143
+ element.innerText = text;
144
+ element.textContent = text;
145
+ };
146
+
147
+ //
148
+ // Selecting elements
149
+
150
+ qq.children = function(element){
151
+ var children = [],
152
+ child = element.firstChild;
153
+
154
+ while (child){
155
+ if (child.nodeType == 1){
156
+ children.push(child);
157
+ }
158
+ child = child.nextSibling;
159
+ }
160
+
161
+ return children;
162
+ };
163
+
164
+ qq.getByClass = function(element, className){
165
+ if (element.querySelectorAll){
166
+ return element.querySelectorAll('.' + className);
167
+ }
168
+
169
+ var result = [];
170
+ var candidates = element.getElementsByTagName("*");
171
+ var len = candidates.length;
172
+
173
+ for (var i = 0; i < len; i++){
174
+ if (qq.hasClass(candidates[i], className)){
175
+ result.push(candidates[i]);
176
+ }
177
+ }
178
+ return result;
179
+ };
180
+
181
+ /**
182
+ * obj2url() takes a json-object as argument and generates
183
+ * a querystring. pretty much like jQuery.param()
184
+ *
185
+ * how to use:
186
+ *
187
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
188
+ *
189
+ * will result in:
190
+ *
191
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
192
+ *
193
+ * @param Object JSON-Object
194
+ * @param String current querystring-part
195
+ * @return String encoded querystring
196
+ */
197
+ qq.obj2url = function(obj, temp, prefixDone){
198
+ var uristrings = [],
199
+ prefix = '&',
200
+ add = function(nextObj, i){
201
+ var nextTemp = temp
202
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
203
+ ? temp
204
+ : temp+'['+i+']'
205
+ : i;
206
+ if ((nextTemp != 'undefined') && (i != 'undefined')) {
207
+ uristrings.push(
208
+ (typeof nextObj === 'object')
209
+ ? qq.obj2url(nextObj, nextTemp, true)
210
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
211
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
212
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
213
+ );
214
+ }
215
+ };
216
+
217
+ if (!prefixDone && temp) {
218
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
219
+ uristrings.push(temp);
220
+ uristrings.push(qq.obj2url(obj));
221
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
222
+ // we wont use a for-in-loop on an array (performance)
223
+ for (var i = 0, len = obj.length; i < len; ++i){
224
+ add(obj[i], i);
225
+ }
226
+ } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
227
+ // for anything else but a scalar, we will use for-in-loop
228
+ for (var i in obj){
229
+ add(obj[i], i);
230
+ }
231
+ } else {
232
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
233
+ }
234
+
235
+ return uristrings.join(prefix)
236
+ .replace(/^&/, '')
237
+ .replace(/%20/g, '+');
238
+ };
239
+
240
+ //
241
+ //
242
+ // Uploader Classes
243
+ //
244
+ //
245
+
246
+ var qq = qq || {};
247
+
248
+ /**
249
+ * Creates upload button, validates upload, but doesn't create file list or dd.
250
+ */
251
+ qq.FileUploaderBasic = function(o){
252
+ this._options = {
253
+ // set to true to see the server response
254
+ debug: false,
255
+ action: '/server/upload',
256
+ params: {},
257
+ button: null,
258
+ multiple: true,
259
+ maxConnections: 3,
260
+ // validation
261
+ allowedExtensions: [],
262
+ sizeLimit: 0,
263
+ minSizeLimit: 0,
264
+ // events
265
+ // return false to cancel submit
266
+ onSubmit: function(id, fileName){},
267
+ onProgress: function(id, fileName, loaded, total){},
268
+ onComplete: function(id, fileName, responseJSON){},
269
+ onCancel: function(id, fileName){},
270
+ // messages
271
+ messages: {
272
+ typeError: "{file} has invalid extension. Only {extensions} are allowed.",
273
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
274
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
275
+ emptyError: "{file} is empty, please select files again without it.",
276
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
277
+ },
278
+ showMessage: function(message){
279
+ alert(message);
280
+ }
281
+ };
282
+ qq.extend(this._options, o);
283
+
284
+ // number of files being uploaded
285
+ this._filesInProgress = 0;
286
+ this._handler = this._createUploadHandler();
287
+
288
+ if (this._options.button){
289
+ this._button = this._createUploadButton(this._options.button);
290
+ }
291
+
292
+ this._preventLeaveInProgress();
293
+ };
294
+
295
+ qq.FileUploaderBasic.prototype = {
296
+ setParams: function(params){
297
+ this._options.params = params;
298
+ },
299
+ getInProgress: function(){
300
+ return this._filesInProgress;
301
+ },
302
+ _createUploadButton: function(element){
303
+ var self = this;
304
+
305
+ return new qq.UploadButton({
306
+ element: element,
307
+ multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
308
+ onChange: function(input){
309
+ self._onInputChange(input);
310
+ }
311
+ });
312
+ },
313
+ _createUploadHandler: function(){
314
+ var self = this,
315
+ handlerClass;
316
+
317
+ if(qq.UploadHandlerXhr.isSupported()){
318
+ handlerClass = 'UploadHandlerXhr';
319
+ } else {
320
+ handlerClass = 'UploadHandlerForm';
321
+ }
322
+
323
+ var handler = new qq[handlerClass]({
324
+ debug: this._options.debug,
325
+ action: this._options.action,
326
+ maxConnections: this._options.maxConnections,
327
+ onProgress: function(id, fileName, loaded, total){
328
+ self._onProgress(id, fileName, loaded, total);
329
+ self._options.onProgress(id, fileName, loaded, total);
330
+ },
331
+ onComplete: function(id, fileName, result){
332
+ self._onComplete(id, fileName, result);
333
+ self._options.onComplete(id, fileName, result);
334
+ },
335
+ onCancel: function(id, fileName){
336
+ self._onCancel(id, fileName);
337
+ self._options.onCancel(id, fileName);
338
+ }
339
+ });
340
+
341
+ return handler;
342
+ },
343
+ _preventLeaveInProgress: function(){
344
+ var self = this;
345
+
346
+ qq.attach(window, 'beforeunload', function(e){
347
+ if (!self._filesInProgress){return;}
348
+
349
+ var e = e || window.event;
350
+ // for ie, ff
351
+ e.returnValue = self._options.messages.onLeave;
352
+ // for webkit
353
+ return self._options.messages.onLeave;
354
+ });
355
+ },
356
+ _onSubmit: function(id, fileName){
357
+ this._filesInProgress++;
358
+ },
359
+ _onProgress: function(id, fileName, loaded, total){
360
+ },
361
+ _onComplete: function(id, fileName, result){
362
+ this._filesInProgress--;
363
+ if (result.error){
364
+ this._options.showMessage(result.error);
365
+ }
366
+ },
367
+ _onCancel: function(id, fileName){
368
+ this._filesInProgress--;
369
+ },
370
+ _onInputChange: function(input){
371
+ if (this._handler instanceof qq.UploadHandlerXhr){
372
+ this._uploadFileList(input.files);
373
+ } else {
374
+ if (this._validateFile(input)){
375
+ this._uploadFile(input);
376
+ }
377
+ }
378
+ this._button.reset();
379
+ },
380
+ _uploadFileList: function(files){
381
+ for (var i=0; i<files.length; i++){
382
+ if ( !this._validateFile(files[i])){
383
+ return;
384
+ }
385
+ }
386
+
387
+ for (var i=0; i<files.length; i++){
388
+ this._uploadFile(files[i]);
389
+ }
390
+ },
391
+ _uploadFile: function(fileContainer){
392
+ var id = this._handler.add(fileContainer);
393
+ var fileName = this._handler.getName(id);
394
+
395
+ if (this._options.onSubmit(id, fileName) !== false){
396
+ this._onSubmit(id, fileName);
397
+ this._handler.upload(id, this._options.params);
398
+ }
399
+ },
400
+ _validateFile: function(file){
401
+ var name, size;
402
+
403
+ if (file.value){
404
+ // it is a file input
405
+ // get input value and remove path to normalize
406
+ name = file.value.replace(/.*(\/|\\)/, "");
407
+ } else {
408
+ // fix missing properties in Safari
409
+ name = file.fileName != null ? file.fileName : file.name;
410
+ size = file.fileSize != null ? file.fileSize : file.size;
411
+ }
412
+
413
+ if (! this._isAllowedExtension(name)){
414
+ this._error('typeError', name);
415
+ return false;
416
+
417
+ } else if (size === 0){
418
+ this._error('emptyError', name);
419
+ return false;
420
+
421
+ } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){
422
+ this._error('sizeError', name);
423
+ return false;
424
+
425
+ } else if (size && size < this._options.minSizeLimit){
426
+ this._error('minSizeError', name);
427
+ return false;
428
+ }
429
+
430
+ return true;
431
+ },
432
+ _error: function(code, fileName){
433
+ var message = this._options.messages[code];
434
+ function r(name, replacement){ message = message.replace(name, replacement); }
435
+
436
+ r('{file}', this._formatFileName(fileName));
437
+ r('{extensions}', this._options.allowedExtensions.join(', '));
438
+ r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
439
+ r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
440
+
441
+ this._options.showMessage(message);
442
+ },
443
+ _formatFileName: function(name){
444
+ if (name.length > 33){
445
+ name = name.slice(0, 19) + '...' + name.slice(-13);
446
+ }
447
+ return name;
448
+ },
449
+ _isAllowedExtension: function(fileName){
450
+ var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
451
+ var allowed = this._options.allowedExtensions;
452
+
453
+ if (!allowed.length){return true;}
454
+
455
+ for (var i=0; i<allowed.length; i++){
456
+ if (allowed[i].toLowerCase() == ext){ return true;}
457
+ }
458
+
459
+ return false;
460
+ },
461
+ _formatSize: function(bytes){
462
+ var i = -1;
463
+ do {
464
+ bytes = bytes / 1024;
465
+ i++;
466
+ } while (bytes > 99);
467
+
468
+ return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
469
+ }
470
+ };
471
+
472
+
473
+ /**
474
+ * Class that creates upload widget with drag-and-drop and file list
475
+ * @inherits qq.FileUploaderBasic
476
+ */
477
+ qq.FileUploader = function(o){
478
+ // call parent constructor
479
+ qq.FileUploaderBasic.apply(this, arguments);
480
+
481
+ // additional options
482
+ qq.extend(this._options, {
483
+ element: null,
484
+ // if set, will be used instead of qq-upload-list in template
485
+ listElement: null,
486
+
487
+ template: '<div class="qq-uploader">' +
488
+ '<div class="qq-upload-drop-area"><span>Drop files here to upload</span></div>' +
489
+ '<div class="qq-upload-button">Upload a file</div>' +
490
+ '<ul class="qq-upload-list"></ul>' +
491
+ '</div>',
492
+
493
+ // template for one item in file list
494
+ fileTemplate: '<li>' +
495
+ '<span class="qq-upload-file"></span>' +
496
+ '<span class="qq-upload-spinner"></span>' +
497
+ '<span class="qq-upload-size"></span>' +
498
+ '<a class="qq-upload-cancel" href="#">Cancel</a>' +
499
+ '<span class="qq-upload-failed-text">Failed</span>' +
500
+ '</li>',
501
+
502
+ classes: {
503
+ // used to get elements from templates
504
+ button: 'qq-upload-button',
505
+ drop: 'qq-upload-drop-area',
506
+ dropActive: 'qq-upload-drop-area-active',
507
+ list: 'qq-upload-list',
508
+
509
+ file: 'qq-upload-file',
510
+ spinner: 'qq-upload-spinner',
511
+ size: 'qq-upload-size',
512
+ cancel: 'qq-upload-cancel',
513
+
514
+ // added to list item when upload completes
515
+ // used in css to hide progress spinner
516
+ success: 'qq-upload-success',
517
+ fail: 'qq-upload-fail'
518
+ }
519
+ });
520
+ // overwrite options with user supplied
521
+ qq.extend(this._options, o);
522
+
523
+ this._element = this._options.element;
524
+ this._element.innerHTML = this._options.template;
525
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
526
+
527
+ this._classes = this._options.classes;
528
+
529
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
530
+
531
+ this._bindCancelEvent();
532
+ this._setupDragDrop();
533
+ };
534
+
535
+ // inherit from Basic Uploader
536
+ qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
537
+
538
+ qq.extend(qq.FileUploader.prototype, {
539
+ /**
540
+ * Gets one of the elements listed in this._options.classes
541
+ **/
542
+ _find: function(parent, type){
543
+ var element = qq.getByClass(parent, this._options.classes[type])[0];
544
+ if (!element){
545
+ throw new Error('element not found ' + type);
546
+ }
547
+
548
+ return element;
549
+ },
550
+ _setupDragDrop: function(){
551
+ var self = this,
552
+ dropArea = this._find(this._element, 'drop');
553
+
554
+ var dz = new qq.UploadDropZone({
555
+ element: dropArea,
556
+ onEnter: function(e){
557
+ qq.addClass(dropArea, self._classes.dropActive);
558
+ e.stopPropagation();
559
+ },
560
+ onLeave: function(e){
561
+ e.stopPropagation();
562
+ },
563
+ onLeaveNotDescendants: function(e){
564
+ qq.removeClass(dropArea, self._classes.dropActive);
565
+ },
566
+ onDrop: function(e){
567
+ dropArea.style.display = 'none';
568
+ qq.removeClass(dropArea, self._classes.dropActive);
569
+ self._uploadFileList(e.dataTransfer.files);
570
+ }
571
+ });
572
+
573
+ dropArea.style.display = 'none';
574
+
575
+ qq.attach(document, 'dragenter', function(e){
576
+ if (!dz._isValidFileDrag(e)) return;
577
+
578
+ dropArea.style.display = 'block';
579
+ });
580
+ qq.attach(document, 'dragleave', function(e){
581
+ if (!dz._isValidFileDrag(e)) return;
582
+
583
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
584
+ // only fire when leaving document out
585
+ if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){
586
+ dropArea.style.display = 'none';
587
+ }
588
+ });
589
+ },
590
+ _onSubmit: function(id, fileName){
591
+ qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
592
+ this._addToList(id, fileName);
593
+ },
594
+ _onProgress: function(id, fileName, loaded, total){
595
+ qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
596
+
597
+ var item = this._getItemByFileId(id);
598
+ var size = this._find(item, 'size');
599
+ size.style.display = 'inline';
600
+
601
+ var text;
602
+ if (loaded != total){
603
+ text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);
604
+ } else {
605
+ text = this._formatSize(total);
606
+ }
607
+
608
+ qq.setText(size, text);
609
+ },
610
+ _onComplete: function(id, fileName, result){
611
+ qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
612
+
613
+ // mark completed
614
+ var item = this._getItemByFileId(id);
615
+ qq.remove(this._find(item, 'cancel'));
616
+ qq.remove(this._find(item, 'spinner'));
617
+
618
+ if (result.success){
619
+ qq.addClass(item, this._classes.success);
620
+ } else {
621
+ qq.addClass(item, this._classes.fail);
622
+ }
623
+ },
624
+ _addToList: function(id, fileName){
625
+ var item = qq.toElement(this._options.fileTemplate);
626
+ item.qqFileId = id;
627
+
628
+ var fileElement = this._find(item, 'file');
629
+ qq.setText(fileElement, this._formatFileName(fileName));
630
+ this._find(item, 'size').style.display = 'none';
631
+
632
+ this._listElement.appendChild(item);
633
+ },
634
+ _getItemByFileId: function(id){
635
+ var item = this._listElement.firstChild;
636
+
637
+ // there can't be txt nodes in dynamically created list
638
+ // and we can use nextSibling
639
+ while (item){
640
+ if (item.qqFileId == id) return item;
641
+ item = item.nextSibling;
642
+ }
643
+ },
644
+ /**
645
+ * delegate click event for cancel link
646
+ **/
647
+ _bindCancelEvent: function(){
648
+ var self = this,
649
+ list = this._listElement;
650
+
651
+ qq.attach(list, 'click', function(e){
652
+ e = e || window.event;
653
+ var target = e.target || e.srcElement;
654
+
655
+ if (qq.hasClass(target, self._classes.cancel)){
656
+ qq.preventDefault(e);
657
+
658
+ var item = target.parentNode;
659
+ self._handler.cancel(item.qqFileId);
660
+ qq.remove(item);
661
+ }
662
+ });
663
+ }
664
+ });
665
+
666
+ qq.UploadDropZone = function(o){
667
+ this._options = {
668
+ element: null,
669
+ onEnter: function(e){},
670
+ onLeave: function(e){},
671
+ // is not fired when leaving element by hovering descendants
672
+ onLeaveNotDescendants: function(e){},
673
+ onDrop: function(e){}
674
+ };
675
+ qq.extend(this._options, o);
676
+
677
+ this._element = this._options.element;
678
+
679
+ this._disableDropOutside();
680
+ this._attachEvents();
681
+ };
682
+
683
+ qq.UploadDropZone.prototype = {
684
+ _disableDropOutside: function(e){
685
+ // run only once for all instances
686
+ if (!qq.UploadDropZone.dropOutsideDisabled ){
687
+
688
+ qq.attach(document, 'dragover', function(e){
689
+ if (e.dataTransfer){
690
+ e.dataTransfer.dropEffect = 'none';
691
+ e.preventDefault();
692
+ }
693
+ });
694
+
695
+ qq.UploadDropZone.dropOutsideDisabled = true;
696
+ }
697
+ },
698
+ _attachEvents: function(){
699
+ var self = this;
700
+
701
+ qq.attach(self._element, 'dragover', function(e){
702
+ if (!self._isValidFileDrag(e)) return;
703
+
704
+ var effect = e.dataTransfer.effectAllowed;
705
+ if (effect == 'move' || effect == 'linkMove'){
706
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
707
+ } else {
708
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
709
+ }
710
+
711
+ e.stopPropagation();
712
+ e.preventDefault();
713
+ });
714
+
715
+ qq.attach(self._element, 'dragenter', function(e){
716
+ if (!self._isValidFileDrag(e)) return;
717
+
718
+ self._options.onEnter(e);
719
+ });
720
+
721
+ qq.attach(self._element, 'dragleave', function(e){
722
+ if (!self._isValidFileDrag(e)) return;
723
+
724
+ self._options.onLeave(e);
725
+
726
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
727
+ // do not fire when moving a mouse over a descendant
728
+ if (qq.contains(this, relatedTarget)) return;
729
+
730
+ self._options.onLeaveNotDescendants(e);
731
+ });
732
+
733
+ qq.attach(self._element, 'drop', function(e){
734
+ if (!self._isValidFileDrag(e)) return;
735
+
736
+ e.preventDefault();
737
+ self._options.onDrop(e);
738
+ });
739
+ },
740
+ _isValidFileDrag: function(e){
741
+ var dt = e.dataTransfer,
742
+ // do not check dt.types.contains in webkit, because it crashes safari 4
743
+ isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;
744
+
745
+ // dt.effectAllowed is none in Safari 5
746
+ // dt.types.contains check is for firefox
747
+ return dt && dt.effectAllowed != 'none' &&
748
+ (dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));
749
+
750
+ }
751
+ };
752
+
753
+ qq.UploadButton = function(o){
754
+ this._options = {
755
+ element: null,
756
+ // if set to true adds multiple attribute to file input
757
+ multiple: false,
758
+ // name attribute of file input
759
+ name: 'file',
760
+ onChange: function(input){},
761
+ hoverClass: 'qq-upload-button-hover',
762
+ focusClass: 'qq-upload-button-focus'
763
+ };
764
+
765
+ qq.extend(this._options, o);
766
+
767
+ this._element = this._options.element;
768
+
769
+ // make button suitable container for input
770
+ qq.css(this._element, {
771
+ position: 'relative',
772
+ overflow: 'hidden',
773
+ // Make sure browse button is in the right side
774
+ // in Internet Explorer
775
+ direction: 'ltr'
776
+ });
777
+
778
+ this._input = this._createInput();
779
+ };
780
+
781
+ qq.UploadButton.prototype = {
782
+ /* returns file input element */
783
+ getInput: function(){
784
+ return this._input;
785
+ },
786
+ /* cleans/recreates the file input */
787
+ reset: function(){
788
+ if (this._input.parentNode){
789
+ qq.remove(this._input);
790
+ }
791
+
792
+ qq.removeClass(this._element, this._options.focusClass);
793
+ this._input = this._createInput();
794
+ },
795
+ _createInput: function(){
796
+ var input = document.createElement("input");
797
+
798
+ if (this._options.multiple){
799
+ input.setAttribute("multiple", "multiple");
800
+ }
801
+
802
+ input.setAttribute("type", "file");
803
+ input.setAttribute("name", this._options.name);
804
+
805
+ qq.css(input, {
806
+ position: 'absolute',
807
+ // in Opera only 'browse' button
808
+ // is clickable and it is located at
809
+ // the right side of the input
810
+ right: 0,
811
+ top: 0,
812
+ fontFamily: 'Arial',
813
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
814
+ fontSize: '118px',
815
+ margin: 0,
816
+ padding: 0,
817
+ cursor: 'pointer',
818
+ opacity: 0
819
+ });
820
+
821
+ this._element.appendChild(input);
822
+
823
+ var self = this;
824
+ qq.attach(input, 'change', function(){
825
+ self._options.onChange(input);
826
+ });
827
+
828
+ qq.attach(input, 'mouseover', function(){
829
+ qq.addClass(self._element, self._options.hoverClass);
830
+ });
831
+ qq.attach(input, 'mouseout', function(){
832
+ qq.removeClass(self._element, self._options.hoverClass);
833
+ });
834
+ qq.attach(input, 'focus', function(){
835
+ qq.addClass(self._element, self._options.focusClass);
836
+ });
837
+ qq.attach(input, 'blur', function(){
838
+ qq.removeClass(self._element, self._options.focusClass);
839
+ });
840
+
841
+ // IE and Opera, unfortunately have 2 tab stops on file input
842
+ // which is unacceptable in our case, disable keyboard access
843
+ if (window.attachEvent){
844
+ // it is IE or Opera
845
+ input.setAttribute('tabIndex', "-1");
846
+ }
847
+
848
+ return input;
849
+ }
850
+ };
851
+
852
+ /**
853
+ * Class for uploading files, uploading itself is handled by child classes
854
+ */
855
+ qq.UploadHandlerAbstract = function(o){
856
+ this._options = {
857
+ debug: false,
858
+ action: '/upload.php',
859
+ // maximum number of concurrent uploads
860
+ maxConnections: 999,
861
+ onProgress: function(id, fileName, loaded, total){},
862
+ onComplete: function(id, fileName, response){},
863
+ onCancel: function(id, fileName){}
864
+ };
865
+ qq.extend(this._options, o);
866
+
867
+ this._queue = [];
868
+ // params for files in queue
869
+ this._params = [];
870
+ };
871
+ qq.UploadHandlerAbstract.prototype = {
872
+ log: function(str){
873
+ if (this._options.debug && window.console) console.log('[uploader] ' + str);
874
+ },
875
+ /**
876
+ * Adds file or file input to the queue
877
+ * @returns id
878
+ **/
879
+ add: function(file){},
880
+ /**
881
+ * Sends the file identified by id and additional query params to the server
882
+ */
883
+ upload: function(id, params){
884
+ var len = this._queue.push(id);
885
+
886
+ var copy = {};
887
+ qq.extend(copy, params);
888
+ this._params[id] = copy;
889
+
890
+ // if too many active uploads, wait...
891
+ if (len <= this._options.maxConnections){
892
+ this._upload(id, this._params[id]);
893
+ }
894
+ },
895
+ /**
896
+ * Cancels file upload by id
897
+ */
898
+ cancel: function(id){
899
+ this._cancel(id);
900
+ this._dequeue(id);
901
+ },
902
+ /**
903
+ * Cancells all uploads
904
+ */
905
+ cancelAll: function(){
906
+ for (var i=0; i<this._queue.length; i++){
907
+ this._cancel(this._queue[i]);
908
+ }
909
+ this._queue = [];
910
+ },
911
+ /**
912
+ * Returns name of the file identified by id
913
+ */
914
+ getName: function(id){},
915
+ /**
916
+ * Returns size of the file identified by id
917
+ */
918
+ getSize: function(id){},
919
+ /**
920
+ * Returns id of files being uploaded or
921
+ * waiting for their turn
922
+ */
923
+ getQueue: function(){
924
+ return this._queue;
925
+ },
926
+ /**
927
+ * Actual upload method
928
+ */
929
+ _upload: function(id){},
930
+ /**
931
+ * Actual cancel method
932
+ */
933
+ _cancel: function(id){},
934
+ /**
935
+ * Removes element from queue, starts upload of next
936
+ */
937
+ _dequeue: function(id){
938
+ var i = qq.indexOf(this._queue, id);
939
+ this._queue.splice(i, 1);
940
+
941
+ var max = this._options.maxConnections;
942
+
943
+ if (this._queue.length >= max && i < max){
944
+ var nextId = this._queue[max-1];
945
+ this._upload(nextId, this._params[nextId]);
946
+ }
947
+ }
948
+ };
949
+
950
+ /**
951
+ * Class for uploading files using form and iframe
952
+ * @inherits qq.UploadHandlerAbstract
953
+ */
954
+ qq.UploadHandlerForm = function(o){
955
+ qq.UploadHandlerAbstract.apply(this, arguments);
956
+
957
+ this._inputs = {};
958
+ };
959
+ // @inherits qq.UploadHandlerAbstract
960
+ qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
961
+
962
+ qq.extend(qq.UploadHandlerForm.prototype, {
963
+ add: function(fileInput){
964
+ fileInput.setAttribute('name', 'qqfile');
965
+ var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
966
+
967
+ this._inputs[id] = fileInput;
968
+
969
+ // remove file input from DOM
970
+ if (fileInput.parentNode){
971
+ qq.remove(fileInput);
972
+ }
973
+
974
+ return id;
975
+ },
976
+ getName: function(id){
977
+ // get input value and remove path to normalize
978
+ return this._inputs[id].value.replace(/.*(\/|\\)/, "");
979
+ },
980
+ _cancel: function(id){
981
+ this._options.onCancel(id, this.getName(id));
982
+
983
+ delete this._inputs[id];
984
+
985
+ var iframe = document.getElementById(id);
986
+ if (iframe){
987
+ // to cancel request set src to something else
988
+ // we use src="javascript:false;" because it doesn't
989
+ // trigger ie6 prompt on https
990
+ iframe.setAttribute('src', 'javascript:false;');
991
+
992
+ qq.remove(iframe);
993
+ }
994
+ },
995
+ _upload: function(id, params){
996
+ var input = this._inputs[id];
997
+
998
+ if (!input){
999
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
1000
+ }
1001
+
1002
+ var fileName = this.getName(id);
1003
+
1004
+ var iframe = this._createIframe(id);
1005
+ var form = this._createForm(iframe, params);
1006
+ form.appendChild(input);
1007
+
1008
+ var self = this;
1009
+ this._attachLoadEvent(iframe, function(){
1010
+ self.log('iframe loaded');
1011
+
1012
+ var response = self._getIframeContentJSON(iframe);
1013
+
1014
+ self._options.onComplete(id, fileName, response);
1015
+ self._dequeue(id);
1016
+
1017
+ delete self._inputs[id];
1018
+ // timeout added to fix busy state in FF3.6
1019
+ setTimeout(function(){
1020
+ qq.remove(iframe);
1021
+ }, 1);
1022
+ });
1023
+
1024
+ form.submit();
1025
+ qq.remove(form);
1026
+
1027
+ return id;
1028
+ },
1029
+ _attachLoadEvent: function(iframe, callback){
1030
+ qq.attach(iframe, 'load', function(){
1031
+ // when we remove iframe from dom
1032
+ // the request stops, but in IE load
1033
+ // event fires
1034
+ if (!iframe.parentNode){
1035
+ return;
1036
+ }
1037
+
1038
+ // fixing Opera 10.53
1039
+ if (iframe.contentDocument &&
1040
+ iframe.contentDocument.body &&
1041
+ iframe.contentDocument.body.innerHTML == "false"){
1042
+ // In Opera event is fired second time
1043
+ // when body.innerHTML changed from false
1044
+ // to server response approx. after 1 sec
1045
+ // when we upload file with iframe
1046
+ return;
1047
+ }
1048
+
1049
+ callback();
1050
+ });
1051
+ },
1052
+ /**
1053
+ * Returns json object received by iframe from server.
1054
+ */
1055
+ _getIframeContentJSON: function(iframe){
1056
+ // iframe.contentWindow.document - for IE<7
1057
+ var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
1058
+ response;
1059
+
1060
+ this.log("converting iframe's innerHTML to JSON");
1061
+ this.log("innerHTML = " + doc.body.innerHTML);
1062
+
1063
+ try {
1064
+ response = eval("(" + doc.body.innerHTML + ")");
1065
+ } catch(err){
1066
+ response = {};
1067
+ }
1068
+
1069
+ return response;
1070
+ },
1071
+ /**
1072
+ * Creates iframe with unique name
1073
+ */
1074
+ _createIframe: function(id){
1075
+ // We can't use following code as the name attribute
1076
+ // won't be properly registered in IE6, and new window
1077
+ // on form submit will open
1078
+ // var iframe = document.createElement('iframe');
1079
+ // iframe.setAttribute('name', id);
1080
+
1081
+ var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
1082
+ // src="javascript:false;" removes ie6 prompt on https
1083
+
1084
+ iframe.setAttribute('id', id);
1085
+
1086
+ iframe.style.display = 'none';
1087
+ document.body.appendChild(iframe);
1088
+
1089
+ return iframe;
1090
+ },
1091
+ /**
1092
+ * Creates form, that will be submitted to iframe
1093
+ */
1094
+ _createForm: function(iframe, params){
1095
+ // We can't use the following code in IE6
1096
+ // var form = document.createElement('form');
1097
+ // form.setAttribute('method', 'post');
1098
+ // form.setAttribute('enctype', 'multipart/form-data');
1099
+ // Because in this case file won't be attached to request
1100
+ var form = qq.toElement('<form method="post" enctype="multipart/form-data"></form>');
1101
+
1102
+ var queryString = qq.obj2url(params, this._options.action);
1103
+
1104
+ form.setAttribute('action', queryString);
1105
+ form.setAttribute('target', iframe.name);
1106
+ form.style.display = 'none';
1107
+ document.body.appendChild(form);
1108
+
1109
+ return form;
1110
+ }
1111
+ });
1112
+
1113
+ /**
1114
+ * Class for uploading files using xhr
1115
+ * @inherits qq.UploadHandlerAbstract
1116
+ */
1117
+ qq.UploadHandlerXhr = function(o){
1118
+ qq.UploadHandlerAbstract.apply(this, arguments);
1119
+
1120
+ this._files = [];
1121
+ this._xhrs = [];
1122
+
1123
+ // current loaded size in bytes for each file
1124
+ this._loaded = [];
1125
+ };
1126
+
1127
+ // static method
1128
+ qq.UploadHandlerXhr.isSupported = function(){
1129
+ var input = document.createElement('input');
1130
+ input.type = 'file';
1131
+
1132
+ return (
1133
+ 'multiple' in input &&
1134
+ typeof File != "undefined" &&
1135
+ typeof (new XMLHttpRequest()).upload != "undefined" );
1136
+ };
1137
+
1138
+ // @inherits qq.UploadHandlerAbstract
1139
+ qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
1140
+
1141
+ qq.extend(qq.UploadHandlerXhr.prototype, {
1142
+ /**
1143
+ * Adds file to the queue
1144
+ * Returns id to use with upload, cancel
1145
+ **/
1146
+ add: function(file){
1147
+ if (!(file instanceof File)){
1148
+ throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
1149
+ }
1150
+
1151
+ return this._files.push(file) - 1;
1152
+ },
1153
+ getName: function(id){
1154
+ var file = this._files[id];
1155
+ // fix missing name in Safari 4
1156
+ return file.fileName != null ? file.fileName : file.name;
1157
+ },
1158
+ getSize: function(id){
1159
+ var file = this._files[id];
1160
+ return file.fileSize != null ? file.fileSize : file.size;
1161
+ },
1162
+ /**
1163
+ * Returns uploaded bytes for file identified by id
1164
+ */
1165
+ getLoaded: function(id){
1166
+ return this._loaded[id] || 0;
1167
+ },
1168
+ /**
1169
+ * Sends the file identified by id and additional query params to the server
1170
+ * @param {Object} params name-value string pairs
1171
+ */
1172
+ _upload: function(id, params){
1173
+ var file = this._files[id],
1174
+ name = this.getName(id),
1175
+ size = this.getSize(id);
1176
+
1177
+ this._loaded[id] = 0;
1178
+
1179
+ var xhr = this._xhrs[id] = new XMLHttpRequest();
1180
+ var self = this;
1181
+
1182
+ xhr.upload.onprogress = function(e){
1183
+ if (e.lengthComputable){
1184
+ self._loaded[id] = e.loaded;
1185
+ self._options.onProgress(id, name, e.loaded, e.total);
1186
+ }
1187
+ };
1188
+
1189
+ xhr.onreadystatechange = function(){
1190
+ if (xhr.readyState == 4){
1191
+ self._onComplete(id, xhr);
1192
+ }
1193
+ };
1194
+
1195
+ // build query string
1196
+ params = params || {};
1197
+ params['qqfile'] = name;
1198
+ var queryString = qq.obj2url(params, this._options.action);
1199
+
1200
+ xhr.open("POST", queryString, true);
1201
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
1202
+ xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
1203
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
1204
+ xhr.send(file);
1205
+ },
1206
+ _onComplete: function(id, xhr){
1207
+ // the request was aborted/cancelled
1208
+ if (!this._files[id]) return;
1209
+
1210
+ var name = this.getName(id);
1211
+ var size = this.getSize(id);
1212
+
1213
+ this._options.onProgress(id, name, size, size);
1214
+
1215
+ if (xhr.status == 200){
1216
+ this.log("xhr - server response received");
1217
+ this.log("responseText = " + xhr.responseText);
1218
+
1219
+ var response;
1220
+
1221
+ try {
1222
+ response = eval("(" + xhr.responseText + ")");
1223
+ } catch(err){
1224
+ response = {};
1225
+ }
1226
+
1227
+ this._options.onComplete(id, name, response);
1228
+
1229
+ } else {
1230
+ this._options.onComplete(id, name, {});
1231
+ }
1232
+
1233
+ this._files[id] = null;
1234
+ this._xhrs[id] = null;
1235
+ this._dequeue(id);
1236
+ },
1237
+ _cancel: function(id){
1238
+ this._options.onCancel(id, this.getName(id));
1239
+
1240
+ this._files[id] = null;
1241
+
1242
+ if (this._xhrs[id]){
1243
+ this._xhrs[id].abort();
1244
+ this._xhrs[id] = null;
1245
+ }
1246
+ }
1247
+ });