droom 0.0.1

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 (207) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +23 -0
  3. data/Rakefile +27 -0
  4. data/app/assets/images/droom/arrows.png +0 -0
  5. data/app/assets/images/droom/big_icons.png +0 -0
  6. data/app/assets/images/droom/blueblob.png +0 -0
  7. data/app/assets/images/droom/close.png +0 -0
  8. data/app/assets/images/droom/cross.png +0 -0
  9. data/app/assets/images/droom/download.png +0 -0
  10. data/app/assets/images/droom/greyblob.png +0 -0
  11. data/app/assets/images/droom/help/goodreader.jpg +0 -0
  12. data/app/assets/images/droom/ical.png +0 -0
  13. data/app/assets/images/droom/icons.png +0 -0
  14. data/app/assets/images/droom/medium_icons.png +0 -0
  15. data/app/assets/images/droom/medium_object_icons.png +0 -0
  16. data/app/assets/images/droom/minimonth.png +0 -0
  17. data/app/assets/images/droom/minisymbols.png +0 -0
  18. data/app/assets/images/droom/object_icons.png +0 -0
  19. data/app/assets/images/droom/pinkblob.png +0 -0
  20. data/app/assets/images/droom/place_busy.png +0 -0
  21. data/app/assets/images/droom/place_quiet.png +0 -0
  22. data/app/assets/images/droom/plus_bullet.png +0 -0
  23. data/app/assets/images/droom/search.png +0 -0
  24. data/app/assets/images/droom/setup.png +0 -0
  25. data/app/assets/images/droom/small_go.png +0 -0
  26. data/app/assets/images/droom/smallbullet.png +0 -0
  27. data/app/assets/images/droom/smallspinner.gif +0 -0
  28. data/app/assets/images/droom/spinner.gif +0 -0
  29. data/app/assets/images/droom/spr_toolbar_icons_r2.png +0 -0
  30. data/app/assets/images/droom/tablesort.png +0 -0
  31. data/app/assets/images/droom/tick.png +0 -0
  32. data/app/assets/images/droom/tinysymbols.png +0 -0
  33. data/app/assets/images/droom/twister.png +0 -0
  34. data/app/assets/images/droom/vcard.png +0 -0
  35. data/app/assets/images/droom/venue_bullet.png +0 -0
  36. data/app/assets/javascripts/droom.js.coffee +88 -0
  37. data/app/assets/javascripts/droom/calendar.js.coffee +121 -0
  38. data/app/assets/javascripts/droom/drag_sort.js.coffee +22 -0
  39. data/app/assets/javascripts/droom/forms.js.coffee +746 -0
  40. data/app/assets/javascripts/droom/lib/extensions.js.coffee +60 -0
  41. data/app/assets/javascripts/droom/lib/jquery.animate-colors.js +109 -0
  42. data/app/assets/javascripts/droom/lib/jquery.cookie.js +71 -0
  43. data/app/assets/javascripts/droom/lib/jquery.sortable.js +97 -0
  44. data/app/assets/javascripts/droom/lib/kalendae.js +1692 -0
  45. data/app/assets/javascripts/droom/lib/modernizr.js +4 -0
  46. data/app/assets/javascripts/droom/lib/parser_rules/advanced.js +553 -0
  47. data/app/assets/javascripts/droom/lib/parser_rules/simple.js +32 -0
  48. data/app/assets/javascripts/droom/lib/wysihtml5.js +9550 -0
  49. data/app/assets/javascripts/droom/map.js.coffee +123 -0
  50. data/app/assets/javascripts/droom/sort.js.coffee +126 -0
  51. data/app/assets/javascripts/droom/suggester.js.coffee +230 -0
  52. data/app/assets/stylesheets/droom.css.sass +1151 -0
  53. data/app/assets/stylesheets/lib/_kalendae.css.sass +142 -0
  54. data/app/assets/stylesheets/lib/_toolbar.css.sass +192 -0
  55. data/app/controllers/droom/dashboard_controller.rb +27 -0
  56. data/app/controllers/droom/document_attachments_controller.rb +19 -0
  57. data/app/controllers/droom/documents_controller.rb +116 -0
  58. data/app/controllers/droom/engine_controller.rb +43 -0
  59. data/app/controllers/droom/events_controller.rb +120 -0
  60. data/app/controllers/droom/group_invitations_controller.rb +47 -0
  61. data/app/controllers/droom/groups_controller.rb +67 -0
  62. data/app/controllers/droom/invitations_controller.rb +43 -0
  63. data/app/controllers/droom/memberships_controller.rb +47 -0
  64. data/app/controllers/droom/pages_controller.rb +61 -0
  65. data/app/controllers/droom/people_controller.rb +92 -0
  66. data/app/controllers/droom/suggestions_controller.rb +58 -0
  67. data/app/controllers/droom/venues_controller.rb +39 -0
  68. data/app/helpers/droom/droom_helper.rb +74 -0
  69. data/app/models/droom/agenda_category.rb +8 -0
  70. data/app/models/droom/category.rb +27 -0
  71. data/app/models/droom/document.rb +110 -0
  72. data/app/models/droom/document_attachment.rb +71 -0
  73. data/app/models/droom/document_link.rb +31 -0
  74. data/app/models/droom/event.rb +409 -0
  75. data/app/models/droom/event_set.rb +6 -0
  76. data/app/models/droom/group.rb +66 -0
  77. data/app/models/droom/group_invitation.rb +23 -0
  78. data/app/models/droom/invitation.rb +30 -0
  79. data/app/models/droom/membership.rb +27 -0
  80. data/app/models/droom/page.rb +26 -0
  81. data/app/models/droom/person.rb +302 -0
  82. data/app/models/droom/personal_document.rb +98 -0
  83. data/app/models/droom/recurrence_rule.rb +82 -0
  84. data/app/models/droom/venue.rb +125 -0
  85. data/app/views/droom/dashboard/_marginalia.html.haml +3 -0
  86. data/app/views/droom/dashboard/_my_future_events.html.haml +9 -0
  87. data/app/views/droom/dashboard/_my_group_documents.html.haml +6 -0
  88. data/app/views/droom/dashboard/_my_past_events.haml +6 -0
  89. data/app/views/droom/dashboard/index.html.haml +5 -0
  90. data/app/views/droom/documents/_created.html.haml +2 -0
  91. data/app/views/droom/documents/_document.html.haml +14 -0
  92. data/app/views/droom/documents/_document_line.html.haml +2 -0
  93. data/app/views/droom/documents/_documents_list.html.haml +31 -0
  94. data/app/views/droom/documents/_documents_table.html.haml +13 -0
  95. data/app/views/droom/documents/_event_document_form.html.haml +15 -0
  96. data/app/views/droom/documents/_form.html.haml +22 -0
  97. data/app/views/droom/documents/_listing.html.haml +8 -0
  98. data/app/views/droom/documents/_suggested.html.haml +9 -0
  99. data/app/views/droom/documents/_table_document.html.haml +31 -0
  100. data/app/views/droom/documents/edit.html.haml +1 -0
  101. data/app/views/droom/documents/index.html.haml +29 -0
  102. data/app/views/droom/documents/new.html.haml +1 -0
  103. data/app/views/droom/errors/bang.html.haml +12 -0
  104. data/app/views/droom/errors/not_allowed.html.haml +12 -0
  105. data/app/views/droom/errors/not_found.html.haml +12 -0
  106. data/app/views/droom/events/_attachment.html.haml +1 -0
  107. data/app/views/droom/events/_attachment_list.html.haml +4 -0
  108. data/app/views/droom/events/_calendar.html.haml +54 -0
  109. data/app/views/droom/events/_created.html.haml +2 -0
  110. data/app/views/droom/events/_event.html.haml +77 -0
  111. data/app/views/droom/events/_event_line.html.haml +13 -0
  112. data/app/views/droom/events/_events.html.haml +2 -0
  113. data/app/views/droom/events/_form.html.haml +35 -0
  114. data/app/views/droom/events/_invitations.html.haml +20 -0
  115. data/app/views/droom/events/_other_page_parts.html.haml +0 -0
  116. data/app/views/droom/events/_popup_event.html.haml +6 -0
  117. data/app/views/droom/events/_suggested.html.haml +12 -0
  118. data/app/views/droom/events/_views.html.haml +7 -0
  119. data/app/views/droom/events/edit.html.haml +1 -0
  120. data/app/views/droom/events/index.html.haml +12 -0
  121. data/app/views/droom/events/index.rss.builder +20 -0
  122. data/app/views/droom/events/new.html.haml +1 -0
  123. data/app/views/droom/events/show.html.haml +4 -0
  124. data/app/views/droom/group_invitations/_attending_groups.html.haml +8 -0
  125. data/app/views/droom/group_invitations/_created.html.haml +3 -0
  126. data/app/views/droom/group_invitations/_form.html.haml +4 -0
  127. data/app/views/droom/group_invitations/new.html.haml +1 -0
  128. data/app/views/droom/groups/_created.html.haml +3 -0
  129. data/app/views/droom/groups/_form.html.haml +12 -0
  130. data/app/views/droom/groups/_group.html.haml +11 -0
  131. data/app/views/droom/groups/_groups.html.haml +3 -0
  132. data/app/views/droom/groups/edit.html.haml +1 -0
  133. data/app/views/droom/groups/index.html.haml +7 -0
  134. data/app/views/droom/groups/show.html.haml +13 -0
  135. data/app/views/droom/invitations/_attending_people.html.haml +8 -0
  136. data/app/views/droom/invitations/_created.html.haml +3 -0
  137. data/app/views/droom/invitations/_form.html.haml +5 -0
  138. data/app/views/droom/invitations/new.html.haml +1 -0
  139. data/app/views/droom/memberships/_button.html.haml +11 -0
  140. data/app/views/droom/memberships/_created.html.haml +4 -0
  141. data/app/views/droom/memberships/_form.html.haml +7 -0
  142. data/app/views/droom/memberships/_member.html.haml +33 -0
  143. data/app/views/droom/memberships/_memberships.html.haml +4 -0
  144. data/app/views/droom/pages/_contents.html.haml +10 -0
  145. data/app/views/droom/pages/_form.html.haml +36 -0
  146. data/app/views/droom/pages/_full_page.html.haml +17 -0
  147. data/app/views/droom/pages/_page.html.haml +5 -0
  148. data/app/views/droom/pages/_pages.html.haml +2 -0
  149. data/app/views/droom/pages/admin.html.haml +24 -0
  150. data/app/views/droom/pages/edit.html.haml +1 -0
  151. data/app/views/droom/pages/index.html.haml +10 -0
  152. data/app/views/droom/pages/new.html.haml +4 -0
  153. data/app/views/droom/pages/show.html.haml +5 -0
  154. data/app/views/droom/people/_created.html.haml +6 -0
  155. data/app/views/droom/people/_form.html.haml +40 -0
  156. data/app/views/droom/people/_people.html.haml +29 -0
  157. data/app/views/droom/people/_person.html.haml +34 -0
  158. data/app/views/droom/people/_suggested.html.haml +9 -0
  159. data/app/views/droom/people/edit.html.haml +1 -0
  160. data/app/views/droom/people/index.html.haml +11 -0
  161. data/app/views/droom/people/new.html.haml +1 -0
  162. data/app/views/droom/people/show.html.haml +1 -0
  163. data/app/views/droom/shared/_calendar_and_search.html.haml +2 -0
  164. data/app/views/droom/shared/_calendar_holder.haml +3 -0
  165. data/app/views/droom/shared/_controls.html.haml +8 -0
  166. data/app/views/droom/shared/_navigation.html.haml +8 -0
  167. data/app/views/droom/shared/_search_form.html.haml +4 -0
  168. data/app/views/droom/shared/_suggestions.html.haml +11 -0
  169. data/app/views/droom/shared/_toolbar.html.haml +17 -0
  170. data/app/views/droom/venues/_suggested.html.haml +6 -0
  171. data/app/views/droom/venues/index.html.haml +6 -0
  172. data/app/views/droom/venues/show.html.haml +21 -0
  173. data/app/views/layouts/droom/application.html.haml +36 -0
  174. data/config/initializers/dav.rb +2 -0
  175. data/config/initializers/paperclip.rb +26 -0
  176. data/config/initializers/snail.rb +2 -0
  177. data/config/locales/en.yml +191 -0
  178. data/config/routes.rb +51 -0
  179. data/db/migrate/20120910075016_create_droom_data.rb +134 -0
  180. data/db/migrate/20120917095804_agenda_sections.rb +13 -0
  181. data/db/migrate/20120918121352_add_postal_address_to_people.rb +10 -0
  182. data/db/migrate/20121009075049_give_groups_descriptions.rb +5 -0
  183. data/db/migrate/20121009105244_more_names.rb +5 -0
  184. data/db/migrate/20121009145944_event_agenda_sections.rb +13 -0
  185. data/db/migrate/20121011091230_create_group_invitations.rb +10 -0
  186. data/db/migrate/20121012144720_give_people_positions.rb +5 -0
  187. data/db/migrate/20121012154558_help_pages.rb +13 -0
  188. data/db/migrate/20121012163201_category_slugs.rb +5 -0
  189. data/db/migrate/20121101160102_document_links.rb +14 -0
  190. data/db/migrate/20121101181247_people_visibility.rb +7 -0
  191. data/db/migrate/20121102094738_shy_people.rb +6 -0
  192. data/db/migrate/20121102095856_visibility_defaults.rb +8 -0
  193. data/lib/droom.rb +73 -0
  194. data/lib/droom/dav_resource.rb +36 -0
  195. data/lib/droom/engine.rb +8 -0
  196. data/lib/droom/helpers.rb +25 -0
  197. data/lib/droom/monkeys.rb +15 -0
  198. data/lib/droom/renderers.rb +13 -0
  199. data/lib/droom/validators.rb +5 -0
  200. data/lib/droom/version.rb +3 -0
  201. data/lib/generators/droom/dashboard/dashboard_generator.rb +16 -0
  202. data/lib/generators/droom/install/USAGE +9 -0
  203. data/lib/generators/droom/install/install_generator.rb +15 -0
  204. data/lib/generators/droom/install/templates/droom_initializer.rb +6 -0
  205. data/lib/generators/droom/views/views_generator.rb +9 -0
  206. data/lib/tasks/droom_tasks.rake +4 -0
  207. metadata +635 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # Droom
