droom 0.0.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -2
- data/app/assets/images/droom/Untitled-1.png +0 -0
- data/app/assets/images/droom/acceptance.png +0 -0
- data/app/assets/images/droom/add.png +0 -0
- data/app/assets/images/droom/asterisk.png +0 -0
- data/app/assets/images/droom/asterisk_up.png +0 -0
- data/app/assets/images/droom/checkboxes.png +0 -0
- data/app/assets/images/droom/clear.png +0 -0
- data/app/assets/images/droom/cog.png +0 -0
- data/app/assets/images/droom/copy.png +0 -0
- data/app/assets/images/droom/copy_to_dropbox.png +0 -0
- data/app/assets/images/droom/crosses.png +0 -0
- data/app/assets/images/droom/dark_scrap_icons.png +0 -0
- data/app/assets/images/droom/defilter.png +0 -0
- data/app/assets/images/droom/delete.png +0 -0
- data/app/assets/images/droom/dropbox.png +0 -0
- data/app/assets/images/droom/dropbox_button.png +0 -0
- data/app/assets/images/droom/dropbox_button_small.png +0 -0
- data/app/assets/images/droom/edit.png +0 -0
- data/app/assets/images/droom/expand.png +0 -0
- data/app/assets/images/droom/expander.png +0 -0
- data/app/assets/images/droom/folder.png +0 -0
- data/app/assets/images/droom/image_box.png +0 -0
- data/app/assets/images/droom/in_dropbox.png +0 -0
- data/app/assets/images/droom/integration_buttons.png +0 -0
- data/app/assets/images/droom/leftright.png +0 -0
- data/app/assets/images/droom/manage.png +0 -0
- data/app/assets/images/droom/medium_object_icons.png +0 -0
- data/app/assets/images/droom/object_icons.png +0 -0
- data/app/assets/images/droom/playable.png +0 -0
- data/app/assets/images/droom/revoke.png +0 -0
- data/app/assets/images/droom/scrap_icons.png +0 -0
- data/app/assets/images/droom/search.png +0 -0
- data/app/assets/images/droom/small_asterisk.png +0 -0
- data/app/assets/images/droom/small_cog.png +0 -0
- data/app/assets/images/droom/small_download.png +0 -0
- data/app/assets/images/droom/small_image_box.png +0 -0
- data/app/assets/images/droom/smalladd.png +0 -0
- data/app/assets/images/droom/smallarrows.png +0 -0
- data/app/assets/images/droom/smallcopy.png +0 -0
- data/app/assets/images/droom/smalldelete.png +0 -0
- data/app/assets/images/droom/smalldownload.png +0 -0
- data/app/assets/images/droom/smalledit.png +0 -0
- data/app/assets/images/droom/smallmanage.png +0 -0
- data/app/assets/images/droom/star.png +0 -0
- data/app/assets/images/droom/symbols.png +0 -0
- data/app/assets/images/droom/tinyarrows.png +0 -0
- data/app/assets/images/droom/to_dropbox.png +0 -0
- data/app/assets/images/droom/twisty.png +0 -0
- data/app/assets/images/droom/youtube_icon.png +0 -0
- data/app/assets/javascripts/droom.js.coffee +62 -65
- data/app/assets/javascripts/droom/actions.js.coffee +492 -0
- data/app/assets/javascripts/droom/ajax.js.coffee +84 -0
- data/app/assets/javascripts/droom/{lib/extensions.js.coffee → extensions.js.coffee} +22 -15
- data/app/assets/javascripts/droom/lib/ZeroClipboard.js +334 -0
- data/app/assets/javascripts/droom/lib/ZeroClipboard.swf +0 -0
- data/app/assets/javascripts/droom/lib/ZeroClipboard10.swf +0 -0
- data/app/assets/javascripts/droom/map.js.coffee +124 -86
- data/app/assets/javascripts/droom/popups.js.coffee +252 -0
- data/app/assets/javascripts/droom/stream.js.coffee +92 -0
- data/app/assets/javascripts/droom/utilities.js.coffee +120 -0
- data/app/assets/javascripts/droom/widgets.js.coffee +1209 -0
- data/app/assets/stylesheets/_definitions.css.sass +189 -0
- data/app/assets/stylesheets/droom.css.sass +1429 -376
- data/app/assets/stylesheets/lib/_kalendae.css.sass +2 -5
- data/app/assets/stylesheets/lib/_popups.css.sass +86 -0
- data/app/assets/stylesheets/lib/_toolbar.css.sass +17 -11
- data/app/controllers/droom/agenda_categories_controller.rb +24 -0
- data/app/controllers/droom/calendars_controller.rb +29 -0
- data/app/controllers/droom/confirmations_controller.rb +38 -0
- data/app/controllers/droom/dashboard_controller.rb +2 -0
- data/app/controllers/droom/documents_controller.rb +42 -51
- data/app/controllers/droom/dropbox_tokens_controller.rb +61 -0
- data/app/controllers/droom/engine_controller.rb +8 -23
- data/app/controllers/droom/events_controller.rb +39 -27
- data/app/controllers/droom/folders_controller.rb +106 -0
- data/app/controllers/droom/group_invitations_controller.rb +15 -7
- data/app/controllers/droom/groups_controller.rb +4 -4
- data/app/controllers/droom/invitations_controller.rb +37 -9
- data/app/controllers/droom/memberships_controller.rb +5 -3
- data/app/controllers/droom/organisations_controller.rb +53 -0
- data/app/controllers/droom/people_controller.rb +46 -27
- data/app/controllers/droom/preferences_controller.rb +34 -0
- data/app/controllers/droom/scraps_controller.rb +77 -0
- data/app/controllers/droom/search_controller.rb +66 -0
- data/app/controllers/droom/suggestions_controller.rb +22 -35
- data/app/controllers/droom/users_controller.rb +69 -0
- data/app/controllers/droom/venues_controller.rb +1 -2
- data/app/controllers/droom/youtube_controller.rb +22 -0
- data/app/helpers/droom/droom_helper.rb +93 -5
- data/app/models/droom/agenda_category.rb +8 -1
- data/app/models/droom/calendar.rb +19 -0
- data/app/models/droom/category.rb +4 -6
- data/app/models/droom/document.rb +103 -64
- data/app/models/droom/document_attachment.rb +13 -47
- data/app/models/droom/dropbox_document.rb +41 -0
- data/app/models/droom/dropbox_token.rb +29 -0
- data/app/models/droom/event.rb +125 -90
- data/app/models/droom/folder.rb +134 -0
- data/app/models/droom/group.rb +68 -27
- data/app/models/droom/group_invitation.rb +12 -3
- data/app/models/droom/invitation.rb +50 -8
- data/app/models/droom/mailing_list_membership.rb +128 -0
- data/app/models/droom/membership.rb +71 -9
- data/app/models/droom/organisation.rb +22 -0
- data/app/models/droom/page.rb +3 -3
- data/app/models/droom/person.rb +180 -166
- data/app/models/droom/personal_folder.rb +31 -0
- data/app/models/droom/preference.rb +35 -0
- data/app/models/droom/recurrence_rule.rb +1 -1
- data/app/models/droom/scrap.rb +110 -0
- data/app/models/droom/tag.rb +120 -0
- data/app/models/droom/tagging.rb +13 -0
- data/app/models/droom/user.rb +211 -0
- data/app/models/droom/user_action_observer.rb +12 -0
- data/app/models/droom/venue.rb +56 -39
- data/app/views/cropper/uploads/_crop.html.haml +46 -0
- data/app/views/devise/mailer/confirmation_instructions.en.html.haml +24 -0
- data/app/views/devise/mailer/reset_password_instructions.en.html.haml +21 -0
- data/app/views/devise/passwords/edit.html.haml +31 -0
- data/app/views/devise/passwords/new.html.haml +21 -0
- data/app/views/devise/sessions/new.html.haml +33 -0
- data/app/views/droom/agenda_categories/_form.html.haml +22 -0
- data/app/views/droom/agenda_categories/new.html.haml +6 -0
- data/app/views/droom/calendars/_action_menu.html.haml +6 -0
- data/app/views/droom/calendars/index.html.haml +12 -0
- data/app/views/droom/calendars/show.rss.builder +0 -0
- data/app/views/droom/confirmations/show.html.haml +40 -0
- data/app/views/droom/dashboard/_calendar.html.haml +1 -0
- data/app/views/droom/dashboard/_folders.html.haml +5 -0
- data/app/views/droom/dashboard/_future_events.html.haml +18 -0
- data/app/views/droom/dashboard/_invitations.html.haml +1 -0
- data/app/views/droom/dashboard/_past_events.haml +14 -0
- data/app/views/droom/dashboard/_quicksearch.html.haml +6 -0
- data/app/views/droom/dashboard/_stream.html.haml +9 -0
- data/app/views/droom/dashboard/index.html.haml +5 -3
- data/app/views/droom/documents/_action_menu.html.haml +7 -0
- data/app/views/droom/documents/_documents.html.haml +5 -0
- data/app/views/droom/documents/_documents_list.html.haml +6 -6
- data/app/views/droom/documents/_documents_table.html.haml +29 -10
- data/app/views/droom/documents/_form.html.haml +3 -7
- data/app/views/droom/documents/_listing.html.haml +5 -6
- data/app/views/droom/documents/_pagination.html.haml +8 -0
- data/app/views/droom/documents/_search_result.html.haml +14 -0
- data/app/views/droom/documents/_suggested.html.haml +0 -5
- data/app/views/droom/documents/_table_document.html.haml +2 -8
- data/app/views/droom/documents/edit.html.haml +7 -1
- data/app/views/droom/documents/index.html.haml +10 -27
- data/app/views/droom/documents/new.html.haml +10 -1
- data/app/views/droom/dropbox_tokens/new.html.haml +6 -0
- data/app/views/droom/events/_action_menu.html.haml +15 -0
- data/app/views/droom/events/_event.html.haml +67 -57
- data/app/views/droom/events/_form.html.haml +15 -4
- data/app/views/droom/events/_invitations.html.haml +4 -19
- data/app/views/droom/events/_search_result.html.haml +19 -0
- data/app/views/droom/events/_views.html.haml +2 -2
- data/app/views/droom/events/edit.html.haml +7 -1
- data/app/views/droom/events/index.html.haml +27 -10
- data/app/views/droom/events/index.rss.builder +1 -1
- data/app/views/droom/events/new.html.haml +6 -1
- data/app/views/droom/events/show.html.haml +5 -1
- data/app/views/droom/folders/_action_menu.html.haml +14 -0
- data/app/views/droom/folders/_attachments.html.haml +3 -0
- data/app/views/droom/folders/_contents.html.haml +6 -0
- data/app/views/droom/folders/_folder.html.haml +20 -0
- data/app/views/droom/folders/_folders.html.haml +2 -0
- data/app/views/droom/folders/_form.html.haml +18 -0
- data/app/views/droom/folders/edit.html.haml +6 -0
- data/app/views/droom/folders/index.html.haml +32 -0
- data/app/views/droom/folders/new.html.haml +6 -0
- data/app/views/droom/folders/show.html.haml +1 -0
- data/app/views/droom/group_invitations/_form.html.haml +15 -4
- data/app/views/droom/group_invitations/new.html.haml +6 -1
- data/app/views/droom/groups/_action_menu.haml +11 -0
- data/app/views/droom/groups/_form.html.haml +10 -11
- data/app/views/droom/groups/_group.html.haml +9 -10
- data/app/views/droom/groups/_groups.html.haml +2 -3
- data/app/views/droom/groups/_search_result.html.haml +16 -0
- data/app/views/droom/groups/_suggested.html.haml +7 -0
- data/app/views/droom/groups/edit.html.haml +7 -1
- data/app/views/droom/groups/index.html.haml +6 -3
- data/app/views/droom/groups/new.html.haml +7 -0
- data/app/views/droom/groups/show.html.haml +1 -1
- data/app/views/droom/invitations/_created.html.haml +1 -1
- data/app/views/droom/invitations/_form.html.haml +9 -5
- data/app/views/droom/invitations/_invitation.html.haml +9 -0
- data/app/views/droom/invitations/_invitations.html.haml +8 -0
- data/app/views/droom/invitations/new.html.haml +6 -1
- data/app/views/droom/memberships/_member.html.haml +1 -1
- data/app/views/droom/memberships/_membership_toggle.html.haml +10 -0
- data/app/views/droom/memberships/new.html.haml +10 -0
- data/app/views/droom/organisations/_action_menu.html.haml +7 -0
- data/app/views/droom/organisations/_form.html.haml +25 -0
- data/app/views/droom/organisations/_organisation.html.haml +23 -0
- data/app/views/droom/organisations/_organisations.html.haml +2 -0
- data/app/views/droom/organisations/_suggested.html.haml +7 -0
- data/app/views/droom/organisations/edit.html.haml +6 -0
- data/app/views/droom/organisations/index.html.haml +20 -0
- data/app/views/droom/organisations/new.html.haml +6 -0
- data/app/views/droom/organisations/show.html.haml +1 -0
- data/app/views/droom/pages/_full_page.html.haml +1 -1
- data/app/views/droom/pages/index.html.haml +1 -1
- data/app/views/droom/panels/_account.html.haml +16 -0
- data/app/views/droom/panels/_admin.html.haml +23 -0
- data/app/views/droom/panels/_devices.html.haml +19 -0
- data/app/views/droom/panels/_dropbox.html.haml +17 -0
- data/app/views/droom/panels/_email.html.haml +16 -0
- data/app/views/droom/panels/_keywords.html.haml +3 -0
- data/app/views/droom/panels/_networks.html.haml +3 -0
- data/app/views/droom/panels/_readers.html.haml +18 -0
- data/app/views/droom/panels/_rss.html.haml +10 -0
- data/app/views/droom/panels/_search.html.haml +16 -0
- data/app/views/droom/panels/_vcal.html.haml +3 -0
- data/app/views/droom/people/_action_menu.html.haml +22 -0
- data/app/views/droom/people/_form.html.haml +69 -39
- data/app/views/droom/people/_listing.html.haml +19 -0
- data/app/views/droom/people/_memberships.html.haml +24 -0
- data/app/views/droom/people/_people.html.haml +5 -29
- data/app/views/droom/people/_person.html.haml +30 -32
- data/app/views/droom/people/_search_result.html.haml +14 -0
- data/app/views/droom/people/_suggested.html.haml +10 -5
- data/app/views/droom/people/edit.html.haml +9 -1
- data/app/views/droom/people/index.html.haml +6 -9
- data/app/views/droom/people/new.html.haml +6 -1
- data/app/views/droom/people/show.html.haml +1 -1
- data/app/views/droom/preferences/_checkbox.html.haml +7 -0
- data/app/views/droom/preferences/_radio_set.html.haml +9 -0
- data/app/views/droom/scraps/_action_menu.html.haml +9 -0
- data/app/views/droom/scraps/_caption.html.haml +9 -0
- data/app/views/droom/scraps/_credit.html.haml +10 -0
- data/app/views/droom/scraps/_form.html.haml +77 -0
- data/app/views/droom/scraps/_heading.html.haml +16 -0
- data/app/views/droom/scraps/_scrap.html.haml +17 -0
- data/app/views/droom/scraps/_search_result.html.haml +5 -0
- data/app/views/droom/scraps/_stream.html.haml +4 -0
- data/app/views/droom/scraps/_suggested.html.haml +5 -0
- data/app/views/droom/scraps/_thumb.html.haml +5 -0
- data/app/views/droom/scraps/edit.html.haml +2 -0
- data/app/views/droom/scraps/full/_document.html.haml +6 -0
- data/app/views/droom/scraps/full/_event.html.haml +20 -0
- data/app/views/droom/scraps/full/_image.html.haml +4 -0
- data/app/views/droom/scraps/full/_link.html.haml +5 -0
- data/app/views/droom/scraps/full/_quote.html.haml +8 -0
- data/app/views/droom/scraps/full/_text.html.haml +4 -0
- data/app/views/droom/scraps/full/_video.html.haml +4 -0
- data/app/views/droom/scraps/index.atom.builder +26 -0
- data/app/views/droom/scraps/index.html.haml +15 -0
- data/app/views/droom/scraps/new.html.haml +2 -0
- data/app/views/droom/scraps/show.html.haml +4 -0
- data/app/views/droom/scraps/thumbs/_document.html.haml +9 -0
- data/app/views/droom/scraps/thumbs/_event.html.haml +17 -0
- data/app/views/droom/scraps/thumbs/_image.html.haml +6 -0
- data/app/views/droom/scraps/thumbs/_link.html.haml +10 -0
- data/app/views/droom/scraps/thumbs/_quote.html.haml +8 -0
- data/app/views/droom/scraps/thumbs/_text.html.haml +8 -0
- data/app/views/droom/scraps/thumbs/_video.html.haml +6 -0
- data/app/views/droom/shared/_controls.html.haml +5 -3
- data/app/views/droom/shared/_excerpt.html.haml +2 -0
- data/app/views/droom/shared/_navigation.html.haml +4 -4
- data/app/views/droom/shared/_panels.html.haml +4 -0
- data/app/views/droom/shared/_quicklinks.html.haml +5 -0
- data/app/views/droom/shared/_search_form.html.haml +2 -1
- data/app/views/droom/shared/_search_results.html.haml +9 -0
- data/app/views/droom/shared/_suggestions.html.haml +13 -9
- data/app/views/droom/shared/_title_excerpt.haml +2 -0
- data/app/views/droom/shared/search.html.haml +18 -0
- data/app/views/droom/users/_confirmation.html.haml +2 -0
- data/app/views/droom/users/_dropbox_status.html.haml +2 -0
- data/app/views/droom/users/_form.html.haml +34 -0
- data/app/views/droom/users/_user.html.haml +2 -0
- data/app/views/droom/users/_user_or_person.html.haml +63 -0
- data/app/views/droom/users/_users_table.html.haml +17 -0
- data/app/views/droom/users/edit.html.haml +10 -0
- data/app/views/droom/users/index.html.haml +42 -0
- data/app/views/droom/users/unwelcome.en.html.haml +17 -0
- data/app/views/droom/venues/_search_result.html.haml +17 -0
- data/app/views/droom/youtube/show.html.haml +12 -0
- data/app/views/kaminari/_paginator.html.haml +16 -0
- data/app/views/layouts/droom/application.html.haml +17 -17
- data/app/views/layouts/droom/email.html.haml +59 -0
- data/config/cucumber.yml +8 -0
- data/config/initializers/devise.rb +240 -0
- data/config/locales/devise.en.yml +58 -0
- data/config/locales/en.yml +203 -32
- data/config/routes.rb +55 -16
- data/config/sunspot.yml +17 -0
- data/db/migrate/20121120115447_tags.rb +18 -0
- data/db/migrate/20130116155331_dropbox_tokens.rb +9 -0
- data/db/migrate/20130118103540_folders.rb +47 -0
- data/db/migrate/20130124113715_preferences.rb +10 -0
- data/db/migrate/20130125132427_users_in_droom.rb +58 -0
- data/db/migrate/20130129142307_mobile_phones.rb +7 -0
- data/db/migrate/20130130120124_folder_ancestry_to_parents.rb +7 -0
- data/db/migrate/20130131161430_mailing_lists.rb +37 -0
- data/db/migrate/20130207091903_preference_uuids.rb +5 -0
- data/db/migrate/20130207123614_stream.rb +13 -0
- data/db/migrate/20130208035113_access_token_secret.rb +5 -0
- data/db/migrate/20130214145014_person_image.rb +9 -0
- data/db/migrate/20130217152644_devise_confirmable.rb +13 -0
- data/db/migrate/20130225095328_create_droom_calendars.rb +20 -0
- data/db/migrate/20130226092944_give_scraps_document.rb +5 -0
- data/db/migrate/20130228083509_privateness.rb +9 -0
- data/db/migrate/20130228134143_store_metadata.rb +5 -0
- data/db/migrate/20130305150648_folders_nicely.rb +6 -0
- data/db/migrate/20130305151936_niceties.rb +7 -0
- data/db/migrate/20130308103807_create_droom_dropbox_documents.rb +11 -0
- data/db/migrate/20130308154552_dropbox_documents_changed.rb +5 -0
- data/db/migrate/20130326135738_user_titles.rb +5 -0
- data/lib/droom.rb +189 -22
- data/lib/droom/engine.rb +6 -1
- data/lib/droom/folders.rb +106 -0
- data/lib/droom/lazy_hash.rb +73 -0
- data/lib/droom/{helpers.rb → model_helpers.rb} +1 -1
- data/lib/droom/monkeys.rb +21 -2
- data/lib/droom/routing.rb +13 -0
- data/lib/droom/searchability.rb +25 -0
- data/lib/droom/taggability.rb +60 -0
- data/lib/droom/title_case.rb +101 -0
- data/lib/droom/user_config.rb +67 -0
- data/lib/droom/validators.rb +6 -0
- data/lib/droom/version.rb +1 -1
- data/lib/generators/droom/install/templates/droom_initializer.rb +0 -1
- data/lib/tasks/cucumber.rake +65 -0
- data/spec/datasets/calendar_events_dataset.rb +44 -0
- data/spec/datasets/calendar_pages_dataset.rb +8 -0
- data/spec/datasets/calendar_sites_dataset.rb +6 -0
- data/spec/datasets/calendars_dataset.rb +34 -0
- data/spec/datasets/documents_dataset.rb +28 -0
- data/spec/datasets/recurrence_dataset.rb +7 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +65 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +21 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +38 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +38 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +7 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/sunspot.yml +17 -0
- data/spec/dummy/db/migrate/20120911070124_create_droom_data.droom.rb +135 -0
- data/spec/dummy/db/migrate/20120921094604_agenda_sections.droom.rb +14 -0
- data/spec/dummy/db/migrate/20120921094605_add_postal_address_to_people.droom.rb +11 -0
- data/spec/dummy/db/migrate/20121102064845_give_groups_descriptions.droom.rb +6 -0
- data/spec/dummy/db/migrate/20121102064846_more_names.droom.rb +6 -0
- data/spec/dummy/db/migrate/20121102064847_event_agenda_sections.droom.rb +14 -0
- data/spec/dummy/db/migrate/20121102064848_create_group_invitations.droom.rb +11 -0
- data/spec/dummy/db/migrate/20121102064849_give_people_positions.droom.rb +6 -0
- data/spec/dummy/db/migrate/20121102064850_help_pages.droom.rb +14 -0
- data/spec/dummy/db/migrate/20121102064851_category_slugs.droom.rb +6 -0
- data/spec/dummy/db/migrate/20121102064852_document_links.droom.rb +15 -0
- data/spec/dummy/db/migrate/20121102064853_people_visibility.droom.rb +8 -0
- data/spec/dummy/db/migrate/20121102094820_shy_people.droom.rb +6 -0
- data/spec/dummy/db/migrate/20121102095927_visibility_defaults.droom.rb +10 -0
- data/spec/dummy/db/migrate/20130121122352_tags.droom.rb +19 -0
- data/spec/dummy/db/migrate/20130121122353_dropbox.droom.rb +10 -0
- data/spec/dummy/db/migrate/20130121122354_folders.droom.rb +48 -0
- data/spec/dummy/db/migrate/20130128142902_memberships_expire.droom.rb +6 -0
- data/spec/dummy/db/migrate/20130128142903_give_documents_extracted_text.droom.rb +6 -0
- data/spec/dummy/db/migrate/20130128142904_preferences.droom.rb +11 -0
- data/spec/dummy/db/migrate/20130128142905_users_in_droom.droom.rb +59 -0
- data/spec/dummy/db/migrate/20130130120631_folder_ancestry_to_parents.droom.rb +8 -0
- data/spec/dummy/db/migrate/20130201103737_mobile_phones.droom.rb +8 -0
- data/spec/dummy/db/migrate/20130201103738_mailing_lists.droom.rb +38 -0
- data/spec/dummy/db/schema.rb +327 -0
- data/spec/dummy/log/sunspot-solr-test.log.1 +222 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/system/droom/documents/files/000/000/001/original/frog.png +0 -0
- data/spec/dummy/public/system/droom/documents/files/000/000/001/original/rat.png +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/solr/conf/admin-extra.html +31 -0
- data/spec/dummy/solr/conf/elevate.xml +36 -0
- data/spec/dummy/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
- data/spec/dummy/solr/conf/protwords.txt +21 -0
- data/spec/dummy/solr/conf/schema.xml +238 -0
- data/spec/dummy/solr/conf/scripts.conf +24 -0
- data/spec/dummy/solr/conf/solrconfig.xml +934 -0
- data/spec/dummy/solr/conf/spellings.txt +2 -0
- data/spec/dummy/solr/conf/stopwords.txt +58 -0
- data/spec/dummy/solr/conf/synonyms.txt +31 -0
- data/spec/dummy/solr/data/development/index/segments.gen +0 -0
- data/spec/dummy/solr/data/development/index/segments_1 +0 -0
- data/spec/dummy/solr/data/development/spellchecker/segments.gen +0 -0
- data/spec/dummy/solr/data/development/spellchecker/segments_1 +0 -0
- data/spec/dummy/solr/data/test/index/segments.gen +0 -0
- data/spec/dummy/solr/data/test/index/segments_3nu +0 -0
- data/spec/dummy/solr/data/test/spellchecker/segments.gen +0 -0
- data/spec/dummy/solr/data/test/spellchecker/segments_1 +0 -0
- data/spec/dummy/webdav/1/group/rat.png +0 -0
- data/spec/dummy/webdav/1/unattached/rat.png +0 -0
- data/spec/dummy/webdav/group/rat.png +0 -0
- data/spec/dummy/webdav/unattached/rat.png +0 -0
- data/spec/dummy/webdav/unattached/rat_v1.png +0 -0
- data/spec/factories/agenda_categories.rb +5 -0
- data/spec/factories/categories.rb +5 -0
- data/spec/factories/documents.rb +16 -0
- data/spec/factories/events.rb +41 -0
- data/spec/factories/folders.rb +5 -0
- data/spec/factories/groups.rb +6 -0
- data/spec/factories/memberships.rb +5 -0
- data/spec/factories/people.rb +16 -0
- data/spec/factories/recurrence_rules.rb +25 -0
- data/spec/factories/user.rb +6 -0
- data/spec/fixtures/dummy.ics +59 -0
- data/spec/fixtures/images/frog.png +0 -0
- data/spec/fixtures/images/rat.png +0 -0
- data/spec/fixtures/ny.ics +36 -0
- data/spec/lib/droom/folders_spec.rb +46 -0
- data/spec/lib/droom/lazy_hash_spec.rb +33 -0
- data/spec/models/droom/agenda_category_spec.rb +15 -0
- data/spec/models/droom/document_spec.rb +76 -0
- data/spec/models/droom/event_spec.rb +154 -0
- data/spec/models/droom/folder_spec.rb +5 -0
- data/spec/models/droom/ical_spec.rb +62 -0
- data/spec/models/droom/mailing_list_membership_spec.rb +81 -0
- data/spec/models/droom/membership_spec.rb +22 -0
- data/spec/models/droom/person_spec.rb +72 -0
- data/spec/models/droom/recurrence_rule_spec.rb +81 -0
- data/spec/models/droom/user_spec.rb +27 -0
- data/spec/models/droom/visibility_spec.rb +6 -0
- data/spec/spec_helper.rb +119 -0
- data/spec/support/matchers/content_type_matcher.rb +17 -0
- metadata +591 -36
- data/app/assets/javascripts/droom/calendar.js.coffee +0 -121
- data/app/assets/javascripts/droom/drag_sort.js.coffee +0 -22
- data/app/assets/javascripts/droom/forms.js.coffee +0 -746
- data/app/assets/javascripts/droom/sort.js.coffee +0 -126
- data/app/assets/javascripts/droom/suggester.js.coffee +0 -230
- data/app/controllers/droom/document_attachments_controller.rb +0 -19
- data/app/models/droom/document_link.rb +0 -31
- data/app/models/droom/personal_document.rb +0 -98
- data/app/views/droom/dashboard/_marginalia.html.haml +0 -3
- data/app/views/droom/dashboard/_my_future_events.html.haml +0 -9
- data/app/views/droom/dashboard/_my_group_documents.html.haml +0 -6
- data/app/views/droom/dashboard/_my_past_events.haml +0 -6
- data/app/views/droom/documents/_event_document_form.html.haml +0 -15
- data/app/views/droom/errors/bang.html.haml +0 -12
- data/app/views/droom/errors/not_allowed.html.haml +0 -12
- data/app/views/droom/errors/not_found.html.haml +0 -12
- data/app/views/droom/events/_attachment.html.haml +0 -1
- data/app/views/droom/events/_attachment_list.html.haml +0 -4
- data/app/views/droom/invitations/_attending_people.html.haml +0 -8
- data/config/initializers/dav.rb +0 -2
@@ -0,0 +1,92 @@
|
|
1
|
+
jQuery ($) ->
|
2
|
+
$.fn.scrap_form = () ->
|
3
|
+
@each ->
|
4
|
+
new ScrapForm(@)
|
5
|
+
@
|
6
|
+
|
7
|
+
$.fn.type_setter = (scrapform) ->
|
8
|
+
@change ->
|
9
|
+
if $(@).is('checked')
|
10
|
+
scrapform.setType($(@).val())
|
11
|
+
|
12
|
+
class ScrapForm
|
13
|
+
constructor: (element) ->
|
14
|
+
@_container = $(element)
|
15
|
+
@_header = @_container.find('.scraptypes')
|
16
|
+
@_fields = @_container.find('.fields')
|
17
|
+
@_header.find('input:radio').change @setType
|
18
|
+
@_body = @_fields.find('.body')
|
19
|
+
@_caption = @_fields.find('.caption')
|
20
|
+
@_image = @_fields.find('.upload')
|
21
|
+
@_event = @_fields.find('.scrapevent')
|
22
|
+
@_document = @_fields.find('.scrapdocument')
|
23
|
+
@_video = @_fields.find('.scrapvideo')
|
24
|
+
@_video.find('input').video_picker()
|
25
|
+
@_thumb = @_video.find '.thumb'
|
26
|
+
@setType()
|
27
|
+
@getVideo()
|
28
|
+
|
29
|
+
getVideo: =>
|
30
|
+
if @_header.find('input:radio:checked').val() == "video"
|
31
|
+
yt_id = @_video.find('input.name').val()
|
32
|
+
$.ajax
|
33
|
+
url: "/videos/#{yt_id}.js"
|
34
|
+
type: "GET"
|
35
|
+
dataType: "html"
|
36
|
+
success: (data) =>
|
37
|
+
@_thumb.append data
|
38
|
+
|
39
|
+
setType: () =>
|
40
|
+
scraptype = @_header.find('input:radio:checked').val()
|
41
|
+
@_fields.attr("class", "fields #{scraptype}")
|
42
|
+
switch scraptype
|
43
|
+
when "text"
|
44
|
+
@_body.find('textarea').attr("placeholder", "Your remarks")
|
45
|
+
@textBased()
|
46
|
+
when "quote"
|
47
|
+
@_body.find('textarea').attr("placeholder", "Quote text")
|
48
|
+
@textBased()
|
49
|
+
when "link"
|
50
|
+
@_body.find('textarea').attr("placeholder", "Link address")
|
51
|
+
@textBased()
|
52
|
+
when "video"
|
53
|
+
@_body.find('textarea').attr("placeholder", "Youtube ID")
|
54
|
+
@videoBased()
|
55
|
+
when "image"
|
56
|
+
@imageBased()
|
57
|
+
when "event"
|
58
|
+
@eventBased()
|
59
|
+
when "document"
|
60
|
+
@documentBased()
|
61
|
+
|
62
|
+
imageBased: =>
|
63
|
+
@_image.show().find('input').prop('disabled', false)
|
64
|
+
@_document.remove().find('input').prop('disabled', true)
|
65
|
+
@_event.hide().find('input').prop('disabled', true)
|
66
|
+
@_body.hide().find('input, textarea').prop('disabled', true)
|
67
|
+
|
68
|
+
textBased: =>
|
69
|
+
@_image.hide().find('input').prop('disabled', true)
|
70
|
+
@_document.remove().find('input').prop('disabled', true)
|
71
|
+
@_event.hide().find('input').prop('disabled', true)
|
72
|
+
@_body.show().find('input, textarea').prop('disabled', false)
|
73
|
+
|
74
|
+
eventBased: =>
|
75
|
+
@_image.hide().find('input').prop('disabled', true)
|
76
|
+
@_document.remove().find('input').prop('disabled', true)
|
77
|
+
@_event.show().find('input').prop('disabled', false)
|
78
|
+
@_body.hide().find('input, textarea').prop('disabled', true)
|
79
|
+
|
80
|
+
documentBased: =>
|
81
|
+
@_image.hide().find('input').prop('disabled', true)
|
82
|
+
@_fields.prepend @_document
|
83
|
+
@_document = @_fields.find('.scrapdocument').activate()
|
84
|
+
@_document.show().find('input').prop('disabled', false)
|
85
|
+
@_event.hide().find('input').prop('disabled', true)
|
86
|
+
@_body.hide().find('input, textarea').prop('disabled', true)
|
87
|
+
|
88
|
+
videoBased: =>
|
89
|
+
@_image.hide().find('input').prop('disabled', true)
|
90
|
+
@_document.remove().find('input').prop('disabled', true)
|
91
|
+
@_event.hide().find('input').prop('disabled', true)
|
92
|
+
@_body.hide().find('input, textarea').prop('disabled', false)
|
@@ -0,0 +1,120 @@
|
|
1
|
+
jQuery ($) ->
|
2
|
+
|
3
|
+
## Utilities
|
4
|
+
# **$.makeGuid** is a neat little rfc4122 generator cribbed from
|
5
|
+
# http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
|
6
|
+
#
|
7
|
+
$.makeGuid = ()->
|
8
|
+
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
|
9
|
+
r = Math.random()*16|0
|
10
|
+
v = if c is 'x' then r else r & 0x3 | 0x8
|
11
|
+
v.toString 16
|
12
|
+
|
13
|
+
# **$.urlParam** is a query string parser used to restore history from one or more query string parameters.
|
14
|
+
# It's regex based and deeply suspect. If you can populate and read a form field, do that instead.
|
15
|
+
#
|
16
|
+
$.urlParam = (name) ->
|
17
|
+
results = new RegExp("[\\?&]" + name + "=([^&#]*)").exec(window.location.href)
|
18
|
+
return false unless results
|
19
|
+
results[1] or null
|
20
|
+
|
21
|
+
# This sends a command to the youtube API event listener in an iframe player.
|
22
|
+
# commands include pauseVideo, playVideo, etc.
|
23
|
+
|
24
|
+
$.fn.sendCommand = (command, args) ->
|
25
|
+
@each ->
|
26
|
+
payload = JSON.stringify
|
27
|
+
"event": "command"
|
28
|
+
"func": command
|
29
|
+
"args": args || [],
|
30
|
+
"id": @id
|
31
|
+
@contentWindow.postMessage payload, "*"
|
32
|
+
|
33
|
+
# Radio buttons don't fire a change event when deselected. Here we mimic that effect
|
34
|
+
# by firing it from any other input in that button set when it *is* selected.
|
35
|
+
#
|
36
|
+
$.fn.trigger_change_on_deselect = ->
|
37
|
+
@each ->
|
38
|
+
input = $(@)
|
39
|
+
name = input.attr('name')
|
40
|
+
$("input[name='#{name}']:radio").not(input).change () ->
|
41
|
+
input.change() if $(@).is(":checked")
|
42
|
+
|
43
|
+
$.easing.glide = (x, t, b, c, d) ->
|
44
|
+
-c * ((t=t/d-1)*t*t*t - 1) + b
|
45
|
+
|
46
|
+
$.easing.boing = (x, t, b, c, d, s) ->
|
47
|
+
s ?= 1.70158;
|
48
|
+
c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b
|
49
|
+
|
50
|
+
$.easing.expo = (x, t, b, c, d) ->
|
51
|
+
(t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b
|
52
|
+
|
53
|
+
$.add_stylesheet = (path) ->
|
54
|
+
if document.createStyleSheet
|
55
|
+
document.createStyleSheet(path)
|
56
|
+
else
|
57
|
+
$('head').append("<link rel=\"stylesheet\" href=\"#{path}\" type=\"text/css\" />");
|
58
|
+
|
59
|
+
$.namespace = (target, name, block) ->
|
60
|
+
[target, name, block] = [(if typeof exports isnt 'undefined' then exports else window), arguments...] if arguments.length < 3
|
61
|
+
top = target
|
62
|
+
target = target[item] or= {} for item in name.split '.'
|
63
|
+
block target, top
|
64
|
+
|
65
|
+
$.fn.find_including_self = (selector) ->
|
66
|
+
selection = @.find(selector)
|
67
|
+
selection.push @ if @is(selector)
|
68
|
+
selection
|
69
|
+
|
70
|
+
$.fn.self_or_ancestor = (selector) ->
|
71
|
+
if @is(selector) then this else @parents(selector)
|
72
|
+
|
73
|
+
$.ajaxError = (jqXHR, textStatus, errorThrown) =>
|
74
|
+
console.log "...error!", jqXHR, textStatus, errorThrown
|
75
|
+
trigger "error", textStatus, errorThrown
|
76
|
+
return
|
77
|
+
|
78
|
+
$.fn.flash = ->
|
79
|
+
@each ->
|
80
|
+
container = $(this)
|
81
|
+
container.fadeIn "fast"
|
82
|
+
$("<a href=\"#\" class=\"closer\">close</a>").prependTo(container)
|
83
|
+
container.bind "click", (e) ->
|
84
|
+
e.preventDefault()
|
85
|
+
container.fadeOut "fast"
|
86
|
+
|
87
|
+
$.fn.disappearAfter = (interval) ->
|
88
|
+
$(@).fadeOut "slow", () ->
|
89
|
+
$(@).remove()
|
90
|
+
|
91
|
+
$.fn.signal = (color, duration) ->
|
92
|
+
color ?= "#f7f283"
|
93
|
+
duration ?= 1000
|
94
|
+
@each ->
|
95
|
+
$(@).css('backgroundColor', color).animate({'backgroundColor': '#ffffff'}, duration)
|
96
|
+
|
97
|
+
$.fn.signal_confirmation = ->
|
98
|
+
@signal('#c7ebb4')
|
99
|
+
|
100
|
+
$.fn.signal_error = ->
|
101
|
+
@signal('#e55a51')
|
102
|
+
|
103
|
+
$.fn.signal_cancellation = ->
|
104
|
+
@signal('#a2a3a3')
|
105
|
+
|
106
|
+
$.fn.back_button = ->
|
107
|
+
@click (e) ->
|
108
|
+
e.preventDefault() if e
|
109
|
+
history.back()
|
110
|
+
true
|
111
|
+
|
112
|
+
$.activations = []
|
113
|
+
|
114
|
+
$.activate_with = (fn) ->
|
115
|
+
$.activations.push fn
|
116
|
+
|
117
|
+
$.fn.activate = () ->
|
118
|
+
$.each $.activations, (i, fn) =>
|
119
|
+
fn.apply(@)
|
120
|
+
@
|
@@ -0,0 +1,1209 @@
|
|
1
|
+
# This is a collection of interface elements. They're all self-contained. Most are attached to a
|
2
|
+
# form element and cause its value to change, but there are also some standalone widgets that live
|
3
|
+
# on the page and follow their own rules.
|
4
|
+
|
5
|
+
jQuery ($) ->
|
6
|
+
|
7
|
+
## Form Widgets
|
8
|
+
#
|
9
|
+
# These attach to a form element and provide a nicer interface by which to update its content.
|
10
|
+
|
11
|
+
class DatePicker
|
12
|
+
constructor: (element) ->
|
13
|
+
@_container = $(element)
|
14
|
+
@_trigger = @_container.find('a')
|
15
|
+
@_field = @_container.find('input')
|
16
|
+
@_holder = @_container.find('div.kal')
|
17
|
+
@_mon = @_container.find('span.mon')
|
18
|
+
@_dom = @_container.find('span.dom')
|
19
|
+
@_year = @_container.find('span.year')
|
20
|
+
@_kal = new Kalendae @_holder[0]
|
21
|
+
@_holder.hide()
|
22
|
+
@_trigger.click @toggle
|
23
|
+
@_container.click @contain
|
24
|
+
@_kal.subscribe 'change', () =>
|
25
|
+
@hide()
|
26
|
+
@_field.val(@_kal.getSelected())
|
27
|
+
[year, month, day] = @_kal.getSelected().split('-')
|
28
|
+
@_year.text(year)
|
29
|
+
@_dom.text(day)
|
30
|
+
@_mon.text(Kalendae.moment.monthsShort[parseInt(month, 10) - 1])
|
31
|
+
|
32
|
+
contain: (e) =>
|
33
|
+
e.stopPropagation() if e
|
34
|
+
|
35
|
+
toggle: (e) =>
|
36
|
+
e.preventDefault() if e
|
37
|
+
if @_holder.is(':visible') then @hide() else @show()
|
38
|
+
|
39
|
+
show: () =>
|
40
|
+
@_holder.fadeIn "fast", () =>
|
41
|
+
@_container.addClass('editing')
|
42
|
+
$(document).bind "click", @hide
|
43
|
+
|
44
|
+
hide: () =>
|
45
|
+
$(document).unbind "click", @hide
|
46
|
+
@_container.removeClass('editing')
|
47
|
+
@_holder.fadeOut("fast")
|
48
|
+
|
49
|
+
$.fn.date_picker = () ->
|
50
|
+
@each ->
|
51
|
+
new DatePicker(@)
|
52
|
+
@
|
53
|
+
|
54
|
+
|
55
|
+
class TimePicker
|
56
|
+
constructor: (element) ->
|
57
|
+
@field = $(element)
|
58
|
+
@holder = $('<div class="timepicker" />')
|
59
|
+
@dropdown = new Dropdown @field,
|
60
|
+
on_select: @select
|
61
|
+
on_keyup: @change
|
62
|
+
times = []
|
63
|
+
for i in [0..24]
|
64
|
+
times.push({value: "#{i}:00"})
|
65
|
+
times.push({value: "#{i}:30"})
|
66
|
+
@dropdown.populate(times)
|
67
|
+
@field.focus @show
|
68
|
+
@field.blur @hide
|
69
|
+
|
70
|
+
select: (value) =>
|
71
|
+
@field.val(value)
|
72
|
+
@field.trigger('change')
|
73
|
+
|
74
|
+
change: (e) =>
|
75
|
+
# this is called on keyup but only if the dropdown doesn't recognise the keypress as a command
|
76
|
+
@dropdown.match(@field.val())
|
77
|
+
|
78
|
+
show: (e) =>
|
79
|
+
@dropdown.show()
|
80
|
+
|
81
|
+
hide: (e) =>
|
82
|
+
@dropdown.hide()
|
83
|
+
|
84
|
+
$.fn.time_picker = ->
|
85
|
+
@each ->
|
86
|
+
new TimePicker(@)
|
87
|
+
|
88
|
+
|
89
|
+
#todo: this can now make proper use of the remote mechanism and doesn't need to take over the whole form any more.
|
90
|
+
#
|
91
|
+
class FilePicker
|
92
|
+
constructor: (element) ->
|
93
|
+
@_container = $(element)
|
94
|
+
@_form = if form = @_container.attr("data-form") then $(form) else @_container.parent()
|
95
|
+
@_holder = @_form.parent()
|
96
|
+
@_link = @_container.find('a.ul')
|
97
|
+
@_filefield = @_container.find('input[type="file"]')
|
98
|
+
@_tip = @_container.find('p.tip')
|
99
|
+
@_link.click_proxy(@_filefield)
|
100
|
+
@_extensions = ['doc', 'docx', 'pdf', 'xls', 'xlsx', 'jpg', 'png']
|
101
|
+
@_filefield.bind 'change', @pick
|
102
|
+
@_file = null
|
103
|
+
@_filename = ""
|
104
|
+
@_ext = ""
|
105
|
+
@_fields = @_container.siblings('.metadata')
|
106
|
+
@_form.submit @submit
|
107
|
+
|
108
|
+
pick: (e) =>
|
109
|
+
@_link.removeClass(@_extensions.join(' '))
|
110
|
+
if files = @_filefield[0].files
|
111
|
+
@_file = files.item(0)
|
112
|
+
@_tip.hide()
|
113
|
+
@showSelection() if @_file
|
114
|
+
|
115
|
+
submit: (e) =>
|
116
|
+
unless @_filefield.attr("disabled")
|
117
|
+
if @_file
|
118
|
+
e.preventDefault() if e
|
119
|
+
@_fields.hide()
|
120
|
+
@_notifier = $('<div class="notifier"></div>').appendTo @_form
|
121
|
+
@_label = $('<h3 class="filename"></h3>').appendTo @_notifier
|
122
|
+
@_progress = $('<div class="progress"></div>').appendTo @_notifier
|
123
|
+
@_bar = $('<div class="bar"></div>').appendTo @_progress
|
124
|
+
@_status = $('<div class="status"></div>').appendTo @_notifier
|
125
|
+
@_label.text(@_filename)
|
126
|
+
@send()
|
127
|
+
|
128
|
+
showSelection: =>
|
129
|
+
@_filename = @_file.name.split(/[\/\\]/).pop()
|
130
|
+
@_ext = @_filename.split('.').pop()
|
131
|
+
@_link.addClass(@_ext) if @_ext in @_extensions
|
132
|
+
$('input.name').val(@_filename)# if $('input.name').val() is ""
|
133
|
+
|
134
|
+
send: =>
|
135
|
+
formData = new FormData @_form.get(0)
|
136
|
+
@xhr = new XMLHttpRequest()
|
137
|
+
@xhr.onreadystatechange = @update
|
138
|
+
@xhr.upload.onprogress = @progress
|
139
|
+
@xhr.upload.onloadend = @finish
|
140
|
+
url = @_form.attr('action')
|
141
|
+
@xhr.open 'POST', url, true
|
142
|
+
@xhr.send formData
|
143
|
+
|
144
|
+
progress: (e) =>
|
145
|
+
@_status.text("Uploading. Please wait.")
|
146
|
+
if e.lengthComputable
|
147
|
+
full_width = @_progress.width()
|
148
|
+
progress_width = Math.round(full_width * e.loaded / e.total)
|
149
|
+
@_bar.width progress_width
|
150
|
+
|
151
|
+
update: =>
|
152
|
+
if @xhr.readyState == 4
|
153
|
+
if @xhr.status == 200
|
154
|
+
response = @xhr.responseText
|
155
|
+
@_form.remove()
|
156
|
+
@_holder.append(response)
|
157
|
+
@_holder.trigger "finished", response
|
158
|
+
|
159
|
+
finish: (e) =>
|
160
|
+
@_status.text("Indexing for search. Please wait.")
|
161
|
+
@_bar.css
|
162
|
+
"background-color": "green"
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
$.fn.file_picker = () ->
|
167
|
+
@each ->
|
168
|
+
new FilePicker @
|
169
|
+
|
170
|
+
$.fn.click_proxy = (target_selector) ->
|
171
|
+
this.bind "click", (e) ->
|
172
|
+
e.preventDefault()
|
173
|
+
$(target_selector).click()
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
$.fn.score_picker = () ->
|
179
|
+
@each ->
|
180
|
+
new ScorePicker @
|
181
|
+
|
182
|
+
class ScorePicker
|
183
|
+
constructor: (element) ->
|
184
|
+
@_field = $(element)
|
185
|
+
@_container = $('<div class="starpicker" />')
|
186
|
+
@_value = @_field.val()
|
187
|
+
@_value =
|
188
|
+
for i in [1..5]
|
189
|
+
do (i) =>
|
190
|
+
star = $('<span class="star" />')
|
191
|
+
star.attr('data-score', i)
|
192
|
+
star.bind "mouseover", (e) =>
|
193
|
+
@hover(e, star)
|
194
|
+
star.bind "mouseout", (e) =>
|
195
|
+
@unhover(e, star)
|
196
|
+
star.bind "click", (e) =>
|
197
|
+
@set(e, star)
|
198
|
+
@_container.append star
|
199
|
+
@_stars = @_container.find('span.star')
|
200
|
+
@_field.after(@_container)
|
201
|
+
@_field.hide()
|
202
|
+
|
203
|
+
hover: (e, star) =>
|
204
|
+
@unhover()
|
205
|
+
i = parseInt(star.attr('data-score'))
|
206
|
+
@_stars.slice(0, i).addClass('hovered')
|
207
|
+
|
208
|
+
unhover: (e, star) =>
|
209
|
+
@_stars.removeClass('hovered')
|
210
|
+
|
211
|
+
set: (e, star) =>
|
212
|
+
e.preventDefault if e
|
213
|
+
@unhover()
|
214
|
+
i = parseInt(star.attr('data-score'))
|
215
|
+
@_stars.removeClass('selected')
|
216
|
+
@_stars.slice(0, i).addClass('selected')
|
217
|
+
@_field.val(i)
|
218
|
+
|
219
|
+
|
220
|
+
$.fn.password_field = ->
|
221
|
+
@each ->
|
222
|
+
new PasswordField(@)
|
223
|
+
|
224
|
+
class PasswordField
|
225
|
+
constructor: (element, opts) ->
|
226
|
+
@options = $.extend
|
227
|
+
length: 6
|
228
|
+
, opts
|
229
|
+
@field = $(element)
|
230
|
+
@_notice = $('.password_notice')
|
231
|
+
@form = @field.parents('form')
|
232
|
+
@submit = @form.find('.submit')
|
233
|
+
@confirmation = $("#" + @field.attr("id") + "_confirmation")
|
234
|
+
@confirmation_holder = @confirmation.parents("p")
|
235
|
+
@mock_password = 'password'
|
236
|
+
@required = @field.attr('required')
|
237
|
+
@field.focus @wake
|
238
|
+
@field.blur @sleep
|
239
|
+
@field.keyup @check
|
240
|
+
@confirmation.keyup @check
|
241
|
+
@form.submit @stumbit
|
242
|
+
# to set up initial state
|
243
|
+
@check()
|
244
|
+
@sleep()
|
245
|
+
|
246
|
+
wake: () =>
|
247
|
+
if @field.val() is @mock_password
|
248
|
+
@field.removeClass "empty"
|
249
|
+
@field.val ""
|
250
|
+
|
251
|
+
sleep: () =>
|
252
|
+
v = @field.val()
|
253
|
+
if v is @mock_password or v is ""
|
254
|
+
@field.val @mock_password
|
255
|
+
@field.addClass("empty")
|
256
|
+
# if we're not required, then both-empty is also a submittable condition
|
257
|
+
if @confirmation.val() is "" and not @required
|
258
|
+
@submittable()
|
259
|
+
|
260
|
+
check: () =>
|
261
|
+
if @empty() and !@required
|
262
|
+
@field.removeClass("ok notok").addClass("empty")
|
263
|
+
@confirmation_holder.hide()
|
264
|
+
@submittable()
|
265
|
+
@notify ""
|
266
|
+
else if @valid()
|
267
|
+
@field.addClass("ok").removeClass "notok"
|
268
|
+
@confirmation_holder.show()
|
269
|
+
@notify "You must confirm your password before you can proceed."
|
270
|
+
if @matching()
|
271
|
+
@notify "Passwords match.", "successful"
|
272
|
+
@confirmation.addClass("ok").removeClass("notok")
|
273
|
+
@submittable()
|
274
|
+
else
|
275
|
+
if @confirmation_empty()
|
276
|
+
@notify "Please confirm your password."
|
277
|
+
else
|
278
|
+
@notify "The confirmation does not match your password.", "erratic"
|
279
|
+
@confirmation.addClass("notok").removeClass("ok")
|
280
|
+
@unsubmittable()
|
281
|
+
else
|
282
|
+
@confirmation_holder.hide()
|
283
|
+
@confirmation.val ""
|
284
|
+
@unsubmittable()
|
285
|
+
@field.addClass("notok").removeClass("ok")
|
286
|
+
@confirmation.addClass("notok").removeClass("ok")
|
287
|
+
if @empty()
|
288
|
+
@notify "Please enter a password."
|
289
|
+
else
|
290
|
+
@notify "Your password should have least six letters.", "erratic"
|
291
|
+
|
292
|
+
notify: (message, cssclass) =>
|
293
|
+
@_notice.removeClass('erratic successful').addClass(cssclass).text(message)
|
294
|
+
|
295
|
+
submittable: () =>
|
296
|
+
@submit.removeClass("unavailable")
|
297
|
+
@blocked = false
|
298
|
+
|
299
|
+
unsubmittable: () =>
|
300
|
+
@submit.addClass("unavailable")
|
301
|
+
@blocked = true
|
302
|
+
|
303
|
+
empty: () =>
|
304
|
+
!@field.val() || @field.val().length == 0
|
305
|
+
|
306
|
+
confirmation_empty: () =>
|
307
|
+
!@confirmation.val() || @confirmation.val().length == 0
|
308
|
+
|
309
|
+
valid: () =>
|
310
|
+
v = @field.val()
|
311
|
+
v.length >= @options.length and (!@options.validator? or @options.validator.test(v))
|
312
|
+
|
313
|
+
matching: () =>
|
314
|
+
@confirmation.val() is @field.val()
|
315
|
+
|
316
|
+
stumbit: (e) =>
|
317
|
+
if @blocked
|
318
|
+
e.preventDefault()
|
319
|
+
else
|
320
|
+
@field.val("") if @field.val() is @mock_password
|
321
|
+
|
322
|
+
|
323
|
+
|
324
|
+
# A captive form submits via an ajax request and pushes its results into the present page in the place
|
325
|
+
# designated by its 'replacing' attribute.
|
326
|
+
#
|
327
|
+
# If options['fast'] is true, the form will submit on every change to a text, radio or checkbox input.
|
328
|
+
#
|
329
|
+
#todo: This is very old now. Tidy it up with a more standard action structure, and fewer options.
|
330
|
+
|
331
|
+
$.fn.captive = (options) ->
|
332
|
+
@each ->
|
333
|
+
new CaptiveForm @, options
|
334
|
+
@
|
335
|
+
|
336
|
+
class CaptiveForm
|
337
|
+
constructor: (element, opts) ->
|
338
|
+
@_form = $(element)
|
339
|
+
@_options = $.extend {
|
340
|
+
fast: false
|
341
|
+
auto: false
|
342
|
+
historical: false
|
343
|
+
}, opts
|
344
|
+
@_selector = @_form.attr('data-target') || @_options.into
|
345
|
+
@_container = $(@_selector)
|
346
|
+
@_original_content = @_container.html()
|
347
|
+
@_prompt = @_form.find("input[type=\"text\"]")
|
348
|
+
@_request = null
|
349
|
+
@_form.remote
|
350
|
+
on_submit: @prepare
|
351
|
+
on_cancel: @cancel
|
352
|
+
on_success: @capture
|
353
|
+
if @_options.fast
|
354
|
+
@_form.find("input[type=\"text\"]").keyup @keyed
|
355
|
+
@_form.find("input[type=\"text\"]").change @submit
|
356
|
+
@_form.find("input[type=\"radio\"]").click @clicked
|
357
|
+
@_form.find("input[type=\"checkbox\"]").click @clicked
|
358
|
+
@submit() if @_options.auto
|
359
|
+
|
360
|
+
keyed: (e) =>
|
361
|
+
k = e.which
|
362
|
+
if (k >= 32 and k <= 165) or k == 8
|
363
|
+
if @_prompt.val() is "" and not @_options.auto
|
364
|
+
@revert()
|
365
|
+
else
|
366
|
+
@submit()
|
367
|
+
|
368
|
+
clicked: (e) =>
|
369
|
+
@submit()
|
370
|
+
|
371
|
+
submit: (e) =>
|
372
|
+
@_form.submit()
|
373
|
+
|
374
|
+
prepare: (xhr, settings) =>
|
375
|
+
@_container.fadeTo "fast", 0.2
|
376
|
+
@_request.abort() if @_request
|
377
|
+
@_request = xhr
|
378
|
+
|
379
|
+
capture: (data, status, xhr) =>
|
380
|
+
@display(data)
|
381
|
+
@_request = null
|
382
|
+
|
383
|
+
display: (results) =>
|
384
|
+
replacement = $(results)
|
385
|
+
@_container.empty().append(replacement).fadeTo("fast", 1)
|
386
|
+
replacement.activate()
|
387
|
+
replacement.find('a.cancel').click(@revert)
|
388
|
+
|
389
|
+
revert: (e) =>
|
390
|
+
e.preventDefault() if e
|
391
|
+
@display(@_original_content)
|
392
|
+
@_prompt.val("")
|
393
|
+
@saveState()
|
394
|
+
|
395
|
+
|
396
|
+
$.fn.filter_form = (options) ->
|
397
|
+
options = $.extend(
|
398
|
+
fast: true
|
399
|
+
auto: true
|
400
|
+
into: "#found"
|
401
|
+
, options)
|
402
|
+
@each ->
|
403
|
+
new CaptiveForm @, options
|
404
|
+
@
|
405
|
+
|
406
|
+
|
407
|
+
|
408
|
+
# The suggestions form is a fast captive with history support based on a single prompt field.
|
409
|
+
#
|
410
|
+
$.fn.suggestion_form = (options) ->
|
411
|
+
options = $.extend(
|
412
|
+
fast: true
|
413
|
+
auto: false
|
414
|
+
into: "#suggestion_box"
|
415
|
+
, options)
|
416
|
+
@each ->
|
417
|
+
new SuggestionForm @, options
|
418
|
+
@
|
419
|
+
|
420
|
+
class SuggestionForm extends CaptiveForm
|
421
|
+
constructor: (element, opts) ->
|
422
|
+
super
|
423
|
+
@_prompt = @_form.find("input[type=\"text\"]")
|
424
|
+
@_param = @_prompt.attr('name')
|
425
|
+
@_original_term = decodeURIComponent($.urlParam(@_param))
|
426
|
+
if @_original_term and @_original_term isnt "false" and @_original_term isnt ""
|
427
|
+
@_prompt.val(@_original_term)
|
428
|
+
@submit()
|
429
|
+
if Modernizr.history
|
430
|
+
$(window).bind 'popstate', @restoreState
|
431
|
+
|
432
|
+
capture: (data, status, xhr) =>
|
433
|
+
@saveState(data) if Modernizr.history
|
434
|
+
super
|
435
|
+
|
436
|
+
saveState: (results) =>
|
437
|
+
results ?= @_original_content
|
438
|
+
term = @_prompt.val()
|
439
|
+
if term
|
440
|
+
url = window.location.pathname + "?" + encodeURIComponent(@_param) + "=" + encodeURIComponent(term)
|
441
|
+
else
|
442
|
+
url = window.location.pathname
|
443
|
+
state =
|
444
|
+
html: results
|
445
|
+
term: term
|
446
|
+
history.pushState state, "Search results", url
|
447
|
+
|
448
|
+
restoreState: (e) =>
|
449
|
+
event = e.originalEvent
|
450
|
+
if event.state? && event.state.html?
|
451
|
+
@display event.state.html
|
452
|
+
@_prompt.val(event.state.term)
|
453
|
+
|
454
|
+
|
455
|
+
# The preferences form is a simple captive that just displays a confirmation message, with or without some control
|
456
|
+
# links (eg to copy or delete dropboxed folders). It may also include a number of preference blocks, which take care
|
457
|
+
# of showing and hiding subsidiary options.
|
458
|
+
|
459
|
+
$.fn.preferences_form = (options) ->
|
460
|
+
options = $.extend(
|
461
|
+
fast: true
|
462
|
+
clearing: null
|
463
|
+
replacing: ".confirmation"
|
464
|
+
, options)
|
465
|
+
@each ->
|
466
|
+
$(@).find('span.preference').preferences_block()
|
467
|
+
new CaptiveForm @, options
|
468
|
+
|
469
|
+
# The preferences block is a very minimal subcontent toggle. If the first contained radio or checkbox element
|
470
|
+
# is checked, anything `.subpreference` is revealed. If its state changes, we show or hide.
|
471
|
+
#
|
472
|
+
# Note that to make this work with radio buttons we have applied a small hack to make sure they fire a change event
|
473
|
+
# on deselection. See $.trigger_change_on_deselect in utilities.js.
|
474
|
+
|
475
|
+
$.fn.preferences_block = ->
|
476
|
+
@each ->
|
477
|
+
new PreferenceBlock(@)
|
478
|
+
|
479
|
+
class PreferenceBlock
|
480
|
+
constructor: (element) ->
|
481
|
+
@_container = $(element)
|
482
|
+
@_input = @_container.find("> input")
|
483
|
+
@_subprefs = @_container.find('span.subpreference')
|
484
|
+
if @_subprefs.length
|
485
|
+
@_input.trigger_change_on_deselect()
|
486
|
+
@_input.change @set
|
487
|
+
@set()
|
488
|
+
|
489
|
+
set: (e) =>
|
490
|
+
# let the event through...
|
491
|
+
if @_input.is(":checked") then @show() else @hide()
|
492
|
+
show: () =>
|
493
|
+
@_subprefs.slideDown()
|
494
|
+
hide: () =>
|
495
|
+
@_subprefs.slideUp()
|
496
|
+
|
497
|
+
|
498
|
+
# HTML-editing support is provided by WysiHTML, which is an ugly but effective iframe-based solution.
|
499
|
+
# Any textarea with the 'data-editable' attribute will be handed over to WysiHTML for processing.
|
500
|
+
#
|
501
|
+
#todo: make this true
|
502
|
+
#
|
503
|
+
$.fn.html_editable = ()->
|
504
|
+
@each ->
|
505
|
+
new Editor(@)
|
506
|
+
|
507
|
+
class Editor
|
508
|
+
constructor: (element) ->
|
509
|
+
@_container = $(element)
|
510
|
+
@_textarea = @_container.find('textarea')
|
511
|
+
@_toolbar = @_container.find('.toolbar')
|
512
|
+
@_toolbar.attr('id', $.makeGuid()) unless @_toolbar.attr('id')?
|
513
|
+
@_textarea.attr('id', $.makeGuid()) unless @_textarea.attr('id')?
|
514
|
+
stylesheets = $("link").map ->
|
515
|
+
$(@).attr('href')
|
516
|
+
@_editor = new wysihtml5.Editor @_textarea.get(0),
|
517
|
+
stylesheets: stylesheets,
|
518
|
+
toolbar: @_toolbar.get(0),
|
519
|
+
parserRules: wysihtml5ParserRules
|
520
|
+
useLineBreaks: false
|
521
|
+
@_toolbar.show()
|
522
|
+
# @_editor.on "load", () =>
|
523
|
+
# @_iframe = @_editor.composer.iframe
|
524
|
+
# $(@_editor.composer.doc).find('html').css
|
525
|
+
# "height": 0
|
526
|
+
# @resizeIframe()
|
527
|
+
# @_textarea = @_editor.composer.element
|
528
|
+
# @_textarea.addEventListener("keyup", @resizeIframe, false)
|
529
|
+
# @_textarea.addEventListener("blur", @resizeIframe, false)
|
530
|
+
# @_textarea.addEventListener("focus", @resizeIframe, false)
|
531
|
+
|
532
|
+
resizeIframe: () =>
|
533
|
+
if $(@_iframe).height() != $(@_editor.composer.doc).height()
|
534
|
+
$(@_iframe).height(@_editor.composer.element.offsetHeight)
|
535
|
+
|
536
|
+
showToolbar: () =>
|
537
|
+
@_hovered = true
|
538
|
+
@_toolbar.fadeTo(200, 1)
|
539
|
+
|
540
|
+
hideToolbar: () =>
|
541
|
+
@_hovered = false
|
542
|
+
@_toolbar.fadeTo(1000, 0.2)
|
543
|
+
|
544
|
+
|
545
|
+
|
546
|
+
|
547
|
+
|
548
|
+
|
549
|
+
## Display Widgets
|
550
|
+
#
|
551
|
+
# These stand alone and usually encapsulate some interaction with the user.
|
552
|
+
|
553
|
+
# The *folder* action is just a display convention that shows and hides the contents of a folder
|
554
|
+
# when its link is clicked. It could perhaps become a subclass of the generic toggle mechanism and benefit from its persistence.
|
555
|
+
|
556
|
+
$.fn.folder = ->
|
557
|
+
@each ->
|
558
|
+
new Folder(@)
|
559
|
+
|
560
|
+
class Folder
|
561
|
+
constructor: (element) ->
|
562
|
+
@_container = $(element)
|
563
|
+
@_list = @_container.children('ul.filing')
|
564
|
+
if @_list[0]
|
565
|
+
@_container.children('a.folder').click @toggle
|
566
|
+
@set()
|
567
|
+
|
568
|
+
set: (e) =>
|
569
|
+
e.preventDefault() if e
|
570
|
+
if @_container.hasClass('open') then @show() else @hide()
|
571
|
+
|
572
|
+
toggle: (e) =>
|
573
|
+
if e
|
574
|
+
e.preventDefault()
|
575
|
+
e.stopPropagation()
|
576
|
+
if @_container.hasClass('open') then @hide() else @show()
|
577
|
+
|
578
|
+
show: (e) =>
|
579
|
+
e.preventDefault() if e
|
580
|
+
@_container.addClass('open')
|
581
|
+
@_list.stop().slideDown("fast")
|
582
|
+
|
583
|
+
hide: (e) =>
|
584
|
+
e.preventDefault() if e
|
585
|
+
@_list.stop().slideUp "normal", () =>
|
586
|
+
@_container.removeClass('open')
|
587
|
+
|
588
|
+
|
589
|
+
# The Calendar widget is a display of one calendar month in the usual tabular format.
|
590
|
+
# Here we wrap it in a scrolling div to support movement from one month to another and
|
591
|
+
# hook it up to the suggestion box, if that's on the page, so that clicking a day or month
|
592
|
+
# name will populate the box (and so trigger a search for events in that period).
|
593
|
+
#
|
594
|
+
# For now we're saving some hassle by assuming that there is only one calendar and saving it
|
595
|
+
# in $.calendar for manipulating links to refer to.
|
596
|
+
|
597
|
+
$.fn.calendar = ->
|
598
|
+
@each ->
|
599
|
+
new Calendar(@)
|
600
|
+
@
|
601
|
+
|
602
|
+
# The calendar changer action usually belongs only to the next and previous buttons that sit in the
|
603
|
+
# calendar table, but you can also put links on the page to specific months.
|
604
|
+
#
|
605
|
+
$.fn.calendar_changer = ->
|
606
|
+
@click (e) ->
|
607
|
+
e.preventDefault() if e
|
608
|
+
link = $(@)
|
609
|
+
year = parseInt(link.attr('data-year'), 10)
|
610
|
+
month = parseInt(link.attr('data-month'), 10)
|
611
|
+
link.addClass('waiting')
|
612
|
+
$.calendar?.show(year, month)
|
613
|
+
@
|
614
|
+
|
615
|
+
# calendar_search links push their text into the suggestion box by way of the calendar.searchFor function.
|
616
|
+
#
|
617
|
+
$.fn.calendar_search = ->
|
618
|
+
@click (e) ->
|
619
|
+
e.preventDefault() if e
|
620
|
+
$.calendar?.searchFor($(@).text())
|
621
|
+
|
622
|
+
|
623
|
+
|
624
|
+
|
625
|
+
class ScoreShower
|
626
|
+
constructor: (element) ->
|
627
|
+
@_container = $(element)
|
628
|
+
@_rating = parseFloat(@_container.text(), 10)
|
629
|
+
@_rating ||= 0
|
630
|
+
@_bar = $('<div class="starbar" />').appendTo(@_container)
|
631
|
+
@_mask = $('<div class="starmask" />').appendTo(@_container)
|
632
|
+
@_bar.css
|
633
|
+
width: @_rating/5 * 80
|
634
|
+
|
635
|
+
$.fn.star_rating = () ->
|
636
|
+
@each ->
|
637
|
+
new ScoreShower @
|
638
|
+
|
639
|
+
|
640
|
+
# The page turner is a pagination link that retrieves a page of results remotely
|
641
|
+
# then slides it into view from either the right or left depending on its relation
|
642
|
+
# to the current page.
|
643
|
+
|
644
|
+
$.fn.sliding_link = () ->
|
645
|
+
@each ->
|
646
|
+
new Slider(@)
|
647
|
+
|
648
|
+
class Slider
|
649
|
+
constructor: (element) ->
|
650
|
+
@_link = $(element)
|
651
|
+
@_selector = @_link.attr('data-affected') || @defaultSelector()
|
652
|
+
@_direction = @getDirection()
|
653
|
+
@_page = @getPage()
|
654
|
+
|
655
|
+
# build viewport and sliding frame around the the content-holding @_page
|
656
|
+
@_frame = @_page.parents('.scroller').first()
|
657
|
+
unless @_frame.length
|
658
|
+
@_page.wrap($('<div class="scroller" />'))
|
659
|
+
@_frame = @_page.parent()
|
660
|
+
|
661
|
+
@_viewport = @_frame.parents('.scrolled').first()
|
662
|
+
unless @_viewport.length
|
663
|
+
@_frame.wrap($('<div class="scrolled" />'))
|
664
|
+
@_viewport = @_frame.parents('.scrolled')
|
665
|
+
|
666
|
+
@_width = @_page.width()
|
667
|
+
@_link.remote
|
668
|
+
on_success: @receive
|
669
|
+
|
670
|
+
getPage: =>
|
671
|
+
@_link.parents(@_selector).first()
|
672
|
+
|
673
|
+
getDirection: =>
|
674
|
+
@_link.attr "data-direction"
|
675
|
+
|
676
|
+
defaultSelector: () =>
|
677
|
+
'.scrap'
|
678
|
+
|
679
|
+
receive: (r) =>
|
680
|
+
response = $(r)
|
681
|
+
@sweep response
|
682
|
+
response.activate()
|
683
|
+
|
684
|
+
sweep: (r) =>
|
685
|
+
@_old_page = @_page
|
686
|
+
@_viewport.css("overflow", "hidden")
|
687
|
+
if @_direction == 'right'
|
688
|
+
@_frame.append(r)
|
689
|
+
@_viewport.animate {scrollLeft: @_width}, 'slow', 'glide', @cleanup
|
690
|
+
else
|
691
|
+
@_frame.prepend(r)
|
692
|
+
@_viewport.scrollLeft(@_width).animate {scrollLeft: 0}, 'slow', 'glide', @cleanup
|
693
|
+
|
694
|
+
cleanup: () =>
|
695
|
+
@_viewport.scrollLeft(0)
|
696
|
+
@_old_page.remove()
|
697
|
+
|
698
|
+
|
699
|
+
|
700
|
+
$.fn.page_turner = () ->
|
701
|
+
@each ->
|
702
|
+
new Pager(@)
|
703
|
+
|
704
|
+
class Pager extends Slider
|
705
|
+
constructor: (element) ->
|
706
|
+
super
|
707
|
+
@_page_number = parseInt(@_link.parent().siblings('.current').text())
|
708
|
+
|
709
|
+
defaultSelector: () =>
|
710
|
+
'.paginated'
|
711
|
+
|
712
|
+
getDirection: =>
|
713
|
+
if @_link.attr "rel"
|
714
|
+
@_direction = if @_link.attr("rel") == "next" then "right" else "left"
|
715
|
+
else
|
716
|
+
@_direction = if parseInt(@_link.text()) > @_page_number then "right" else "left"
|
717
|
+
|
718
|
+
|
719
|
+
|
720
|
+
|
721
|
+
|
722
|
+
|
723
|
+
#todo: make this a case of the page turner?
|
724
|
+
|
725
|
+
class Calendar
|
726
|
+
constructor: (element, options) ->
|
727
|
+
@_container = $(element)
|
728
|
+
@_scroller = @_container.find('.scroller')
|
729
|
+
@_table = null
|
730
|
+
@_cache = {}
|
731
|
+
@_month = null
|
732
|
+
@_year = null
|
733
|
+
@_request = null
|
734
|
+
@_incoming = {}
|
735
|
+
@_width = @_container.width()
|
736
|
+
$.calendar = @
|
737
|
+
@_container.bind "refresh", @refresh_in_place
|
738
|
+
@init()
|
739
|
+
|
740
|
+
init: () =>
|
741
|
+
@_table = @_container.find('table')
|
742
|
+
@_month = parseInt(@_table.attr('data-month'), 10)
|
743
|
+
@_year = parseInt(@_table.attr('data-year'), 10)
|
744
|
+
@cache(@_year, @_month, @_table)
|
745
|
+
@_table.find('a.next, a.previous').calendar_changer()
|
746
|
+
@_table.find('a.day').click @searchForDay
|
747
|
+
@_table.find('a.month').click @searchForMonth
|
748
|
+
|
749
|
+
cache: (year, month, table) =>
|
750
|
+
@_cache[year] ?= {}
|
751
|
+
@_cache[year][month] ?= table
|
752
|
+
|
753
|
+
cached: (year, month) =>
|
754
|
+
@_cache[year] ?= {}
|
755
|
+
@_cache[year][month]
|
756
|
+
|
757
|
+
# The calendar is intrinsically refreshable: it responds to a 'refresh' event by calling this method
|
758
|
+
# to reload the currently displayed month/year.
|
759
|
+
#
|
760
|
+
refresh_in_place: () =>
|
761
|
+
@_request = $.ajax
|
762
|
+
type: "GET"
|
763
|
+
dataType: "html"
|
764
|
+
url: "/events/calendar.js?month=#{encodeURIComponent(@_month)}&year=#{encodeURIComponent(@_year)}"
|
765
|
+
success: @update_quietly
|
766
|
+
|
767
|
+
update_quietly: (response) =>
|
768
|
+
@_container.find('a').removeClass('waiting')
|
769
|
+
@_scroller.find('table').remove()
|
770
|
+
@_scroller.append(response)
|
771
|
+
@init()
|
772
|
+
|
773
|
+
show: (year, month) =>
|
774
|
+
if cached = @cached(year, month)
|
775
|
+
@update(cached, year, month)
|
776
|
+
else
|
777
|
+
@_request = $.ajax
|
778
|
+
type: "GET"
|
779
|
+
dataType: "html"
|
780
|
+
url: "/events/calendar.js?month=#{encodeURIComponent(month)}&year=#{encodeURIComponent(year)}"
|
781
|
+
success: (response) =>
|
782
|
+
@update(response, year, month)
|
783
|
+
|
784
|
+
update: (response, year, month) =>
|
785
|
+
@_container.find('a').removeClass('waiting')
|
786
|
+
direction = "left" if ((year * 12) + month) > ((@_year * 12) + @_month)
|
787
|
+
@sweep response, direction
|
788
|
+
|
789
|
+
sweep: (table, direction) =>
|
790
|
+
old = @_scroller.find('table')
|
791
|
+
if direction == 'left'
|
792
|
+
@_scroller.append(table)
|
793
|
+
@_container.animate {scrollLeft: @_width}, 'fast', () =>
|
794
|
+
old.remove()
|
795
|
+
@_container.scrollLeft(0)
|
796
|
+
@init()
|
797
|
+
else
|
798
|
+
@_scroller.prepend(table)
|
799
|
+
@_container.scrollLeft(@_width).animate {scrollLeft: 0}, 'fast', () =>
|
800
|
+
old.remove()
|
801
|
+
@init()
|
802
|
+
|
803
|
+
# This use of moment depends on the loading of Kalendae, which we use for the date-picker widget above.
|
804
|
+
#
|
805
|
+
monthName: () =>
|
806
|
+
Kalendae.moment.months[@_month-1]
|
807
|
+
|
808
|
+
searchForm: =>
|
809
|
+
@_form ?= $('#suggestions')
|
810
|
+
|
811
|
+
searchForDay: (e) =>
|
812
|
+
e.preventDefault() if e
|
813
|
+
day = $(e.target).text()
|
814
|
+
@search("#{day} #{@monthName()} #{@_year}")
|
815
|
+
|
816
|
+
searchForMonth: (e) =>
|
817
|
+
e.preventDefault() if e
|
818
|
+
@search("#{@monthName()} #{@_year}")
|
819
|
+
|
820
|
+
search: (term) =>
|
821
|
+
@searchForm()?.find('input#term').val(term).change()
|
822
|
+
@searchForm().trigger('show')
|
823
|
+
# should trigger a change event, then we hit the cache if possible
|
824
|
+
# @searchForm()?.submit()
|
825
|
+
|
826
|
+
|
827
|
+
|
828
|
+
# The Suggester hooks us up to the machinery that drives the main suggestions box, usually with some
|
829
|
+
# restrictions to eg. a single type of object. It lets us provide typeahead boxes that work both for existing
|
830
|
+
# and new objects.
|
831
|
+
#
|
832
|
+
# The suggestion box itself is just `suggestible`. Other inputs are generally more restricted.
|
833
|
+
#
|
834
|
+
$.fn.suggestible = (options) ->
|
835
|
+
options = $.extend(
|
836
|
+
submit_form: true
|
837
|
+
threshold: 3
|
838
|
+
, options)
|
839
|
+
@each ->
|
840
|
+
new Suggester(@, options)
|
841
|
+
@
|
842
|
+
|
843
|
+
$.fn.venue_picker = (options) ->
|
844
|
+
options = $.extend(
|
845
|
+
submit_form: false
|
846
|
+
threshold: 1
|
847
|
+
type: 'venue'
|
848
|
+
, options)
|
849
|
+
@each ->
|
850
|
+
new Suggester(@, options)
|
851
|
+
@
|
852
|
+
|
853
|
+
$.fn.person_selector = (options) ->
|
854
|
+
options = $.extend(
|
855
|
+
submit_form: false
|
856
|
+
threshold: 1
|
857
|
+
type: 'person'
|
858
|
+
, options)
|
859
|
+
@each ->
|
860
|
+
target = $(@).siblings('.person_picker_target')
|
861
|
+
$(@).bind "keyup", () =>
|
862
|
+
target.val null
|
863
|
+
suggester = new Suggester(@, options)
|
864
|
+
suggester.options.afterSelect = (value, id) ->
|
865
|
+
target.val id
|
866
|
+
@
|
867
|
+
|
868
|
+
$.fn.person_picker = (options) ->
|
869
|
+
options = $.extend(
|
870
|
+
submit_form: false
|
871
|
+
threshold: 1
|
872
|
+
type: 'person'
|
873
|
+
, options)
|
874
|
+
@each ->
|
875
|
+
target = $(@).siblings('.person_picker_target')
|
876
|
+
$(@).bind "keyup", () =>
|
877
|
+
target.val null
|
878
|
+
suggester = new Suggester(@, options)
|
879
|
+
suggester.options.afterSelect = () ->
|
880
|
+
id = JSON.parse(suggester.request.responseText)[0].id
|
881
|
+
target.val id
|
882
|
+
suggester.form.submit()
|
883
|
+
@
|
884
|
+
|
885
|
+
$.fn.group_picker = (options) ->
|
886
|
+
options = $.extend(
|
887
|
+
submit_form: true
|
888
|
+
threshold: 1
|
889
|
+
type: 'group'
|
890
|
+
, options)
|
891
|
+
@each ->
|
892
|
+
target = $(@).siblings('.group_picker_target')
|
893
|
+
$(@).bind "keyup", () =>
|
894
|
+
target.val null
|
895
|
+
suggester = new Suggester(@, options)
|
896
|
+
suggester.options.afterSelect = () ->
|
897
|
+
id = JSON.parse(suggester.request.responseText)[0].id
|
898
|
+
target.val id
|
899
|
+
suggester.form.submit()
|
900
|
+
@
|
901
|
+
|
902
|
+
$.fn.application_suggester = (options) ->
|
903
|
+
options = $.extend(
|
904
|
+
submit_form: false
|
905
|
+
threshold: 1
|
906
|
+
limit: 5
|
907
|
+
type: 'application'
|
908
|
+
, options)
|
909
|
+
@each ->
|
910
|
+
new Suggester(@, options)
|
911
|
+
@
|
912
|
+
|
913
|
+
class Suggester
|
914
|
+
constructor: (element, options) ->
|
915
|
+
@prompt = $(element)
|
916
|
+
@type = @prompt.attr('data-type')
|
917
|
+
@form = @prompt.parents("form")
|
918
|
+
@options = $.extend(
|
919
|
+
fill_field: true
|
920
|
+
empty_field: false
|
921
|
+
submit_form: false
|
922
|
+
preload: false
|
923
|
+
threshold: 2
|
924
|
+
limit: 10
|
925
|
+
afterSuggest: null
|
926
|
+
afterSelect: null
|
927
|
+
, options)
|
928
|
+
if options.type
|
929
|
+
@url = "/suggestions/#{options.type}.json"
|
930
|
+
else
|
931
|
+
@url = "/suggestions.json"
|
932
|
+
if options.preload
|
933
|
+
@url += "?empty=all"
|
934
|
+
@dropdown = new Dropdown @prompt,
|
935
|
+
on_select: @select
|
936
|
+
on_keyup: @get
|
937
|
+
@button = @form.find("a.search")
|
938
|
+
@previously = null
|
939
|
+
@request = null
|
940
|
+
@visible = false
|
941
|
+
@suggestions = []
|
942
|
+
@suggestion = null
|
943
|
+
@cache = {}
|
944
|
+
@blanks = []
|
945
|
+
@prompt.bind "blur", @hide
|
946
|
+
@prompt.bind "paste", @get
|
947
|
+
@form.submit @hide
|
948
|
+
@get(null, true) if @options.preload
|
949
|
+
@
|
950
|
+
|
951
|
+
reset: () =>
|
952
|
+
@dropdown.reset().hide()
|
953
|
+
|
954
|
+
get: (e, force) =>
|
955
|
+
@wait()
|
956
|
+
query = @prompt.val()
|
957
|
+
if force or query.length >= @options.threshold and query isnt @previously
|
958
|
+
if @cache[query]
|
959
|
+
@suggest @cache[query]
|
960
|
+
else if @previously_blank(query)
|
961
|
+
@suggest []
|
962
|
+
else
|
963
|
+
@request.abort() if @request
|
964
|
+
@request = $.getJSON(@url, "term=" + encodeURIComponent(query) + "&limit=" + @options.limit, (suggestions) =>
|
965
|
+
@cache[query] = suggestions
|
966
|
+
@blanks.push query if suggestions.length is 0
|
967
|
+
@suggest suggestions
|
968
|
+
)
|
969
|
+
else
|
970
|
+
@hide()
|
971
|
+
|
972
|
+
previously_blank: (query) =>
|
973
|
+
if @blanks.length > 0
|
974
|
+
blank_re = new RegExp("(" + @blanks.join("|") + ")")
|
975
|
+
return blank_re.test(query)
|
976
|
+
false
|
977
|
+
|
978
|
+
suggest: (suggestions) =>
|
979
|
+
@unwait()
|
980
|
+
@dropdown.show(suggestions)
|
981
|
+
@options.afterSuggest.call(@, suggestions) if @options.afterSuggest
|
982
|
+
|
983
|
+
select: (value) =>
|
984
|
+
if @options.fill_field?
|
985
|
+
@prompt.val(value)
|
986
|
+
@prompt.trigger('suggester.change')
|
987
|
+
else if @options.empty_field?
|
988
|
+
@prompt.val ""
|
989
|
+
@options.afterSelect.call(@, value) if @options.afterSelect
|
990
|
+
|
991
|
+
show: () =>
|
992
|
+
@dropdown.show()
|
993
|
+
|
994
|
+
hide: () =>
|
995
|
+
@dropdown.hide()
|
996
|
+
|
997
|
+
wait: () =>
|
998
|
+
@button.addClass "waiting"
|
999
|
+
@prompt.addClass "waiting"
|
1000
|
+
|
1001
|
+
unwait: () =>
|
1002
|
+
@button.removeClass "waiting"
|
1003
|
+
@prompt.removeClass "waiting"
|
1004
|
+
|
1005
|
+
|
1006
|
+
|
1007
|
+
|
1008
|
+
|
1009
|
+
|
1010
|
+
|
1011
|
+
$.fn.video_picker = (options) ->
|
1012
|
+
options = $.extend(
|
1013
|
+
submit_form: true
|
1014
|
+
threshold: 3
|
1015
|
+
type: "video"
|
1016
|
+
url: "/videos.json"
|
1017
|
+
, options)
|
1018
|
+
@each ->
|
1019
|
+
new YoutubeSuggester(@, options)
|
1020
|
+
@
|
1021
|
+
|
1022
|
+
|
1023
|
+
class YoutubeSuggester extends Suggester
|
1024
|
+
constructor: () ->
|
1025
|
+
super
|
1026
|
+
@target = $('textarea#scrap_body')
|
1027
|
+
@thumb = $('.scrapvideo .thumb')
|
1028
|
+
|
1029
|
+
suggest: (suggestions) =>
|
1030
|
+
@button.removeClass "waiting"
|
1031
|
+
@prompt.removeClass "waiting"
|
1032
|
+
if suggestions.length > 0
|
1033
|
+
$.each suggestions, (i, suggestion) =>
|
1034
|
+
link = $("<a href=\"/videos/#{suggestion.unique_id}.js\"><img src='#{suggestion.thumbnails[2].url}' /><span class=\"title\">#{suggestion.title.truncate(36)}</span><br /><span class=\"description\">#{suggestion.description.truncate(48)}</span></a>")
|
1035
|
+
value = suggestion.value || suggestion.prompt
|
1036
|
+
link.remote
|
1037
|
+
on_success: (response) =>
|
1038
|
+
@select(response, suggestion)
|
1039
|
+
link.hover () =>
|
1040
|
+
@hover(link)
|
1041
|
+
$("<li></li>").addClass("video").append(link).appendTo(@container)
|
1042
|
+
@suggestions = @container.find("a")
|
1043
|
+
@show()
|
1044
|
+
else
|
1045
|
+
@hide()
|
1046
|
+
@options.afterSuggest.call @, suggestions if @options.afterSuggest
|
1047
|
+
|
1048
|
+
select: (response, selection) =>
|
1049
|
+
@hide()
|
1050
|
+
@prompt.trigger 'suggester.change'
|
1051
|
+
@target.val selection.unique_id
|
1052
|
+
@thumb.empty().html(response)
|
1053
|
+
@options.afterSelect?.call(@, value)
|
1054
|
+
|
1055
|
+
|
1056
|
+
|
1057
|
+
# I've just lifted this out of the suggester so that it can be used in other pickers.
|
1058
|
+
|
1059
|
+
$.fn.dropdown = (options) ->
|
1060
|
+
@each ->
|
1061
|
+
new Dropdown(@, options)
|
1062
|
+
|
1063
|
+
class Dropdown
|
1064
|
+
constructor: (element, opts) ->
|
1065
|
+
@hook = $(element)
|
1066
|
+
@drop = $('<ul class="dropdown" />').insertAfter(@hook).hide()
|
1067
|
+
@options = $.extend {}, opts
|
1068
|
+
@hook.bind "keydown", @keydown
|
1069
|
+
@hook.bind "keyup", @keyup
|
1070
|
+
@
|
1071
|
+
|
1072
|
+
place: () =>
|
1073
|
+
@drop.css
|
1074
|
+
top: @hook.position().top + @hook.outerHeight() - 2
|
1075
|
+
left: @hook.position().left
|
1076
|
+
width: @hook.outerWidth() - 2
|
1077
|
+
|
1078
|
+
populate: (items) =>
|
1079
|
+
@reset()
|
1080
|
+
if items.length > 0
|
1081
|
+
$.each items, (i, item) =>
|
1082
|
+
id = item.id || item.value
|
1083
|
+
value = item.value || item.prompt || item.id
|
1084
|
+
link = $("<a href=\"#\">#{value}</a>")
|
1085
|
+
link.hover () =>
|
1086
|
+
@hover(link)
|
1087
|
+
link.click (e) =>
|
1088
|
+
e.preventDefault()
|
1089
|
+
e.stopPropagation()
|
1090
|
+
@item = i
|
1091
|
+
@select(value)
|
1092
|
+
$("<li></li>").addClass(item.type).append(link).appendTo(@drop)
|
1093
|
+
@items = @drop.find("a")
|
1094
|
+
|
1095
|
+
reset: () =>
|
1096
|
+
@drop.empty()
|
1097
|
+
|
1098
|
+
select_highlit: (e) =>
|
1099
|
+
if highlit = @items[@item]
|
1100
|
+
e.preventDefault()
|
1101
|
+
e.stopPropagation()
|
1102
|
+
@select $(highlit).text()
|
1103
|
+
|
1104
|
+
select: (value) =>
|
1105
|
+
@hide()
|
1106
|
+
@options.on_select?(value)
|
1107
|
+
|
1108
|
+
cancel: (e) =>
|
1109
|
+
@hide()
|
1110
|
+
@options.on_cancel?()
|
1111
|
+
|
1112
|
+
show: (values) =>
|
1113
|
+
@place()
|
1114
|
+
@populate(values) if values
|
1115
|
+
unless @visible
|
1116
|
+
@drop.stop().fadeIn "fast"
|
1117
|
+
@visible = true
|
1118
|
+
|
1119
|
+
hide: () =>
|
1120
|
+
if @visible
|
1121
|
+
@drop.stop().fadeOut "fast"
|
1122
|
+
@visible = false
|
1123
|
+
|
1124
|
+
# the keyup and keydown handlers will first check for local significacne (ie it's a movement or action key
|
1125
|
+
# that we recognise). If there is none, they call the supplied callback, if any. Event cancellation is up to
|
1126
|
+
# the called function: we don't stop propagation here.
|
1127
|
+
#
|
1128
|
+
# On keydown we look for keys that have to be intercepted: enter, mostly. Perhaps also tab, but that's not
|
1129
|
+
# very reliable.
|
1130
|
+
#
|
1131
|
+
keydown: (e) =>
|
1132
|
+
kc = e.which
|
1133
|
+
if action = @actionKey(kc)
|
1134
|
+
action.call(@, e) if @items?.length
|
1135
|
+
return true
|
1136
|
+
else
|
1137
|
+
@options.on_keydown?(e)
|
1138
|
+
return true
|
1139
|
+
|
1140
|
+
actionKey: (kc) =>
|
1141
|
+
switch kc
|
1142
|
+
# we may also want to catch tab here
|
1143
|
+
when 27 # escape
|
1144
|
+
@hide
|
1145
|
+
when 13 # enter
|
1146
|
+
@select_highlit
|
1147
|
+
|
1148
|
+
# on keyup we look for movement keys. Everything else is let through.
|
1149
|
+
#
|
1150
|
+
keyup: (e, discard) =>
|
1151
|
+
kc = e.which
|
1152
|
+
if action = @movementKey(kc)
|
1153
|
+
@show() if @items?.length
|
1154
|
+
action.call(@, e) if @visible
|
1155
|
+
return true
|
1156
|
+
else
|
1157
|
+
@options.on_keyup?(e)
|
1158
|
+
return true
|
1159
|
+
|
1160
|
+
movementKey: (kc) =>
|
1161
|
+
switch kc
|
1162
|
+
when 33 # page up
|
1163
|
+
@first
|
1164
|
+
when 38 # up
|
1165
|
+
@previous
|
1166
|
+
when 40 # down
|
1167
|
+
@next
|
1168
|
+
when 34 # page down
|
1169
|
+
@last
|
1170
|
+
|
1171
|
+
match: (text) =>
|
1172
|
+
matching = @items.filter(":contains(#{text})")
|
1173
|
+
$.items = @items
|
1174
|
+
if item = matching.first()
|
1175
|
+
@hover(item)
|
1176
|
+
if holder = item.parents('li').first()
|
1177
|
+
top = holder.offset().top
|
1178
|
+
@drop.scrollTop(top)
|
1179
|
+
|
1180
|
+
next: (e) =>
|
1181
|
+
if !@item? or @item >= @items.length - 1
|
1182
|
+
@first()
|
1183
|
+
else
|
1184
|
+
@highlight(@item + 1)
|
1185
|
+
|
1186
|
+
previous: (e) =>
|
1187
|
+
if @item <= 0
|
1188
|
+
@last()
|
1189
|
+
else
|
1190
|
+
@highlight(@item - 1)
|
1191
|
+
|
1192
|
+
first: (e) =>
|
1193
|
+
@highlight(0)
|
1194
|
+
|
1195
|
+
last: (e) =>
|
1196
|
+
@highlight(@items.length - 1)
|
1197
|
+
|
1198
|
+
hover: (link) =>
|
1199
|
+
@highlight(@items.index(link))
|
1200
|
+
|
1201
|
+
highlight: (i) =>
|
1202
|
+
@unHighlight(@item) if @item isnt null
|
1203
|
+
$(@items.get(i)).addClass("hover")
|
1204
|
+
@item = i
|
1205
|
+
|
1206
|
+
unHighlight: (i) =>
|
1207
|
+
if item = @items?.get(i)
|
1208
|
+
$(item).removeClass("hover")
|
1209
|
+
|