2
+
3
+ This is a minimal system for secure document and event distribution. It provides a calendar, library and user directory with excellent integration and very little else.
4
+
5
+ ## Status
6
+
7
+ Brand new. In progress. Avoid!
8
+
9
+ ## Installation
10
+
11
+ In your gemfile:
12
+
13
+ gem "droom"
14
+
15
+ To migrate:
16
+
17
+ rake droom:install:migrations
18
+ rake db:migrate
19
+
20
+
21
+ ## Copyright
22
+
23
+ 2012 Spanner Ltd. Released under the same terms as rails.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Droom'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,88 @@
1
+ #= require droom/lib/modernizr
2
+ #= require droom/lib/extensions
3
+ #= require jquery
4
+ #= require jquery_ujs
5
+ #= require droom/lib/jquery.animate-colors
6
+ #= require droom/lib/jquery.sortable
7
+ #= require droom/lib/jquery.cookie
8
+ #= require droom/lib/kalendae
9
+ #= require droom/lib/wysihtml5
10
+ #= require droom/lib/parser_rules/advanced
11
+ #= require droom/forms
12
+ #= require droom/suggester
13
+ #= require droom/calendar
14
+ #= require droom/sort
15
+ #= require droom/map
16
+ #= require droom/drag_sort
17
+ #= require_self
18
+
19
+ jQuery ($) ->
20
+ $.fn.flash = ->
21
+ @each ->
22
+ container = $(this)
23
+ container.fadeIn "fast"
24
+ $("<a href=\"#\" class=\"closer\">close</a>").prependTo(container)
25
+ container.bind "click", (e) ->
26
+ e.preventDefault()
27
+ container.fadeOut "fast"
28
+
29
+ $.fn.signal = (color, duration) ->
30
+ color ?= "#f7f283"
31
+ duration ?= 1000
32
+ @each ->
33
+ $(@).css('backgroundColor', color).animate({'backgroundColor': '#ffffff'}, duration)
34
+
35
+ $.fn.signal_confirmation = ->
36
+ @signal('#c7ebb4')
37
+
38
+ $.fn.signal_error = ->
39
+ @signal('#e55a51')
40
+
41
+ $.fn.signal_cancellation = ->
42
+ @signal('#a2a3a3')
43
+
44
+ $.fn.back_button = ->
45
+ @click (e) ->
46
+ e.preventDefault() if e
47
+ history.back()
48
+ true
49
+
50
+ $.fn.find_including_self = (selector) ->
51
+ selection = @.find(selector)
52
+ selection.push @ if @is(selector)
53
+ selection
54
+
55
+ $.fn.activate = () ->
56
+ @find_including_self('a.toggle_active').replace_with_remote_toggle()
57
+ @find_including_self('#flashes p:parent').flash()
58
+ @find_including_self('.twister').twister()
59
+ @find_including_self('.wysihtml').html_editable()
60
+ @find_including_self('.venuepicker').venue_picker()
61
+ @find_including_self('.datepicker').date_picker()
62
+ @find_including_self('.timepicker').time_picker()
63
+ @find_including_self('.filepicker').file_picker()
64
+ @find_including_self('a.delete').removes('.holder')
65
+ @find_including_self('[data-action="popup"]').popup_remote_content()
66
+ @find_including_self('[data-action="toggle"]').toggle()
67
+ @find_including_self('[data-action="append_form"]').append_remote_form()
68
+ @find_including_self('[data-action="overlay_form"]').overlay_remote_form()
69
+ @find_including_self('[data-action="replace_with_form"]').replace_with_remote_form()
70
+ @find_including_self('[data-refreshable]').refresher()
71
+ @find_including_self('table.sortable').table_sort
72
+ sort: "created"
73
+ order: "DESC"
74
+ @find_including_self('#map').init_map()
75
+ @find_including_self('input.password').password_field()
76
+ @find_including_self('input[type="submit"]').submitter()
77
+ @find_including_self('input.person_picker').person_picker()
78
+ @find_including_self('input.group_picker').group_picker()
79
+ @find_including_self('.drag_sort').drag_sort()
80
+ @find_including_self('.back').back_button()
81
+ @
82
+
83
+ $ ->
84
+ $('body').activate()
85
+ $('#minicalendar').calendar()
86
+ $('form#searchform').captive
87
+ replacing: '.search_results'
88
+ fast: true
@@ -0,0 +1,121 @@
1
+ jQuery ($) ->
2
+
3
+ class Calendar
4
+ constructor: (element, options) ->
5
+ @_container = $(element)
6
+ @_scroller = @_container.find('.scroller')
7
+ @_table = null
8
+ @_cache = {}
9
+ @_month = null
10
+ @_year = null
11
+ @_request = null
12
+ @_incoming = {}
13
+ @_width = @_container.width()
14
+ $.calendar = @
15
+ @_container.bind "refresh", @refresh_in_place
16
+ @init()
17
+
18
+ init: () =>
19
+ @_table = @_container.find('table')
20
+ @_month = parseInt(@_table.attr('data-month'), 10)
21
+ @_year = parseInt(@_table.attr('data-year'), 10)
22
+ @cache(@_year, @_month, @_table)
23
+ @_table.find('a.next, a.previous').calendar_changer()
24
+ @_table.find('a.day').day_search()
25
+ @_table.find('a.month').month_search()
26
+
27
+ cache: (year, month, table) =>
28
+ @_cache[year] ?= {}
29
+ @_cache[year][month] ?= table
30
+
31
+ cached: (year, month) =>
32
+ @_cache[year] ?= {}
33
+ @_cache[year][month]
34
+
35
+ refresh_in_place: () =>
36
+ @_request = $.ajax
37
+ type: "GET"
38
+ dataType: "html"
39
+ url: "/events/calendar.js?month=#{encodeURIComponent(@_month)}&year=#{encodeURIComponent(@_year)}"
40
+ success: @update_quietly
41
+
42
+ update_quietly: (response) =>
43
+ @_container.find('a').removeClass('waiting')
44
+ @_scroller.find('table').remove()
45
+ @_scroller.append(response)
46
+ @init()
47
+
48
+ show: (year, month) =>
49
+ if cached = @cached(year, month)
50
+ @update(cached, year, month)
51
+ else
52
+ @_request = $.ajax
53
+ type: "GET"
54
+ dataType: "html"
55
+ url: "/events/calendar.js?month=#{encodeURIComponent(month)}&year=#{encodeURIComponent(year)}"
56
+ success: (response) =>
57
+ @update(response, year, month)
58
+
59
+ update: (response, year, month) =>
60
+ @_container.find('a').removeClass('waiting')
61
+ direction = "left" if ((year * 12) + month) > ((@_year * 12) + @_month)
62
+ @sweep response, direction
63
+
64
+ sweep: (table, direction) =>
65
+ old = @_scroller.find('table')
66
+ if direction == 'left'
67
+ @_scroller.append(table)
68
+ @_container.animate {scrollLeft: @_width}, 'fast', () =>
69
+ old.remove()
70
+ @_container.scrollLeft(0)
71
+ @init()
72
+ else
73
+ @_scroller.prepend(table)
74
+ @_container.scrollLeft(@_width).animate {scrollLeft: 0}, 'fast', () =>
75
+ old.remove()
76
+ @init()
77
+
78
+ monthName: () =>
79
+ Kalendae.moment.months[@_month-1]
80
+
81
+ searchForm: =>
82
+ @_form ?= $('#searchform')
83
+
84
+ searchFor: (day) =>
85
+ if day?
86
+ @search("#{@monthName()} #{day}, #{@_year}")
87
+ else
88
+ @search("#{@monthName()} #{@_year}")
89
+
90
+ search: (term) =>
91
+ @searchForm().find('input#term').val(term)
92
+ @searchForm().submit()
93
+
94
+
95
+ $.fn.calendar = ->
96
+ @each ->
97
+ new Calendar(@)
98
+ @
99
+
100
+ $.fn.calendar_changer = ->
101
+ @click (e) ->
102
+ e.preventDefault() if e
103
+ link = $(@)
104
+ year = parseInt(link.attr('data-year'), 10)
105
+ month = parseInt(link.attr('data-month'), 10)
106
+ link.addClass('waiting')
107
+ $.calendar?.show(year, month)
108
+ @
109
+
110
+ $.fn.day_search = ->
111
+ @click (e) ->
112
+ e.preventDefault() if e
113
+ $.calendar?.searchFor($(@).text())
114
+
115
+ $.fn.month_search = ->
116
+ @click (e) ->
117
+ e.preventDefault() if e
118
+ $.calendar?.searchFor()
119
+
120
+
121
+
@@ -0,0 +1,22 @@
1
+ jQuery ($) ->
2
+
3
+ $.fn.drag_sort = (options) ->
4
+ @each ->
5
+ first = 0
6
+ offset = 1 + first
7
+ sorter = $(@).sortable
8
+ handle: ".handle"
9
+ $.each $(@).children(), (i, child) =>
10
+ $(child).bind "dragend", (e) =>
11
+ child = $(child)
12
+ index = child.index() + offset
13
+ id = parseInt(child.attr('id').split("person_")[1], 10)
14
+ $.ajax
15
+ url: "/people/#{id}"
16
+ type: "PUT"
17
+ dataType: "JSON"
18
+ data:
19
+ person:
20
+ position: index
21
+
22
+ @
@@ -0,0 +1,746 @@
1
+ jQuery ($) ->
2
+
3
+ # minimal rfc4122 generator taken from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
4
+ $.makeGuid = ()->
5
+ 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
6
+ r = Math.random()*16|0
7
+ v = if c is 'x' then r else r & 0x3 | 0x8
8
+ v.toString 16
9
+
10
+ # Query string parser for history-restoration purposes.
11
+
12
+ $.urlParam = (name) ->
13
+ results = new RegExp("[\\?&]" + name + "=([^&#]*)").exec(window.location.href)
14
+ return false unless results
15
+ results[1] or 0
16
+
17
+
18
+
19
+ class Toggle
20
+ constructor: (element, @_selector, @_name) ->
21
+ @_container = $(element)
22
+ @_name ?= "droom_#{@_selector}_state"
23
+ @_showing_text = @_container.text().replace('show', 'hide').replace('Show', 'Hide')
24
+ @_hiding_text = @_showing_text.replace('hide', 'show').replace('Hide', 'Show')
25
+ @_container.click @toggle
26
+ if cookie = $.cookie(@_name)
27
+ @_showing = cookie is "showing"
28
+ @apply()
29
+ else
30
+ @_showing = $(@_selector).is(":visible")
31
+ @store()
32
+
33
+ apply: (e) =>
34
+ e.preventDefault() if e
35
+ if @_showing then @show() else @hide()
36
+
37
+ toggle: (e) =>
38
+ e.preventDefault() if e
39
+ if @_showing then @fadeOut() else @fadeIn()
40
+
41
+ fadeIn: =>
42
+ $(@_selector).fadeIn () =>
43
+ @show()
44
+
45
+ show: =>
46
+ $(@_selector).show()
47
+ @_container.text(@_showing_text)
48
+ @_showing = true
49
+ @store()
50
+
51
+ fadeOut: =>
52
+ $(@_selector).fadeOut () =>
53
+ @hide()
54
+
55
+ hide: =>
56
+ $(@_selector).hide()
57
+ @_container.text(@_hiding_text)
58
+ @_showing = false
59
+ @store()
60
+
61
+ store: () =>
62
+ value = if @_showing then "showing" else "hidden"
63
+ $.cookie @_name, value,
64
+ path: '/'
65
+
66
+
67
+ $.fn.toggle = () ->
68
+ @each ->
69
+ new Toggle(@, $(@).attr('data-affected'))
70
+
71
+
72
+
73
+
74
+ class Twister
75
+ constructor: (element) ->
76
+ @_twister = $(element)
77
+ @_twisted = @_twister.siblings('.twisted')
78
+ @_toggle = @_twister.find('a')
79
+ @_toggle.click @toggle
80
+ @close() if @_twister.hasClass('closed')
81
+
82
+ toggle: (e) =>
83
+ e.preventDefault() if e
84
+ if @_twisted.is(':visible') then @close() else @open()
85
+
86
+ open: () =>
87
+ @_twister.removeClass("closed")
88
+ @_twisted.slideDown "slow"
89
+
90
+ close: () =>
91
+ @_twisted.slideUp "slow", () =>
92
+ @_twister.addClass("closed")
93
+
94
+ $.fn.twister = ->
95
+ @each ->
96
+ new Twister(@)
97
+
98
+ # A captive form submits via an ajax request and pushes its results into the present page.
99
+
100
+ class CaptiveForm
101
+ constructor: (element, @_options) ->
102
+ @_form = $(element)
103
+ @_prompt = @_form.find("input[type=\"text\"]")
104
+ @_request = null
105
+ @_original_content = $(@_options.replacing).clone()
106
+ @_original_term = decodeURIComponent $.urlParam("q") if $.urlParam("q")
107
+ if @_original_term
108
+ @_prompt.val(@_original_term)
109
+ @submit() unless @_prompt.val() is ""
110
+ else
111
+ @revert()
112
+ if @_options.fast
113
+ @_form.find("input[type=\"text\"]").keyup @keyed
114
+ @_form.find("input[type=\"radio\"]").click @submit
115
+ @_form.find("input[type=\"checkbox\"]").click @submit
116
+ if Modernizr.history
117
+ $(window).bind 'popstate', @restoreState
118
+ @_form.submit @submit
119
+
120
+ keyed: (e) =>
121
+ k = e.which
122
+ if (k >= 32 and k <= 165) or k == 8
123
+ if @_prompt.val() == ""
124
+ @revert()
125
+ else
126
+ @_form.submit()
127
+
128
+ submit: (e) =>
129
+ e.preventDefault() if e
130
+ $(@_options.replacing).fadeTo "fast", 0.2
131
+ @_request.abort() if @_request
132
+ @_form.find("input[type='submit']").addClass "waiting"
133
+ @_request = $.ajax
134
+ type: "GET"
135
+ dataType: "html"
136
+ url: @_form.attr("action") + ".js"
137
+ data: @_form.serialize()
138
+ success: @update
139
+
140
+ update: (results) =>
141
+ @_form.find("input[type='submit']").removeClass "waiting"
142
+ @saveState(results) if Modernizr.history
143
+ @display results
144
+
145
+ saveState: (results) =>
146
+ results ?= @_original_content.html()
147
+ term = @_prompt.val()
148
+ if term
149
+ url = window.location.pathname + '?q=' + encodeURIComponent(term)
150
+ else
151
+ url = window.location.pathname
152
+ state =
153
+ html: results
154
+ term: term
155
+ history.pushState state, "New page title", url
156
+
157
+ restoreState: (e) =>
158
+ event = e.originalEvent
159
+ if event.state? && event.state.html?
160
+ @display event.state.html
161
+ @_prompt.val(event.state.term)
162
+
163
+ revert: (e) =>
164
+ e.preventDefault() if e
165
+ @display @_original_content
166
+ @_original_content.fadeTo "fast", 1
167
+ @_prompt.val("")
168
+ @saveState()
169
+
170
+ display: (results) =>
171
+ $(@_options.replacing).replaceWith results
172
+ $(@_options.clearing).val "" if @_options.clearing?
173
+ $(@_options.replacing).find('a.popup').popup_remote_content()
174
+ $(@_options.replacing).find('a.cancel').click @revert
175
+
176
+ $.fn.captive = (options) ->
177
+ options = $.extend(
178
+ replacing: "#results"
179
+ clearing: null
180
+ , options)
181
+ @each ->
182
+ new CaptiveForm @, options
183
+ @
184
+
185
+ class Editor
186
+ constructor: (element) ->
187
+ @_container = $(element)
188
+ @_textarea = @_container.find('textarea')
189
+ @_toolbar = @_container.find('.toolbar')
190
+ @_toolbar.attr('id', $.makeGuid()) unless @_toolbar.attr('id')?
191
+ @_textarea.attr('id', $.makeGuid()) unless @_textarea.attr('id')?
192
+ stylesheets = $("link").map ->
193
+ $(@).attr('href')
194
+ @_editor = new wysihtml5.Editor @_textarea.attr('id'),
195
+ stylesheets: stylesheets,
196
+ toolbar: @_toolbar.attr('id'),
197
+ parserRules: wysihtml5ParserRules
198
+ useLineBreaks: false
199
+ @_toolbar.show()
200
+ @_editor.on "load", () =>
201
+ @_iframe = @_editor.composer.iframe
202
+ $(@_editor.composer.doc).find('html').css
203
+ "height": 0
204
+ @resizeIframe()
205
+ @_textarea = @_editor.composer.element
206
+ @_textarea.addEventListener("keyup", @resizeIframe, false)
207
+ @_textarea.addEventListener("blur", @resizeIframe, false)
208
+ @_textarea.addEventListener("focus", @resizeIframe, false)
209
+
210
+ resizeIframe: () =>
211
+ if $(@_iframe).height() != $(@_editor.composer.doc).height()
212
+ $(@_iframe).height(@_editor.composer.element.offsetHeight)
213
+
214
+ showToolbar: () =>
215
+ @_hovered = true
216
+ @_toolbar.fadeTo(200, 1)
217
+
218
+ hideToolbar: () =>
219
+ @_hovered = false
220
+ @_toolbar.fadeTo(1000, 0.2)
221
+
222
+ $.fn.html_editable = ()->
223
+ @each ->
224
+ new Editor(@)
225
+
226
+
227
+
228
+ class RemoteForm
229
+ constructor: (element, opts) ->
230
+ @_form = $(element)
231
+ @_options = $.extend {}, opts
232
+ @_form.on 'ajax:beforeSend', @pend
233
+ @_form.on 'ajax:error', @fail
234
+ @_form.on 'ajax:success', @receive
235
+ @activate()
236
+
237
+ activate: () =>
238
+ @_form.find('a.cancel').click @cancel
239
+ @_form.activate()
240
+ @_options.on_prepare?()
241
+
242
+ pend: (event, xhr, settings) =>
243
+ xhr.setRequestHeader('X-PJAX', 'true')
244
+ @_form.addClass('waiting')
245
+ @_options.on_submit?()
246
+
247
+ fail: (event, xhr, status) ->
248
+ @_form?.removeClass('waiting').addClass('erratic')
249
+ @_options.on_error?()
250
+
251
+ receive: (event, response, status) =>
252
+ replacement = $(response)
253
+ @_form.after(replacement)
254
+ @_form.remove()
255
+ if replacement.is('form')
256
+ @_form = replacement
257
+ @activate()
258
+ #todo: make sure we get error markers displaying nicely here
259
+ else
260
+ @_options.on_complete?(replacement)
261
+
262
+ cancel: (e) =>
263
+ e.preventDefault() if e
264
+ if @_options.on_cancel?
265
+ @_options.on_cancel()
266
+ else
267
+ @_form.remove()
268
+
269
+
270
+ $.fn.remote_link = (callback) ->
271
+ @
272
+ .on 'ajax:beforeSend', (event, xhr, settings) ->
273
+ $(@).addClass('waiting')
274
+ xhr.setRequestHeader('X-PJAX', 'true')
275
+ .on 'ajax:error', (event, xhr, status) ->
276
+ console.log "remote_link error:", status
277
+ $(@).removeClass('waiting').addClass('erratic')
278
+ .on 'ajax:success', (event, response, status) ->
279
+ $(@).removeClass('waiting')
280
+ callback(response)
281
+
282
+
283
+
284
+ $.fn.removes = (selector) ->
285
+ selector ?= '.holder'
286
+ @each ->
287
+ affected = $(@).attr('data-affected')
288
+ $(@).remote_link (response) =>
289
+ $(@).parents(selector).first().fadeOut 'fast', () ->
290
+ $(@).remove()
291
+ $(affected).trigger "refresh"
292
+
293
+
294
+
295
+ class Replacement
296
+ constructor: (content, container) ->
297
+ @_container = $(container)
298
+ @_content = $(content)
299
+ @_mask = $('#mask')
300
+ @_original_content = @_container.html()
301
+ @_container.html(@_content)
302
+
303
+ revert: (e) =>
304
+ @_container.html(@_original_content)
305
+ @_container.signal_cancellation()
306
+
307
+ $.fn.replace_with_remote_form = (container) ->
308
+ @each ->
309
+ container ?= $(@).parents('.holder')
310
+ affected = $(@).attr('data-affected')
311
+ $(@).remote_link (response) =>
312
+ f = $(response)
313
+ rp = new Replacement f, container
314
+ new RemoteForm f,
315
+ on_cancel: rp.revert
316
+ on_complete: (response) =>
317
+ container.replaceWith(response)
318
+ response.activate()
319
+ response.signal_confirmation()
320
+ $(affected).trigger "refresh"
321
+
322
+
323
+
324
+ class Interjection
325
+ constructor: (content, target, @_position) ->
326
+ @_options = $.extend {position: 'after'}, @_opts
327
+ @_content = $(content)
328
+ @_mask = $('#mask')
329
+ @_target = $(target)
330
+ @_container = $('<div class="interjected" />')
331
+ switch @_position
332
+ when "insert" then @_container.prependTo(@_target)
333
+ when "before" then @_container.insertBefore(@_target)
334
+ when "after" then @_container.insertAfter(@_target)
335
+ else throw "interjection overruled"
336
+ @_container.hide().append(@_content)
337
+ @show()
338
+
339
+ show: (e) =>
340
+ e.preventDefault() if e
341
+ @_container.slideDown 'slow'
342
+
343
+ hide: (e) =>
344
+ e.preventDefault() if e
345
+ @_container.slideUp 'slow'
346
+
347
+ remove: (e) =>
348
+ e.preventDefault() if e
349
+ @_container.slideUp 'fast', () ->
350
+ $(@).remove()
351
+
352
+
353
+ $.fn.append_remote_form = (target) ->
354
+ @each ->
355
+ container = target ? $(@).parents('.holder').first()
356
+ affected = $(@).attr('data-affected')
357
+ $(@).remote_link (response) =>
358
+ f = $(response)
359
+ ij = new Interjection f, container, 'after'
360
+ new RemoteForm f,
361
+ on_cancel: ij.remove
362
+ on_complete: (response) =>
363
+ $(affected).trigger "refresh"
364
+
365
+
366
+
367
+ class Overlay
368
+ constructor: (content, marker) ->
369
+ @_content = $(content)
370
+ @_marker = $(marker)
371
+ @_container = $('<div class="overlay" />')
372
+ @_mask = $('#mask')
373
+ @_marker.offsetParent().append(@_container)
374
+ @_container.hide().append(@_content)
375
+ position =
376
+ top: @_marker.position().top
377
+ left: @_marker.position().left
378
+ @_container.css position
379
+ @show()
380
+
381
+
382
+ show: (e) =>
383
+ e.preventDefault() if e
384
+ @_mask.bind "click", @hide
385
+ @_mask.fadeTo 'fast', 0.8
386
+ @_container.fadeIn 'fast'
387
+
388
+ hide: (e) =>
389
+ e.preventDefault() if e
390
+ @_mask.fadeOut('slow')
391
+ @_mask.unbind "click", @hide
392
+ @_container.fadeOut('slow')
393
+
394
+ remove: (e) =>
395
+ @_mask.fadeOut('fast')
396
+ @_mask.unbind "click", @hide
397
+ @_container.fadeOut 'slow', () ->
398
+ $(@).remove()
399
+
400
+
401
+ $.fn.overlay_remote_form = () ->
402
+ @each ->
403
+ $(@).remote_link (response) =>
404
+ marker = $(@).parents('.holder')
405
+ affected = $(@).attr('data-affected')
406
+ f = $(response)
407
+ ov = new Overlay f, marker
408
+ new RemoteForm f,
409
+ on_cancel: ov.remove
410
+ on_complete: (response) =>
411
+ ov.remove()
412
+ marker.replaceWith(response)
413
+ response.activate()
414
+ response.signal_confirmation()
415
+ $(affected).trigger "refresh"
416
+
417
+
418
+ class Popup
419
+ constructor: (content) ->
420
+ @_content = $(content)
421
+ @_mask = $('#mask')
422
+ @_container = $('<div class="popup" />')
423
+ @_container.insertAfter(@_mask).hide().append(@_content)
424
+ @_content.find('a.cancel').click @hide
425
+ @_content.activate()
426
+ @show()
427
+
428
+ show: (e) =>
429
+ e.preventDefault() if e
430
+ @_container.fadeTo('fast', 1)
431
+ @_mask.fadeTo('fast', 0.8)
432
+ @_mask.bind "click", @hide
433
+
434
+ hide: (e) =>
435
+ e.preventDefault() if e
436
+ @_container.fadeOut('fast')
437
+ @_mask.fadeOut('fast')
438
+ @_mask.unbind "click", @hide
439
+
440
+ $.fn.popup_remote_content = () ->
441
+ @remote_link (response) ->
442
+ new Popup(response)
443
+
444
+
445
+ class Refresher
446
+ constructor: (element) ->
447
+ @_container = $(element)
448
+ @_url = @_container.attr 'data-url'
449
+ @_container.bind "refresh", @refresh
450
+
451
+ refresh: () =>
452
+ $.ajax @_url,
453
+ dataType: "html"
454
+ success: @replace
455
+
456
+ replace: (data, textStatus, jqXHR) =>
457
+ replacement = $(data)
458
+ @_container.fadeOut 'fast', () =>
459
+ replacement.hide().insertAfter(@_container)
460
+ @_container.remove()
461
+ @_container = replacement
462
+ @_container.activate().fadeIn('fast')
463
+
464
+ $.fn.refresher = () ->
465
+ @each ->
466
+ new Refresher @
467
+
468
+
469
+
470
+ class DatePicker
471
+ constructor: (element) ->
472
+ @_container = $(element)
473
+ @_trigger = @_container.find('a')
474
+ @_field = @_container.find('input')
475
+ @_holder = @_container.find('div.kal')
476
+ @_mon = @_container.find('span.mon')
477
+ @_dom = @_container.find('span.dom')
478
+ @_year = @_container.find('span.year')
479
+ @_kal = new Kalendae @_holder[0]
480
+ @_holder.hide()
481
+ @_trigger.click @toggle
482
+ @_kal.subscribe 'change', () =>
483
+ @hide()
484
+ @_field.val(@_kal.getSelected())
485
+ [year, month, day] = @_kal.getSelected().split('-')
486
+ @_year.text(year)
487
+ @_dom.text(day)
488
+ @_mon.text(Kalendae.moment.monthsShort[parseInt(month, 10) - 1])
489
+
490
+ toggle: (e) =>
491
+ e.preventDefault() if e
492
+ if @_holder.is(':visible') then @hide() else @show()
493
+
494
+ show: () =>
495
+ @_holder.fadeIn "fast", () =>
496
+ @_container.addClass('editing')
497
+ # $(document).bind "click", @hide
498
+
499
+ hide: () =>
500
+ # $(document).unbind "click", @hide
501
+ @_container.removeClass('editing')
502
+ @_holder.fadeOut("fast")
503
+
504
+ $.fn.date_picker = () ->
505
+ @each ->
506
+ new DatePicker(@)
507
+ @
508
+
509
+ class TimePicker
510
+ constructor: (element) ->
511
+ holder = $('<div class="timepicker" />')
512
+ menu = $('<ul />').appendTo(holder)
513
+ field = $(element)
514
+ for i in [0..24]
515
+ $("<li>#{i}:00</li><li>#{i}:30</li>").appendTo(menu)
516
+ menu.find('li').click (e) ->
517
+ e.preventDefault()
518
+ field.val $(@).text()
519
+ field.trigger('change')
520
+ field.after holder
521
+ field.focus @show
522
+ field.blur @hide
523
+ @holder = holder
524
+ @field = field
525
+
526
+ show: (e) =>
527
+ position = @field.position()
528
+ @holder.css
529
+ left: position.left
530
+ top: position.top + @field.outerHeight() - 2
531
+ @holder.show()
532
+ $(document).bind "click", @hide
533
+
534
+ hide: (e) =>
535
+ unless e.target is @field[0]
536
+ $(document).unbind "click", @hide
537
+ @holder.hide()
538
+
539
+ $.fn.time_picker = () ->
540
+ @each ->
541
+ new TimePicker(@)
542
+
543
+
544
+
545
+
546
+ class FilePicker
547
+ constructor: (element) ->
548
+ @_container = $(element)
549
+ @_form = @_container.parent()
550
+ @_holder = @_form.parent()
551
+ @_link = @_container.find('a.ul')
552
+ @_filefield = @_container.find('input[type="file"]')
553
+ @_tip = @_container.find('p.tip')
554
+ @_link.click_proxy(@_filefield)
555
+ @_extensions = ['doc', 'docx', 'pdf', 'xls', 'xlsx', 'jpg', 'png']
556
+ @_filefield.bind 'change', @pick
557
+ @_file = null
558
+ @_filename = ""
559
+ @_ext = ""
560
+ @_fields = @_container.siblings('.metadata')
561
+ @_form.submit @submit
562
+
563
+ pick: (e) =>
564
+ @_link.removeClass(@_extensions.join(' '))
565
+ if files = @_filefield[0].files
566
+ @_file = files.item(0)
567
+ @_tip.hide()
568
+ @showSelection() if @_file
569
+
570
+ submit: (e) =>
571
+ if @_file
572
+ e.preventDefault() if e
573
+ @_fields.hide()
574
+ @_notifier = $('<div class="notifier"></div>').appendTo @_form
575
+ @_label = $('<h2 class="filename"></div>').appendTo @_notifier
576
+ @_progress = $('<div class="progress"></div>').appendTo @_notifier
577
+ @_bar = $('<div class="bar"></div>').appendTo @_progress
578
+ @_status = $('<div class="status"></div>').appendTo @_notifier
579
+ @_label.text(@_filename)
580
+ @send()
581
+
582
+ showSelection: () =>
583
+ @_filename = @_file.name.split(/[\/\\]/).pop()
584
+ @_ext = @_filename.split('.').pop()
585
+ @_link.addClass(@_ext) if @_ext in @_extensions
586
+ $('input.name').val(@_filename) if $('input.name').val() is ""
587
+
588
+ send: () =>
589
+ formData = new FormData @_form.get(0)
590
+ @xhr = new XMLHttpRequest()
591
+ @xhr.onreadystatechange = @update
592
+ @xhr.upload.onprogress = @progress
593
+ @xhr.upload.onloadend = @finish
594
+ url = @_form.attr('action')
595
+ @xhr.open 'POST', url, true
596
+ @xhr.send formData
597
+
598
+ progress: (e) =>
599
+ @_status.text("Uploading")
600
+ if e.lengthComputable
601
+ full_width = @_progress.width()
602
+ progress_width = Math.round(full_width * e.loaded / e.total)
603
+ @_bar.width progress_width
604
+
605
+ update: () =>
606
+ if @xhr.readyState == 4
607
+ if @xhr.status == 200
608
+ @_form.remove()
609
+ @_holder.append(@xhr.responseText).delay(5000).slideUp()
610
+ #todo: remove this nasty shortcut and integrate with RemoteForm and jquery_ujs
611
+ #(which will require us to prevent form serialization in some way)
612
+ $('.documents').trigger("refresh")
613
+
614
+ finish: (e) =>
615
+ @_status.text("Processing")
616
+ @_bar.css
617
+ "background-color": "green"
618
+
619
+
620
+
621
+ $.fn.file_picker = () ->
622
+ @each ->
623
+ new FilePicker @
624
+
625
+ $.fn.click_proxy = (target_selector) ->
626
+ this.bind "click", (e) ->
627
+ e.preventDefault()
628
+ $(target_selector).click()
629
+
630
+
631
+ $.fn.replace_with_remote_toggle = () ->
632
+ @
633
+ .on 'ajax:beforeSend', (event, xhr, settings) ->
634
+ $(@).addClass('waiting')
635
+ $(@).children().addClass('waiting')
636
+ xhr.setRequestHeader('X-PJAX', 'true')
637
+ .on 'ajax:error', (event, xhr, status) ->
638
+ $(@).removeClass('waiting').addClass('erratic')
639
+ .on 'ajax:success', (event, response, status) ->
640
+ if response? && response != " "
641
+ self = $(@)
642
+ container = $(@).parents('.holder').first()
643
+ replacement = $(response)
644
+ self.removeClass('waiting')
645
+ container.replaceWith(replacement.activate())
646
+
647
+ class PasswordField
648
+ constructor: (element, opts) ->
649
+ @options = $.extend
650
+ length: 6
651
+ , opts
652
+ @field = $(element)
653
+ @_notice = $('.notice')
654
+ @form = @field.parents('form')
655
+ @submit = @form.find('.submit')
656
+ @confirmation = $("#" + @field.attr("id") + "_confirmation")
657
+ @confirmation_holder = @confirmation.parents("p")
658
+ @mock_password = 'password'
659
+ @required = @field.attr('required')
660
+ @field.focus @wake
661
+ @field.blur @sleep
662
+ @field.keyup @check
663
+ @confirmation.keyup @check
664
+ @form.submit @stumbit
665
+ # to set up initial state
666
+ @check()
667
+ @sleep()
668
+
669
+ wake: () =>
670
+ if @field.val() is @mock_password
671
+ @field.removeClass "empty"
672
+ @field.val ""
673
+
674
+ sleep: () =>
675
+ v = @field.val()
676
+ if v is @mock_password or v is ""
677
+ @field.val @mock_password
678
+ @field.addClass("empty")
679
+ # if we're not required, then both-empty is also a submittable condition
680
+ if @confirmation.val() is "" and not @required
681
+ @submittable()
682
+
683
+ check: () =>
684
+ if @empty() and !@required
685
+ @field.removeClass("ok notok").addClass("empty")
686
+ @confirmation_holder.hide()
687
+ @submittable()
688
+ @notify ""
689
+ else if @valid()
690
+ @field.addClass("ok").removeClass "notok"
691
+ @confirmation_holder.show()
692
+ @notify "You must confirm your password before you can proceed."
693
+ if @matching()
694
+ @notify "Passwords match.", "successful"
695
+ @confirmation.addClass("ok").removeClass("notok")
696
+ @submittable()
697
+ else
698
+ @notify "The confirmation does not match your password.", "erratic"
699
+ @confirmation.addClass("notok").removeClass("ok")
700
+ @unsubmittable()
701
+ else
702
+ @confirmation_holder.hide()
703
+ @confirmation.val ""
704
+ @unsubmittable()
705
+ @field.addClass("notok").removeClass("ok")
706
+ @confirmation.addClass("notok").removeClass("ok")
707
+ @notify "Please enter password of at least six letters.", "erratic"
708
+
709
+ notify: (message, cssclass) =>
710
+ @_notice.removeClass('erratic successful').addClass(cssclass).text(message)
711
+
712
+ submittable: () =>
713
+ @submit.removeClass("unavailable")
714
+ @blocked = false
715
+
716
+ unsubmittable: () =>
717
+ @submit.addClass("unavailable")
718
+ @blocked = true
719
+
720
+ empty: () =>
721
+ !@field.val() || @field.val().length == 0
722
+
723
+ valid: () =>
724
+ v = @field.val()
725
+ v.length >= @options.length and (!@options.validator? or @options.validator.test(v))
726
+
727
+ matching: () =>
728
+ @confirmation.val() is @field.val()
729
+
730
+ stumbit: (e) =>
731
+ if @blocked
732
+ e.preventDefault()
733
+ else
734
+ @field.val("") if @field.val() is @mock_password
735
+
736
+
737
+ $.fn.password_field = ->
738
+ @each ->
739
+ new PasswordField(@)
740
+
741
+
742
+ $.fn.submitter = ->
743
+ @click (e) ->
744
+ $(@).addClass('waiting').text('Please wait').bind "click", (e) =>
745
+ e.preventDefault() if e
746
+