From d1450f65f6f88117c16ac078968396e5d8807966 Mon Sep 17 00:00:00 2001 From: cyril Date: Wed, 23 Mar 2016 18:39:41 +0100 Subject: [PATCH 1/2] release 2.0 rc --- .dockerignore | 33 + .gitignore | 19 +- .ruby-gemset | 2 +- .ruby-version | 2 +- CONTRIBUTING.md | 132 + Dockerfile | 71 + Gemfile | 45 +- Gemfile.lock | 237 +- LICENSE => LICENSE.md | 95 +- Procfile | 2 +- README.md | 583 +- Vagrantfile | 73 + app/assets/images/about-fablab.jpg | Bin 20842 -> 0 bytes app/assets/images/fablab-logo.png | Bin 0 -> 4453 bytes app/assets/images/fablab-w.png | Bin 16211 -> 2980 bytes app/assets/images/fablab.jpg | Bin 14825 -> 8611 bytes app/assets/images/fablab.png | Bin 2641 -> 2828 bytes app/assets/images/la_casemate-logo.png | Bin 0 -> 20809 bytes app/assets/images/mastercard.png | Bin 0 -> 3810 bytes app/assets/images/powered_by_stripe.png | Bin 0 -> 5141 bytes app/assets/images/select2/select2-spinner.gif | Bin 1849 -> 0 bytes app/assets/images/select2/select2.png | Bin 613 -> 0 bytes app/assets/images/select2/select2x2.png | Bin 845 -> 0 bytes app/assets/images/visa.png | Bin 0 -> 3064 bytes app/assets/javascripts/app.js.erb | 101 +- app/assets/javascripts/application.js | 31 +- .../javascripts/controllers/about.coffee | 19 + .../controllers/admin/admins.coffee | 0 .../controllers/admin/authentications.coffee | 221 + .../controllers/admin/calendar.coffee.erb | 403 + .../controllers/admin/events.coffee | 121 +- .../controllers/admin/graphs.coffee | 648 + .../controllers/admin/groups.coffee.erb | 64 + .../controllers/admin/invoices.coffee.erb | 492 + .../controllers/admin/members.coffee | 153 - .../controllers/admin/members.coffee.erb | 439 + .../controllers/admin/plans.coffee.erb | 260 + .../controllers/admin/pricing.coffee.erb | 383 + .../controllers/admin/project_elements.coffee | 13 +- .../controllers/admin/settings.coffee | 196 + .../controllers/admin/statistics.coffee | 453 + .../controllers/admin/tags.coffee.erb | 65 + .../controllers/admin/trainings.coffee.erb | 230 + .../controllers/application.coffee.erb | 86 +- .../javascripts/controllers/dashboard.coffee | 42 +- .../javascripts/controllers/events.coffee.erb | 544 +- .../javascripts/controllers/home.coffee | 56 +- .../controllers/machines.coffee.erb | 850 +- .../controllers/main_nav.coffee.erb | 68 +- .../javascripts/controllers/members.coffee | 145 +- .../controllers/notifications.coffee | 4 +- .../javascripts/controllers/plans.coffee.erb | 232 + .../javascripts/controllers/profile.coffee | 152 + .../{projects.coffee => projects.coffee.erb} | 175 +- .../controllers/trainings.coffee.erb | 658 + .../directives/bs-jasny-fileinput.js | 2 +- .../directives/confirmation_needed.coffee | 20 + .../javascripts/directives/directives.coffee | 37 + .../javascripts/directives/stripe-angular.js | 24 + .../javascripts/directives/validators.coffee | 34 + app/assets/javascripts/filters/filters.coffee | 132 +- app/assets/javascripts/router.coffee.erb | 1018 +- app/assets/javascripts/services/_t.coffee | 6 + app/assets/javascripts/services/abuse.coffee | 8 + app/assets/javascripts/services/admin.coffee | 8 + .../javascripts/services/authProvider.coffee | 14 + .../javascripts/services/availability.coffee | 21 + app/assets/javascripts/services/credit.coffee | 8 + .../javascripts/services/customAsset.coffee | 6 + .../javascripts/services/dialogs.coffee.erb | 22 +- .../javascripts/services/elastic.js.erb | 3 + app/assets/javascripts/services/group.coffee | 4 +- .../javascripts/services/invoice.coffee | 8 + app/assets/javascripts/services/member.coffee | 5 + app/assets/javascripts/services/plan.coffee | 8 + app/assets/javascripts/services/price.coffee | 14 + .../javascripts/services/pricing.coffee | 8 + .../javascripts/services/project.coffee | 4 + .../javascripts/services/reservation.coffee | 8 + .../javascripts/services/setting.coffee | 10 + app/assets/javascripts/services/slot.coffee | 11 + .../javascripts/services/statistics.coffee | 5 + .../javascripts/services/subscription.coffee | 8 + app/assets/javascripts/services/tag.coffee | 8 + .../javascripts/services/training.coffee | 8 + .../services/trainings_pricing.coffee | 8 + .../javascripts/services/translations.coffee | 13 + app/assets/javascripts/services/user.coffee | 8 + app/assets/stylesheets/app.base.scss | 13 +- app/assets/stylesheets/app.buttons.scss | 20 +- app/assets/stylesheets/app.colors.scss | 9 +- app/assets/stylesheets/app.components.scss | 55 +- app/assets/stylesheets/app.layout.scss | 135 +- app/assets/stylesheets/app.nav.scss | 6 +- app/assets/stylesheets/app.plugins.scss | 137 +- app/assets/stylesheets/app.printer.scss | 2 +- app/assets/stylesheets/app.utilities.scss | 2 +- app/assets/stylesheets/application.scss | 15 +- .../stylesheets/bootstrap_and_overrides.scss | 16 +- app/assets/stylesheets/modules/invoice.scss | 181 + .../templates/admin/admins/new.html.erb | 34 + .../admin/authentications/_form.html.erb | 30 + .../admin/authentications/_oauth2.html.erb | 93 + .../authentications/_oauth2_mapping.html.erb | 76 + .../admin/authentications/edit.html.erb | 51 + .../admin/authentications/index.html.erb | 40 + .../admin/authentications/new.html.erb | 52 + .../admin/calendar/calendar.html.erb | 66 + .../admin/calendar/eventModal.html.erb | 65 + .../templates/admin/events/index.html.erb | 38 +- .../admin/events/reservations.html.erb | 52 + .../templates/admin/groups/index.html.erb | 37 + .../admin/invoices/avoirModal.html.erb | 60 + .../templates/admin/invoices/index.html.erb | 383 + .../templates/admin/members/_form.html.erb | 58 +- .../templates/admin/members/edit.html.erb | 195 +- .../templates/admin/members/index.html.erb | 173 +- .../templates/admin/members/new.html.erb | 8 +- .../templates/admin/plans/_form.html.erb | 158 + .../templates/admin/plans/edit.html.erb | 69 + app/assets/templates/admin/plans/new.html.erb | 33 + .../templates/admin/pricing/index.html.erb | 219 + .../admin/project_elements/index.html.erb | 38 +- .../templates/admin/settings/index.html | 463 + .../admin/statistics/graphs.html.erb | 147 + .../templates/admin/statistics/index.html.erb | 287 + .../admin/subscriptions/create_modal.html.erb | 20 + .../subscriptions/expired_at_modal.html.erb | 33 + .../templates/admin/tags/index.html.erb | 37 + .../templates/admin/trainings/index.html.erb | 124 + .../admin/trainings/modal_edit.html.erb | 14 + .../trainings/validTrainingModal.html.erb | 18 + .../templates/dashboard/events.html.erb | 47 + .../templates/dashboard/invoices.html.erb | 47 + app/assets/templates/dashboard/nav.html.erb | 13 +- .../templates/dashboard/profile.html.erb | 85 +- .../templates/dashboard/projects.html.erb | 19 +- .../templates/dashboard/trainings.html.erb | 60 + app/assets/templates/events/_form.html.erb | 90 +- app/assets/templates/events/edit.html.erb | 2 +- app/assets/templates/events/index.html.erb | 14 +- .../modify_event_reservation_modal.html.erb | 14 + app/assets/templates/events/new.html.erb | 2 +- app/assets/templates/events/show.html.erb | 110 +- app/assets/templates/home.html.erb | 99 +- app/assets/templates/machines/_form.html.erb | 60 +- app/assets/templates/machines/edit.html.erb | 4 +- app/assets/templates/machines/index.html.erb | 15 +- app/assets/templates/machines/new.html.erb | 8 +- .../machines/request_training_modal.html.erb | 18 + .../templates/machines/reserve.html.erb | 213 + app/assets/templates/machines/show.html.erb | 10 +- .../training_reservation_modal.html.erb | 10 + app/assets/templates/members/index.html.erb | 13 +- app/assets/templates/members/show.html.erb | 44 +- .../templates/notifications/index.html.erb | 26 +- app/assets/templates/plans/_plan.html.erb | 47 + app/assets/templates/plans/index.html.erb | 169 + .../templates/plans/payment_modal.html.erb | 12 + app/assets/templates/profile/_token.html.erb | 28 + .../templates/profile/complete.html.erb | 105 + app/assets/templates/projects/_form.html.erb | 132 +- app/assets/templates/projects/edit.html.erb | 12 +- app/assets/templates/projects/index.html.erb | 134 +- app/assets/templates/projects/new.html.erb | 12 +- app/assets/templates/projects/show.html.erb | 37 +- app/assets/templates/shared/_admin_form.html | 120 + .../templates/shared/_member_form.html.erb | 163 +- .../templates/shared/_member_select.html.erb | 13 +- .../templates/shared/_partner_new_modal.html | 35 + app/assets/templates/shared/about.html.erb | 23 +- .../templates/shared/confirm_modal.html.erb | 6 +- .../shared/confirm_modify_slot_modal.html.erb | 14 + .../templates/shared/deviseModal.html.erb | 40 +- app/assets/templates/shared/header.html.erb | 51 +- app/assets/templates/shared/leftnav.html.erb | 118 +- .../shared/passwordEditModal.html.erb | 33 +- .../shared/passwordNewModal.html.erb | 19 +- .../shared/signalAbuseModal.html.erb | 41 + .../templates/shared/signupModal.html.erb | 119 +- .../shared/valid_reservation_modal.html.erb | 14 + .../templates/stripe/payment_modal.html.erb | 58 + .../templates/trainings/reserve.html.erb | 197 + app/controllers/api/abuses_controller.rb | 22 + app/controllers/api/admins_controller.rb | 50 + .../api/auth_providers_controller.rb | 67 + .../api/availabilities_controller.rb | 161 + app/controllers/api/credits_controller.rb | 47 + .../api/custom_assets_controller.rb | 48 + app/controllers/api/events_controller.rb | 2 +- app/controllers/api/feeds_controller.rb | 3 +- app/controllers/api/groups_controller.rb | 35 + app/controllers/api/invoices_controller.rb | 35 + app/controllers/api/machines_controller.rb | 15 +- app/controllers/api/members_controller.rb | 104 +- .../api/notifications_controller.rb | 2 +- app/controllers/api/plans_controller.rb | 100 + app/controllers/api/prices_controller.rb | 67 + app/controllers/api/pricing_controller.rb | 25 + app/controllers/api/projects_controller.rb | 10 +- .../api/reservations_controller.rb | 64 + app/controllers/api/settings_controller.rb | 30 + app/controllers/api/slots_controller.rb | 36 + app/controllers/api/statistics_controller.rb | 19 + app/controllers/api/stylesheets_controller.rb | 11 + .../api/subscriptions_controller.rb | 64 + app/controllers/api/tags_controller.rb | 47 + app/controllers/api/trainings_controller.rb | 53 + .../api/trainings_pricings_controller.rb | 26 + .../api/translations_controller.rb | 18 + app/controllers/api/users_controller.rb | 35 + app/controllers/application_controller.rb | 9 + .../concerns/fablab_configuration.rb | 5 + app/controllers/registrations_controller.rb | 2 +- app/controllers/sessions_controller.rb | 9 + .../users/omniauth_callbacks_controller.rb | 83 + app/controllers/webhooks_controller.rb | 3 + app/exceptions/duplicate_index_error.rb | 3 + app/flow_workers/members_flow_worker.rb | 45 + app/helpers/application_helper.rb | 68 + app/helpers/upload_helper.rb | 2 +- app/mailers/notifications_mailer.rb | 29 + app/mailers/users_mailer.rb | 4 +- app/models/abuse.rb | 15 + app/models/auth_provider.rb | 82 + app/models/availability.rb | 72 + app/models/availability_tag.rb | 4 + app/models/avoir.rb | 54 + app/models/category.rb | 2 +- app/models/component.rb | 2 +- app/models/concerns/stat_concern.rb | 18 + .../concerns/stat_reservation_concern.rb | 9 + app/models/credit.rb | 7 + app/models/custom_asset.rb | 10 + app/models/custom_asset_file.rb | 3 + app/models/database_provider.rb | 15 + app/models/event.rb | 25 +- app/models/event_image.rb | 2 +- app/models/group.rb | 46 +- app/models/invoice.rb | 219 + app/models/invoice_item.rb | 6 + app/models/machine.rb | 62 +- app/models/machines_availability.rb | 16 + app/models/machines_pricing.rb | 15 +- app/models/notification.rb | 4 + app/models/notification_type.rb | 30 + app/models/o_auth2_mapping.rb | 3 + app/models/o_auth2_provider.rb | 17 + app/models/offer_day.rb | 6 + app/models/partner_plan.rb | 14 + app/models/plan.rb | 118 + app/models/plan_file.rb | 3 + app/models/plan_image.rb | 4 + app/models/price.rb | 133 + app/models/profile.rb | 3 +- app/models/project.rb | 105 + app/models/project_user.rb | 2 +- app/models/reservation.rb | 400 + app/models/role.rb | 4 - app/models/setting.rb | 44 + app/models/slot.rb | 49 + app/models/statistic_field.rb | 3 + app/models/statistic_graph.rb | 3 + app/models/statistic_index.rb | 5 + app/models/statistic_sub_type.rb | 4 + app/models/statistic_type.rb | 5 + app/models/statistic_type_sub_type.rb | 4 + app/models/stats/account.rb | 6 + app/models/stats/event.rb | 10 + app/models/stats/machine.rb | 9 + app/models/stats/project.rb | 14 + app/models/stats/subscription.rb | 12 + app/models/stats/training.rb | 10 + app/models/stats/user.rb | 6 + app/models/stylesheet.rb | 79 + app/models/subscription.rb | 232 + app/models/tag.rb | 7 + app/models/training.rb | 59 + app/models/trainings_availability.rb | 15 + app/models/trainings_pricing.rb | 8 + app/models/user.rb | 297 +- app/models/user_tag.rb | 4 + app/models/user_training.rb | 15 + app/models/users_credit.rb | 7 + app/pdfs/pdf/invoice.rb | 267 + app/policies/admin_policy.rb | 9 + app/policies/auth_provider_policy.rb | 19 + app/policies/avoir_policy.rb | 2 + app/policies/credit_policy.rb | 17 + app/policies/custom_asset_policy.rb | 11 + app/policies/export_policy.rb | 2 +- app/policies/group_policy.rb | 13 + app/policies/invoice_policy.rb | 13 + app/policies/machine_policy.rb | 2 +- app/policies/partner_plan_policy.rb | 17 + app/policies/plan_policy.rb | 13 + app/policies/price_policy.rb | 9 + app/policies/pricing_policy.rb | 5 + app/policies/project_policy.rb | 2 +- app/policies/reservation_policy.rb | 5 + app/policies/setting_policy.rb | 7 + app/policies/slot_policy.rb | 15 + app/policies/statistic_policy.rb | 7 + app/policies/subscription_policy.rb | 9 + app/policies/tag_policy.rb | 13 + app/policies/training_policy.rb | 19 + app/policies/user_policy.rb | 14 +- app/services/statistic_service.rb | 440 + app/sweepers/stylesheet_sweeper.rb | 9 + app/uploaders/custom_assets_uploader.rb | 58 + app/uploaders/event_image_uploader.rb | 66 + app/uploaders/machine_file_uploader.rb | 3 +- app/uploaders/machine_image_uploader.rb | 2 +- app/uploaders/plan_file_uploader.rb | 49 + app/uploaders/plan_image_uploader.rb | 62 + app/uploaders/profil_image_uploader.rb | 2 +- app/uploaders/project_cao_uploader.rb | 2 +- app/uploaders/project_image_uploader.rb | 2 +- app/views/api/abuses/create.json.jbuilder | 3 + app/views/api/admins/_admin.json.jbuilder | 13 + app/views/api/admins/create.json.jbuilder | 3 + app/views/api/admins/index.json.jbuilder | 1 + .../_auth_provider.json.jbuilder | 1 + .../api/auth_providers/active.json.jbuilder | 12 + .../api/auth_providers/index.json.jbuilder | 3 + .../mapping_fields.json.jbuilder | 9 + .../api/auth_providers/show.json.jbuilder | 12 + .../api/availabilities/index.json.jbuilder | 12 + .../api/availabilities/machine.json.jbuilder | 26 + .../availabilities/reservations.json.jbuilder | 14 + .../api/availabilities/show.json.jbuilder | 13 + .../availabilities/trainings.json.jbuilder | 42 + app/views/api/credits/index.json.jbuilder | 8 + app/views/api/credits/show.json.jbuilder | 5 + .../api/custom_assets/show.json.jbuilder | 12 + app/views/api/events/_event.json.jbuilder | 3 +- app/views/api/events/index.json.jbuilder | 1 + app/views/api/events/show.json.jbuilder | 1 + app/views/api/events/upcoming.json.jbuilder | 1 + app/views/api/groups/_group.json.jbuilder | 1 + app/views/api/groups/create.json.jbuilder | 1 + app/views/api/groups/index.json.jbuilder | 5 +- app/views/api/groups/update.json.jbuilder | 1 + app/views/api/invoices/avoir.json.jbuilder | 13 + app/views/api/invoices/index.json.jbuilder | 12 + app/views/api/invoices/show.json.jbuilder | 15 + app/views/api/machines/index.json.jbuilder | 8 + app/views/api/machines/show.json.jbuilder | 15 + app/views/api/members/export_members.xls.erb | 24 +- .../api/members/export_reservations.xls.erb | 22 + .../api/members/export_subscriptions.xls.erb | 26 + app/views/api/members/index.json.jbuilder | 40 +- app/views/api/members/show.json.jbuilder | 63 +- ..._notify_admin_abuse_reported.json.jbuilder | 6 + ...tify_admin_invoicing_changed.json.jbuilder | 7 + ...in_member_create_reservation.json.jbuilder | 5 + ...otify_admin_profile_complete.json.jbuilder | 5 + ...otify_admin_slot_is_canceled.json.jbuilder | 5 + ...otify_admin_slot_is_modified.json.jbuilder | 3 + ...notify_admin_subscribed_plan.json.jbuilder | 5 + ..._admin_subscription_canceled.json.jbuilder | 4 + ..._admin_subscription_extended.json.jbuilder | 9 + ...dmin_subscription_is_expired.json.jbuilder | 4 + ...iption_will_expire_in_7_days.json.jbuilder | 4 + ...ify_admin_user_group_changed.json.jbuilder | 7 + .../_notify_admin_user_merged.json.jbuilder | 9 + ...admin_when_project_published.json.jbuilder | 4 +- ...y_admin_when_user_is_created.json.jbuilder | 4 +- ..._admin_when_user_is_imported.json.jbuilder | 5 + ...fy_member_create_reservation.json.jbuilder | 4 + ...tify_member_slot_is_canceled.json.jbuilder | 5 + ...tify_member_slot_is_modified.json.jbuilder | 3 + ...otify_member_subscribed_plan.json.jbuilder | 4 + ...r_subscribed_plan_is_changed.json.jbuilder | 4 + ...member_subscription_canceled.json.jbuilder | 4 + ...member_subscription_extended.json.jbuilder | 8 + ...mber_subscription_is_expired.json.jbuilder | 3 + ...iption_will_expire_in_7_days.json.jbuilder | 3 + ...tify_partner_subscribed_plan.json.jbuilder | 7 + ...thor_when_collaborator_valid.json.jbuilder | 4 +- ...roject_collaborator_to_valid.json.jbuilder | 3 +- .../_notify_user_auth_migration.json.jbuilder | 3 + ...notify_user_profile_complete.json.jbuilder | 3 + .../_notify_user_training_valid.json.jbuilder | 4 + ...tify_user_user_group_changed.json.jbuilder | 3 + ...notify_user_when_avoir_ready.json.jbuilder | 7 + ...tify_user_when_invoice_ready.json.jbuilder | 7 + .../_undefined_notification.json.jbuilder | 5 + .../api/notifications/index.json.jbuilder | 8 +- app/views/api/plans/_plan.json.jbuilder | 28 + app/views/api/plans/index.json.jbuilder | 1 + .../api/plans/shallow_index.json.jbuilder | 11 + app/views/api/plans/show.json.jbuilder | 1 + app/views/api/prices/_price.json.jbuilder | 2 + app/views/api/prices/compute.json.jbuilder | 9 + app/views/api/prices/index.json.jbuilder | 1 + app/views/api/prices/update.json.jbuilder | 1 + app/views/api/pricing/index.json.jbuilder | 20 + app/views/api/projects/show.json.jbuilder | 5 +- .../reservations/_reservation.json.jbuilder | 14 + .../api/reservations/index.json.jbuilder | 4 + app/views/api/reservations/show.json.jbuilder | 29 + app/views/api/settings/_setting.json.jbuilder | 1 + app/views/api/settings/index.json.jbuilder | 3 + app/views/api/settings/show.json.jbuilder | 3 + app/views/api/settings/update.json.jbuilder | 3 + app/views/api/shared/_plan.json.jbuilder | 14 + app/views/api/slots/cancel.json.jbuilder | 2 + app/views/api/slots/show.json.jbuilder | 1 + app/views/api/statistics/index.json.jbuilder | 16 + .../subscriptions/_subscription.json.jbuilder | 7 + .../api/subscriptions/show.json.jbuilder | 1 + .../api/subscriptions/update.json.jbuilder | 1 + app/views/api/tags/index.json.jbuilder | 3 + app/views/api/tags/show.json.jbuilder | 1 + app/views/api/trainings/index.json.jbuilder | 19 + app/views/api/trainings/show.json.jbuilder | 11 + .../_trainings_pricing.json.jbuilder | 5 + .../trainings_pricings/index.json.jbuilder | 1 + .../trainings_pricings/update.json.jbuilder | 1 + app/views/api/users/create.json.jbuilder | 2 + app/views/api/users/index.json.jbuilder | 4 + app/views/application/index.html.erb | 105 +- .../mailer/confirmation_instructions.html.erb | 8 +- .../reset_password_instructions.html.erb | 8 +- app/views/devise/registrations/new.html.erb | 88 - .../layouts/notifications_mailer.html.erb | 24 +- .../notify_admin_abuse_reported.html.erb | 9 + .../notify_admin_invoicing_changed.html.erb | 17 + ...y_admin_member_create_reservation.html.erb | 13 + .../notify_admin_profile_complete.html.erb | 7 + .../notify_admin_slot_is_canceled.html.erb | 9 + .../notify_admin_slot_is_modified.html.erb | 5 + .../notify_admin_subscribed_plan.html.erb | 7 + ...otify_admin_subscription_canceled.html.erb | 6 + ...otify_admin_subscription_extended.html.erb | 13 + ...ify_admin_subscription_is_expired.html.erb | 6 + ...ubscription_will_expire_in_7_days.html.erb | 6 + .../notify_admin_user_group_changed.html.erb | 8 + .../notify_admin_user_merged.html.erb | 10 + ...tify_admin_when_project_published.html.erb | 4 +- ...notify_admin_when_user_is_created.html.erb | 8 +- ...otify_admin_when_user_is_imported.html.erb | 22 + .../notify_member_avoir_ready.html.erb | 20 + .../notify_member_create_reservation.html.erb | 10 + .../notify_member_invoice_ready.html.erb | 20 + .../notify_member_slot_is_canceled.html.erb | 4 + .../notify_member_slot_is_modified.html.erb | 5 + .../notify_member_subscribed_plan.html.erb | 5 + ...member_subscribed_plan_is_changed.html.erb | 3 + ...tify_member_subscription_canceled.html.erb | 3 + ...tify_member_subscription_extended.html.erb | 3 + ...fy_member_subscription_is_expired.html.erb | 5 + ...ubscription_will_expire_in_7_days.html.erb | 6 + .../notify_partner_subscribed_plan.html.erb | 3 + ...ct_author_when_collaborator_valid.html.erb | 4 +- ...ify_project_collaborator_to_valid.html.erb | 6 +- .../notify_user_auth_migration.html.erb | 29 + .../notify_user_profile_complete.html.erb | 3 + .../notify_user_training_valid.html.erb | 3 + .../notify_user_user_group_changed.html.erb | 3 + .../shared/_hello.html.erb | 1 + .../notify_member_account_is_created.html.erb | 10 - .../notify_user_account_created.html.erb | 51 + app/workers/indexer_worker.rb | 21 + app/workers/invoice_worker.rb | 24 + app/workers/statistic_worker.rb | 7 + app/workers/stripe_worker.rb | 17 + app/workers/subscription_expire_worker.rb | 25 + bin/rails | 2 +- bin/setup | 29 - bin/spring | 2 +- bower.json | 40 +- config.ru | 2 +- config/application.rb | 17 +- config/application.yml.default | 48 + config/database.yml.default | 96 +- config/deploy.rb | 14 +- config/deploy/production.rb | 10 +- config/deploy/staging.rb | 26 +- config/disqus_api.yml | 14 - config/environments/development.rb | 2 +- config/environments/production.rb | 26 +- config/environments/staging.rb | 26 +- config/initializers/activerecord.rb | 6 + config/initializers/assets.rb | 2 + config/initializers/devise.rb | 13 +- config/initializers/elasticsearch.rb | 3 + config/initializers/mail.rb | 15 + config/initializers/mandrill.rb | 3 - .../initializers/postgresql_database_tasks.rb | 11 + config/initializers/session_store.rb | 2 +- config/initializers/sidekiq.rb | 27 +- config/initializers/stripe.rb | 3 + config/locales/app.admin.en.yml | 463 + config/locales/app.admin.fr.yml | 463 + config/locales/app.logged.en.yml | 169 + config/locales/app.logged.fr.yml | 169 + config/locales/app.public.en.yml | 214 + config/locales/app.public.fr.yml | 214 + config/locales/app.shared.en.yml | 262 + config/locales/app.shared.fr.yml | 262 + config/locales/devise.en.yml | 5 + config/locales/devise.fr.yml | 127 +- config/locales/en.yml | 248 +- config/locales/fr.yml | 439 +- config/locales/mails.en.yml | 244 + config/locales/mails.fr.yml | 244 + config/locales/rails.en.yml | 204 + config/locales/rails.fr.yml | 206 + config/newrelic.yml | 224 + config/nginx.conf | 43 +- config/nginx_puma.conf | 44 + config/nginx_staging.conf | 43 +- config/routes.rb | 68 +- config/schedule.yml | 16 + config/secrets.yml | 83 +- config/sidekiq.yml | 6 +- config/unicorn.rb | 4 +- config/unicorn_init.sh | 2 +- config/unicorn_init_staging.sh | 2 +- config/unicorn_staging.rb | 4 +- contrib/docker/Dockerfile | 85 - contrib/docker/Makefile | 33 - contrib/docker/README.md | 118 - contrib/docker/fabmanager.conf | 23 - contrib/docker/fabmanager.yml | 6 - ... => 20140409083104_devise_create_users.rb} | 10 +- ... => 20140409083610_rolify_create_roles.rb} | 0 ...s.rb => 20140409153915_create_profiles.rb} | 5 +- ...s.rb => 20140410101026_create_projects.rb} | 6 - ...s.rb => 20140410140516_create_machines.rb} | 4 +- ...ets.rb => 20140410162151_create_assets.rb} | 2 +- ...0140411152729_create_projects_machines.rb} | 4 +- ...0414141134_add_is_allow_contact_to_user.rb | 5 + .../20140415104151_create_project_user.rb | 10 + ...20140415123625_add_author_id_to_project.rb | 5 + ...=> 20140416130838_create_project_steps.rb} | 4 +- ...> 20140422085949_create_availabilities.rb} | 1 - ...22090412_create_machines_availabilities.rb | 8 + ...0140513152025_add_title_to_project_step.rb | 6 + .../20140516083543_create_reservations.rb | 10 + db/migrate/20140516083909_create_slots.rb | 11 + ...516093335_add_reservable_to_reservation.rb | 5 + ....rb => 20140522115617_create_addresses.rb} | 0 ...rb => 20140522175539_create_components.rb} | 2 +- ...mes.rb => 20140522175714_create_themes.rb} | 0 ...s.rb => 20140522180032_create_licences.rb} | 0 ...40522180930_create_projects_components.rb} | 4 +- ... 20140522181011_create_projects_themes.rb} | 4 +- .../20140522181148_add_tags_to_project.rb | 5 + ...140523083230_add_licence_id_to_project.rb} | 4 +- .../20140526144327_add_state_to_project.rb | 8 + ...=> 20140527092045_create_notifications.rb} | 2 - ...0528134944_add_is_valid_to_project_user.rb | 5 + ...8140257_add_valid_token_to_project_user.rb | 5 + .../20140529145140_create_user_trainings.rb | 10 + ...ups.rb => 20140603084413_create_groups.rb} | 0 db/migrate/20140603085817_create_plans.rb | 13 + ...0140603164215_create_trainings_pricings.rb | 11 + ...rb => 20140604094514_add_group_to_user.rb} | 2 +- db/migrate/20140604113611_create_trainings.rb | 9 + ...20140604113919_create_trainings_machine.rb | 8 + ...4132045_create_trainings_availabilities.rb | 10 + ...5131_add_availability_id_to_reservation.rb | 6 + ...ne_id_to_training_id_from_user_training.rb | 6 + ...hine_to_training_from_trainings_pricing.rb | 6 + ...20140606133116_create_machines_pricings.rb | 13 + ...emove_availability_id_form_reservations.rb | 5 + ...20140609092827_add_availability_to_slot.rb | 5 + ...0610153123_add_stp_customer_id_to_users.rb | 14 + .../20140610170446_create_subscriptions.rb | 11 + ...50651_add_stp_invoice_id_to_reservation.rb | 6 + ...0620131525_add_start_at_to_subscription.rb | 8 + ...0140622121724_create_friendly_id_slugs.rb} | 0 .../20140622122944_add_slug_to_projects.rb | 6 + .../20140622145648_add_strip_id_to_groups.rb | 5 + .../20140623023557_add_slug_to_machines.rb | 6 + db/migrate/20140624123359_create_credits.rb | 11 + .../20140624123814_create_users_credits.rb | 11 + ...24124338_add_training_credit_nb_to_plan.rb | 11 + ...tart_at_to_expired_at_from_subscription.rb | 16 + .../20140703231208_add_username_to_user.rb | 12 + ...140703233420_add_index_username_to_user.rb | 5 + ...703233942_remove_username_from_profiles.rb | 5 + .../20140703235739_add_slug_to_users.rb | 7 + db/migrate/20140710144142_create_events.rb | 10 + ...rb => 20140710144427_create_categories.rb} | 0 ...0140710144610_create_events_categories.rb} | 4 +- db/migrate/20140711084809_change_event.rb | 10 + ...b_reserve_reduced_places_to_reservation.rb | 6 + ...140717143735_add_recurrence_id_to_event.rb | 12 + db/migrate/20140722162046_create_invoices.rb | 11 + .../20140722162309_create_invoice_items.rb | 12 + .../20140723075942_add_user_id_to_invoice.rb | 5 + ...7_remove_description_from_invoice_items.rb | 5 + ...723172610_add_invoiced_to_invoice_items.rb | 6 + ...5605_remove_invoiced_from_invoice_items.rb | 6 + ...131808_add_description_to_invoice_items.rb | 5 + ...55_add_subscription_id_to_invoice_items.rb | 5 + ...20140728110430_add_reference_to_invoice.rb | 5 + ...2111736_add_canceled_at_to_subscription.rb | 5 + ...110131407_add_to_cancel_to_subscription.rb | 5 + ...2044_remove_to_cancel_from_subscription.rb | 5 + .../20141215153643_create_offer_days.rb | 11 + ...141648_add_nb_total_places_to_trainings.rb | 5 + ...843_add_nb_total_places_to_availability.rb | 5 + .../20150107103903_add_slug_to_trainings.rb | 16 + ...0108082541_add_published_at_to_projects.rb | 13 + ...20150112160349_create_statistic_indices.rb | 11 + .../20150112160405_create_statistic_types.rb | 12 + ...150112160425_create_statistic_sub_types.rb | 11 + ...112757_add_simple_to_statistic_sub_type.rb | 5 + ...2_remove_simple_from_statistic_sub_type.rb | 5 + ...0114111243_add_simple_to_statistic_type.rb | 5 + .../20150114141926_create_statistic_fields.rb | 11 + ...e_additional_field_from_statistic_index.rb | 5 + ...50115143750_add_type_to_statistic_field.rb | 5 + ...ename_statistic_field_type_to_data_type.rb | 5 + ...atistic_type_id_from_statistic_sub_type.rb | 5 + ...9093811_create_statistic_type_sub_types.rb | 10 + ...0119160758_add_table_to_statistic_index.rb | 5 + .../20150119161004_create_statistic_graphs.rb | 11 + ...0150127101521_add_ca_to_statistic_index.rb | 5 + ...0150127155141_add_avoir_mode_to_invoice.rb | 5 + ...0150127161235_add_avoir_date_to_invoice.rb | 5 + ...0150127172510_add_invoice_id_to_invoice.rb | 6 + ..._add_subscription_to_expire_to_invoices.rb | 5 + ...ceiver_type_and_is_send_to_notification.rb | 8 + ...075148_add_is_active_attribute_to_users.rb | 5 + db/migrate/20150428091057_create_settings.rb | 12 + .../20150429092847_create_project_users.rb | 12 - db/migrate/20150429102505_create_events.rb | 17 - ...0506090921_add_description_to_trainings.rb | 5 + ...20150507075506_add_ex_start_at_to_slots.rb | 5 + .../20150507075620_add_ex_end_at_to_slots.rb | 5 + .../20150512123546_add_attributes_to_plan.rb | 7 + db/migrate/20150520132030_create_prices.rb | 12 + ...e_data_from_machines_pricings_to_prices.rb | 35 + .../20150526130729_add_base_name_to_plans.rb | 5 + .../20150527153312_add_canceled_at_to_slot.rb | 5 + ...default_for_training_nb_credit_to_plans.rb | 17 + ...nge_stripe_id_to_short_name_from_groups.rb | 5 + ...02_change_short_name_to_slug_from_group.rb | 5 + ...658_add_unique_index_to_slug_from_group.rb | 5 + .../20150603133050_drop_machines_pricings.rb | 5 + .../20150604081757_add_ui_weight_to_plans.rb | 5 + ...04131525_add_meta_data_to_notifications.rb | 5 + ...8142234_add_invoicing_disabled_to_users.rb | 5 + ...50609094336_add_interval_count_to_plans.rb | 5 + .../20150615135539_migrate_plan_stats.rb | 22 + .../20150617085623_create_custom_assets.rb | 9 + .../20150701090642_create_stylesheets.rb | 9 + db/migrate/20150702150754_create_tags.rb | 11 + db/migrate/20150702151009_create_user_tags.rb | 10 + ...20150706102547_create_availability_tags.rb | 10 + .../20150707135343_add_offered_to_slots.rb | 5 + ...542_change_amount_type_in_invoice_items.rb | 9 + ...115_add_invoice_item_id_to_invoice_item.rb | 5 + ...50715135751_add_description_to_invoices.rb | 5 + .../20150915144448_create_auth_providers.rb | 11 + ...20150915144939_create_o_auth2_providers.rb | 14 + .../20150915152943_create_o_auth2_mappings.rb | 13 + ...131_change_auth_provider_to_polymorphic.rb | 9 + ...0150916093159_create_database_providers.rb | 8 + .../20150921135557_add_sso_id_to_user.rb | 5 + ...5817_add_local_model_to_o_auth2_mapping.rb | 5 + ...ndpoints_to_urls_from_o_auth2_providers.rb | 8 + ...move_resource_url_from_o_auth2_mappings.rb | 6 + ...url_to_endpoints_from_o_auth2_providers.rb | 9 + ...d_resource_endpoint_to_o_auth2_mappings.rb | 6 + ...resource_endpoint_from_o_auth2_mappings.rb | 5 + .../20150924141714_add_omniauth_to_users.rb | 9 + ...41_add_profile_url_to_o_auth2_providers.rb | 5 + .../20151008152219_add_auth_token_to_users.rb | 6 + db/migrate/20151105125623_create_abuses.rb | 13 + .../20151210113548_add_merged_at_to_users.rb | 5 + ...19131623_add_destroying_to_availability.rb | 7 + db/schema.rb | 529 +- db/seeds.rb | 421 +- db/test_seeds.rb | 64 + doc/controllers_brief.svg | 206 + doc/controllers_complete.svg | 551 + doc/diagram.mwb | Bin 0 -> 47116 bytes doc/diagram.mwb.bak | Bin 0 -> 47112 bytes doc/diagram.png | Bin 0 -> 1200977 bytes doc/diagram_eer_fablab.pdf | Bin 0 -> 54153 bytes doc/elasticsearch.md | 416 + doc/models_brief.svg | 960 + doc/models_complete.svg | 1496 + doc/sso_authentication.md | 130 + docker/README.md | 195 + docker/database.yml | 27 + docker/env.example | 45 + docker/nginx.conf.example | 49 + docker/nginx_with_ssl.conf.example | 63 + docker/supervisor.conf | 30 + .../javascripts/fullcalendar/fullcalendar.js | 6112 + lib/omni_auth/omni_auth.rb | 5 + .../strategies/sso_oauth2_provider.rb | 65 + lib/tasks/fablab.rake | 180 + public/Charte_FABLAB.pdf | Bin 91022 -> 0 bytes public/about-fablab.jpg | Bin 0 -> 71754 bytes spec/factories/abuses.rb | 10 + spec/factories/auth_providers.rb | 8 + spec/factories/availability_tags.rb | 7 + spec/factories/avoirs.rb | 6 + spec/factories/credits.rb | 6 + spec/factories/{roles.rb => custom_assets.rb} | 2 +- spec/factories/database_providers.rb | 6 + spec/factories/invoices.rb | 6 + spec/factories/o_auth2_mappings.rb | 10 + spec/factories/o_auth2_providers.rb | 11 + spec/factories/plans.rb | 6 + spec/factories/prices.rb | 9 + spec/factories/reservations.rb | 6 + spec/factories/stylesheets.rb | 6 + spec/factories/subscriptions.rb | 6 + spec/factories/tags.rb | 6 + spec/factories/trainings.rb | 6 + spec/factories/user_tags.rb | 7 + spec/mailers/.keep | 0 .../mailers/previews/devise_mailer_preview.rb | 9 + .../previews/notifications_mailer_preview.rb | 7 + spec/mailers/previews/users_mailer_preview.rb | 5 + spec/models/abuse_spec.rb | 5 + spec/models/auth_provider_spec.rb | 5 + spec/models/availability_spec.rb | 9 +- spec/models/availability_tag_spec.rb | 5 + spec/models/avoir_spec.rb | 9 + spec/models/credit_spec.rb | 11 + spec/models/custom_asset_spec.rb | 5 + spec/models/database_provider_spec.rb | 5 + spec/models/invoice_spec.rb | 9 + spec/models/o_auth2_mapping_spec.rb | 5 + spec/models/o_auth2_provider_spec.rb | 5 + spec/models/plan_spec.rb | 60 + spec/models/price_spec.rb | 5 + spec/models/reservation_spec.rb | 28 + spec/models/stylesheet_spec.rb | 5 + spec/models/subscription_spec.rb | 26 + spec/models/{role_spec.rb => tag_spec.rb} | 2 +- spec/models/training_spec.rb | 18 + spec/models/user_spec.rb | 7 + spec/models/user_tag_spec.rb | 5 + test/controllers/.keep | 0 test/fixtures/.keep | 0 test/helpers/.keep | 0 test/integration/.keep | 0 test/models/.keep | 0 test/test_helper.rb | 7 + .../components/angular-animate/.bower.json | 20 + .../components/angular-animate/README.md | 68 + .../angular-animate/angular-animate.js | 2142 + .../angular-animate/angular-animate.min.js | 33 + .../angular-animate.min.js.map | 8 + .../components/angular-animate/bower.json | 9 + .../components/angular-animate/index.js | 2 + .../components/angular-animate/package.json | 26 + .../angular-base64-upload/.bower.json | 46 + .../angular-base64-upload/Gruntfile.js | 86 + .../angular-base64-upload/README.md | 92 + .../angular-base64-upload/banner.png | Bin 0 -> 77739 bytes .../angular-base64-upload/bower.json | 36 + .../angular-base64-upload/demo/README.md | 13 + .../angular-base64-upload/demo/index.html | 48 + .../demo/placeholder.png | Bin 0 -> 3479 bytes .../angular-base64-upload/demo/server.php | 43 + .../dist/angular-base64-upload.js | 65 + .../dist/angular-base64-upload.min.js | 4 + .../angular-base64-upload/karma-unit.js | 64 + .../angular-base64-upload/package.json | 43 + .../src/angular-base64-upload.js | 62 + .../components/angular-bootstrap-.gitignore | 1 + .../components/angular-bootstrap-.npmignore | 1 + .../components/angular-bootstrap-README.md | 120 + .../components/angular-bootstrap-index.js | 2 + .../components/angular-bootstrap-package.json | 23 + .../angular-bootstrap-switch/.bower.json | 41 + .../angular-bootstrap-switch/.bowerrc | 3 + .../angular-bootstrap-switch/.editorconfig | 21 + .../angular-bootstrap-switch/.gitattributes | 1 + .../.gitignore | 3 + .../angular-bootstrap-switch/.jshintrc | 26 + .../angular-bootstrap-switch/.npmignore | 12 + .../angular-bootstrap-switch/.travis.yml | 16 + .../angular-bootstrap-switch/CHANGELOG.md | 89 + .../angular-bootstrap-switch/CONTRIBUTING.md | 34 + .../angular-bootstrap-switch/Gruntfile.js | 133 + .../angular-bootstrap-switch/LICENSE | 191 + .../angular-bootstrap-switch/README.md | 148 + .../angular-bootstrap-switch/bower.json | 31 + .../angular-bootstrap-switch/bsSwitch.prefix | 2 + .../angular-bootstrap-switch/bsSwitch.suffix | 1 + .../angular-bootstrap-switch/common/module.js | 3 + .../dist/angular-bootstrap-switch.js | 250 + .../dist/angular-bootstrap-switch.min.js | 9 + .../example/index.html | 91 + .../example/scripts/app.js | 3 + .../example/scripts/controllers/main.js | 27 + .../example/styles/main.css | 22 + .../karma-chrome.conf.js | 73 + .../angular-bootstrap-switch/karma.conf.js | 73 + .../angular-bootstrap-switch/package.json | 43 + .../src/directives/bsSwitch.js | 252 + .../angular-bootstrap-switch/test/.jshintrc | 37 + .../angular-bootstrap-switch/test/runner.html | 10 + .../test/spec/directives/bsSwitchSpec.js | 619 + .../angular-bootstrap-ui-bootstrap-csp.css | 6 + .../components/angular-bootstrap/.bower.json | 12 +- .../components/angular-bootstrap/bower.json | 6 +- .../angular-bootstrap/ui-bootstrap-tpls.js | 7968 +- .../ui-bootstrap-tpls.min.js | 10 +- .../angular-bootstrap/ui-bootstrap.js | 7700 +- .../angular-bootstrap/ui-bootstrap.min.js | 8 +- .../components/angular-cookies/.bower.json | 20 + .../components/angular-cookies/README.md | 68 + .../angular-cookies/angular-cookies.js | 207 + .../angular-cookies/angular-cookies.min.js | 8 + .../angular-cookies.min.js.map | 8 + .../components/angular-cookies/bower.json | 9 + .../components/angular-cookies/index.js | 2 + .../components/angular-cookies/package.json | 26 + .../angular-google-analytics/.bower.json | 8 +- .../angular-google-analytics/bower.json | 2 +- .../dist/angular-google-analytics.js | 2 +- .../dist/angular-google-analytics.min.js | 2 +- .../angular-google-analytics/package.json | 4 +- .../components/angular-growl-v2/.bower.json | 49 + .../LICENSE | 0 .../components/angular-growl-v2/bower.json | 36 + .../angular-growl-v2/build/angular-growl.css | 135 + .../angular-growl-v2/build/angular-growl.js | 431 + .../build/angular-growl.min.css | 7 + .../build/angular-growl.min.js | 6 + .../components/angular-growl/.bower.json | 39 - .../assets/components/angular-growl/README.md | 275 - .../angular-growl/build/angular-growl.js | 183 - .../angular-growl/build/angular-growl.min.css | 7 - .../angular-growl/build/angular-growl.min.js | 6 - .../angular-growl/doc/screenshot.jpg | Bin 30921 -> 0 bytes .../components/angular-i18n/.bower.json | 5 +- .../angular-medium-editor/.bower.json | 19 + .../angular-medium-editor/.editorconfig | 13 + .../angular-medium-editor/.gitignore | 4 + .../angular-medium-editor/.jshintrc | 25 + .../angular-medium-editor/.travis.yml | 4 + .../angular-medium-editor/CONTRIBUTING.md | 25 + .../angular-medium-editor/Gruntfile.js | 154 + .../angular-medium-editor/README.md | 75 + .../angular-medium-editor/bower.json | 9 + .../angular-medium-editor/demo/css/demo.css | 104 + .../demo/css/normalize.css | 402 + .../angular-medium-editor/demo/demo.js | 34 + .../demo/img/medium-editor.jpg | Bin 0 -> 176383 bytes .../angular-medium-editor/demo/index.html | 26 + .../dist/angular-medium-editor.js | 85 + .../dist/angular-medium-editor.min.js | 8 + .../angular-medium-editor/package.json | 40 + .../src/angular-medium-editor.js | 85 + .../components/angular-minicolors/.bower.json | 39 + .../components/angular-minicolors/LICENSE | 20 + .../components/angular-minicolors/README.md | 95 + .../angular-minicolors/angular-minicolors.js | 94 + .../components/angular-minicolors/bower.json | 29 + .../components/angular-moment/.bower.json | 14 +- .../components/angular-moment/.gitignore | 3 +- .../components/angular-moment/.travis.yml | 1 + .../components/angular-moment/CHANGELOG.md | 14 + .../components/angular-moment/README.md | 40 +- .../angular-moment/angular-moment.js | 109 +- .../angular-moment/angular-moment.min.js | 2 +- .../angular-moment/angular-moment.min.js.map | 2 +- .../angular-moment/angular-moment.nuspec | 4 +- .../components/angular-moment/bower.json | 5 +- .../components/angular-moment/package.json | 3 +- .../assets/components/angular-moment/tests.js | 121 +- .../components/angular-resource/.bower.json | 20 + .../components/angular-resource/README.md | 68 + .../angular-resource/angular-resource.js | 668 + .../angular-resource/angular-resource.min.js | 13 + .../angular-resource.min.js.map | 8 + .../components/angular-resource/bower.json | 9 + .../components/angular-resource/index.js | 2 + .../components/angular-resource/package.json | 26 + .../components/angular-sanitize/.bower.json | 20 + .../components/angular-sanitize/README.md | 68 + .../angular-sanitize/angular-sanitize.js | 679 + .../angular-sanitize/angular-sanitize.min.js | 16 + .../angular-sanitize.min.js.map | 8 + .../components/angular-sanitize/bower.json | 9 + .../components/angular-sanitize/index.js | 2 + .../components/angular-sanitize/package.json | 26 + .../components/angular-summernote/.bower.json | 22 +- .../angular-summernote/CHANGELOG.md | 43 + .../components/angular-summernote/README.md | 66 +- .../components/angular-summernote/bower.json | 14 +- .../dist/angular-summernote.js | 84 +- .../dist/angular-summernote.min.js | 8 +- .../components/angular-summernote/gulpfile.js | 107 + .../src/angular-summernote.js | 83 +- .../components/angular-touch/.bower.json | 20 + .../assets/components/angular-touch/README.md | 68 + .../components/angular-touch/angular-touch.js | 631 + .../angular-touch/angular-touch.min.js | 13 + .../angular-touch/angular-touch.min.js.map | 8 + .../components/angular-touch/bower.json | 9 + .../assets/components/angular-touch/index.js | 2 + .../components/angular-touch/package.json | 26 + .../.bower.json | 24 + .../README.md | 29 + ...r-translate-interpolation-messageformat.js | 157 + ...anslate-interpolation-messageformat.min.js | 6 + .../bower.json | 13 + .../package.json | 25 + .../.bower.json | 23 + .../README.md | 29 + .../angular-translate-loader-partial.js | 522 + .../angular-translate-loader-partial.min.js | 6 + .../bower.json | 12 + .../package.json | 24 + .../components/angular-translate/.bower.json | 23 + .../components/angular-translate/README.md | 23 + .../angular-translate/angular-translate.js | 3138 + .../angular-translate.min.js | 6 + .../components/angular-translate/bower.json | 12 + .../angular-ui-calendar/.bower.json | 36 + .../LICENSE | 42 +- .../components/angular-ui-calendar/README.md | 129 + .../components/angular-ui-calendar/bower.json | 27 + .../angular-ui-calendar/src/calendar.js | 274 + .../components/angular-ui-router/.bower.json | 10 +- .../components/angular-ui-router/CHANGELOG.md | 31 + .../components/angular-ui-router/LICENSE | 2 +- .../components/angular-ui-router/README.md | 6 +- .../components/angular-ui-router/bower.json | 2 +- .../release/angular-ui-router.js | 308 +- .../release/angular-ui-router.min.js | 4 +- .../angular-ui-router/src/common.js | 2 +- .../components/angular-ui-router/src/state.js | 170 +- .../angular-ui-router/src/stateDirectives.js | 41 +- .../src/urlMatcherFactory.js | 72 +- .../angular-ui-router/src/urlRouter.js | 18 +- .../angular-ui-router/src/viewDirective.js | 1 + .../angular-ui-router/src/viewScroll.js | 2 +- .../components/angular-ui-select/.bower.json | 42 + .../components/angular-ui-select/CHANGELOG.md | 28 + .../components/angular-ui-select/LICENSE | 20 + .../components/angular-ui-select/README.md | 46 + .../components/angular-ui-select/bower.json | 29 + .../angular-ui-select/dist/select.css | 267 + .../angular-ui-select/dist/select.js | 1926 + .../angular-ui-select/dist/select.min.css | 6 + .../angular-ui-select/dist/select.min.js | 8 + .../components/angular-ui-select/package.json | 39 + .../components/angular-ui-select2/.bower.json | 29 - .../components/angular-ui-select2/.bowerrc | 3 - .../components/angular-ui-select2/.jshintrc | 11 - .../components/angular-ui-select2/.travis.yml | 13 - .../angular-ui-select2/CONTRIBUTING.md | 8 - .../angular-ui-select2/Gruntfile.js | 56 - .../components/angular-ui-select2/README.md | 165 - .../components/angular-ui-select2/bower.json | 20 - .../angular-ui-select2/docs/index.html | 47 - .../angular-ui-select2/docs/styles.css | 4 - .../angular-ui-select2/package.json | 28 - .../angular-ui-select2/src/select2.js | 212 - .../angular-ui-select2/test/karma.conf.js | 80 - .../angular-ui-select2/test/select2Spec.js | 411 - vendor/assets/components/angular/.bower.json | 10 +- vendor/assets/components/angular/README.md | 7 +- .../assets/components/angular/angular-csp.css | 13 +- vendor/assets/components/angular/angular.js | 13305 +- .../assets/components/angular/angular.min.js | 460 +- .../components/angular/angular.min.js.gzip | Bin 40123 -> 46451 bytes .../components/angular/angular.min.js.map | 6 +- vendor/assets/components/angular/bower.json | 2 +- vendor/assets/components/angular/index.js | 2 + vendor/assets/components/angular/package.json | 4 +- .../components/bootstrap-switch/.bower.json | 39 + .../components/bootstrap-switch/.bowerrc | 3 + .../components/bootstrap-switch/.gitignore | 5 + .../components/bootstrap-switch/CHANGELOG.md | 71 + .../components/bootstrap-switch/LICENSE | 176 + .../components/bootstrap-switch/README.md | 75 + .../components/bootstrap-switch/bower.json | 29 + .../dist/css/bootstrap2/bootstrap-switch.css | 519 + .../css/bootstrap2/bootstrap-switch.min.css | 22 + .../dist/css/bootstrap3/bootstrap-switch.css | 196 + .../css/bootstrap3/bootstrap-switch.min.css | 22 + .../dist/js/bootstrap-switch.js | 710 + .../dist/js/bootstrap-switch.min.js | 22 + .../bootstrap-switch/documentation-2.html | 320 + .../components/bootstrap-switch/events.html | 108 + .../components/bootstrap-switch/examples.html | 288 + .../components/bootstrap-switch/karma.json | 19 + .../components/bootstrap-switch/main.html | 67 + .../components/bootstrap-switch/methods.html | 123 + .../components/bootstrap-switch/options.html | 248 + .../src/coffee/bootstrap-switch.coffee | 525 + .../src/coffee/bootstrap-switch.tests.coffee | 27 + .../src/less/bootstrap2/bootstrap-switch.less | 193 + .../src/less/bootstrap2/build.less | 3 + .../src/less/bootstrap2/mixins.less | 702 + .../src/less/bootstrap2/variables.less | 301 + .../src/less/bootstrap3/bootstrap-switch.less | 193 + .../src/less/bootstrap3/build.less | 3 + .../src/less/bootstrap3/mixins.less | 929 + .../src/less/bootstrap3/variables.less | 829 + vendor/assets/components/d3/.bower.json | 36 + vendor/assets/components/d3/.gitattributes | 5 + vendor/assets/components/d3/CONTRIBUTING.md | 27 + vendor/assets/components/d3/LICENSE | 26 + vendor/assets/components/d3/README.md | 9 + vendor/assets/components/d3/bower.json | 25 + vendor/assets/components/d3/d3.js | 9550 ++ vendor/assets/components/d3/d3.min.js | 5 + vendor/assets/components/d3/package.js | 13 + .../components/elasticsearch/.bower.json | 24 + .../assets/components/elasticsearch/README.md | 50 + .../components/elasticsearch/bower.json | 14 + .../elasticsearch/elasticsearch.angular.js | 35958 +++++ .../elasticsearch.angular.min.js | 12 + .../elasticsearch/elasticsearch.jquery.js | 35947 +++++ .../elasticsearch/elasticsearch.jquery.min.js | 12 + .../components/elasticsearch/elasticsearch.js | 41112 +++++ .../elasticsearch/elasticsearch.min.js | 14 + .../components/elasticsearch/package.json | 19 + .../components/fullcalendar/.bower.json | 62 + .../assets/components/fullcalendar/bower.json | 53 + .../components/fullcalendar/changelog.md | 875 + .../fullcalendar/dist/fullcalendar.css | 1061 + .../fullcalendar/dist/fullcalendar.js | 10793 ++ .../fullcalendar/dist/fullcalendar.min.css | 5 + .../fullcalendar/dist/fullcalendar.min.js | 8 + .../fullcalendar/dist/fullcalendar.print.css | 202 + .../components/fullcalendar/dist/gcal.js | 184 + .../components/fullcalendar/dist/lang-all.js | 4 + .../fullcalendar/dist/lang/ar-ma.js | 1 + .../fullcalendar/dist/lang/ar-sa.js | 1 + .../fullcalendar/dist/lang/ar-tn.js | 1 + .../components/fullcalendar/dist/lang/ar.js | 1 + .../components/fullcalendar/dist/lang/bg.js | 1 + .../components/fullcalendar/dist/lang/ca.js | 1 + .../components/fullcalendar/dist/lang/cs.js | 1 + .../components/fullcalendar/dist/lang/da.js | 1 + .../fullcalendar/dist/lang/de-at.js | 1 + .../components/fullcalendar/dist/lang/de.js | 1 + .../components/fullcalendar/dist/lang/el.js | 1 + .../fullcalendar/dist/lang/en-au.js | 1 + .../fullcalendar/dist/lang/en-ca.js | 1 + .../fullcalendar/dist/lang/en-gb.js | 1 + .../components/fullcalendar/dist/lang/es.js | 1 + .../components/fullcalendar/dist/lang/fa.js | 1 + .../components/fullcalendar/dist/lang/fi.js | 1 + .../fullcalendar/dist/lang/fr-ca.js | 1 + .../components/fullcalendar/dist/lang/fr.js | 1 + .../components/fullcalendar/dist/lang/he.js | 1 + .../components/fullcalendar/dist/lang/hi.js | 1 + .../components/fullcalendar/dist/lang/hr.js | 1 + .../components/fullcalendar/dist/lang/hu.js | 1 + .../components/fullcalendar/dist/lang/id.js | 1 + .../components/fullcalendar/dist/lang/is.js | 1 + .../components/fullcalendar/dist/lang/it.js | 1 + .../components/fullcalendar/dist/lang/ja.js | 1 + .../components/fullcalendar/dist/lang/ko.js | 1 + .../components/fullcalendar/dist/lang/lt.js | 1 + .../components/fullcalendar/dist/lang/lv.js | 1 + .../components/fullcalendar/dist/lang/nb.js | 1 + .../components/fullcalendar/dist/lang/nl.js | 1 + .../components/fullcalendar/dist/lang/pl.js | 1 + .../fullcalendar/dist/lang/pt-br.js | 1 + .../components/fullcalendar/dist/lang/pt.js | 1 + .../components/fullcalendar/dist/lang/ro.js | 1 + .../components/fullcalendar/dist/lang/ru.js | 1 + .../components/fullcalendar/dist/lang/sk.js | 1 + .../components/fullcalendar/dist/lang/sl.js | 1 + .../fullcalendar/dist/lang/sr-cyrl.js | 1 + .../components/fullcalendar/dist/lang/sr.js | 1 + .../components/fullcalendar/dist/lang/sv.js | 1 + .../components/fullcalendar/dist/lang/th.js | 1 + .../components/fullcalendar/dist/lang/tr.js | 1 + .../components/fullcalendar/dist/lang/uk.js | 1 + .../components/fullcalendar/dist/lang/vi.js | 1 + .../fullcalendar/dist/lang/zh-cn.js | 1 + .../fullcalendar/dist/lang/zh-tw.js | 1 + .../components/fullcalendar/license.txt | 20 + .../assets/components/fullcalendar/readme.md | 16 + vendor/assets/components/holderjs/.bower.json | 11 +- vendor/assets/components/holderjs/README.md | 3 +- vendor/assets/components/holderjs/gulpfile.js | 3 +- vendor/assets/components/holderjs/holder.js | 229 +- .../assets/components/holderjs/holder.min.js | 4 +- .../assets/components/holderjs/package.json | 2 +- .../assets/components/holderjs/src/holder.js | 10 +- .../components/holderjs/src/lib/polyfills.js | 39 +- .../components/holderjs/test/.gitignore | 1 + .../components/holderjs/test/index.html | 2 +- .../components/holderjs/test/phantom.js | 15 + .../components/jquery-minicolors/.bower.json | 34 + .../components/jquery-minicolors/bower.json | 25 + .../jquery-minicolors/component.json | 19 + .../jquery-minicolors/composer.json | 20 + .../components/jquery-minicolors/index.html | 591 + .../jquery-minicolors/jquery.minicolors.css | 268 + .../jquery-minicolors/jquery.minicolors.js | 844 + .../jquery.minicolors.min.js | 9 + .../jquery-minicolors/jquery.minicolors.png | Bin 0 -> 77459 bytes .../components/jquery-minicolors/readme.md | 9 + .../jquery-minicolors/without-bootstrap.html | 154 + vendor/assets/components/jquery/.bower.json | 11 +- vendor/assets/components/jquery/bower.json | 3 +- .../assets/components/jquery/dist/jquery.js | 13 +- .../components/jquery/dist/jquery.min.js | 8 +- .../components/jquery/dist/jquery.min.map | 2 +- vendor/assets/components/jquery/src/core.js | 7 +- .../components/medium-editor/.bower.json | 48 + .../assets/components/medium-editor/LICENSE | 36 + .../components/medium-editor/bower.json | 37 + .../medium-editor/dist/css/medium-editor.css | 212 + .../dist/css/medium-editor.min.css | 1 + .../dist/css/themes/bootstrap.css | 67 + .../dist/css/themes/bootstrap.min.css | 1 + .../medium-editor/dist/css/themes/default.css | 63 + .../dist/css/themes/default.min.css | 1 + .../medium-editor/dist/css/themes/flat.css | 57 + .../dist/css/themes/flat.min.css | 1 + .../medium-editor/dist/css/themes/mani.css | 56 + .../dist/css/themes/mani.min.css | 1 + .../medium-editor/dist/css/themes/roman.css | 57 + .../dist/css/themes/roman.min.css | 1 + .../medium-editor/dist/js/medium-editor.js | 3982 + .../dist/js/medium-editor.min.js | 2 + .../medium-editor/src/sass/_clearfix.scss | 8 + .../medium-editor/src/sass/_pop-upwards.scss | 29 + .../medium-editor/src/sass/medium-editor.scss | 196 + .../src/sass/themes/bootstrap.scss | 99 + .../src/sass/themes/default.scss | 84 + .../medium-editor/src/sass/themes/flat.scss | 88 + .../medium-editor/src/sass/themes/mani.scss | 84 + .../medium-editor/src/sass/themes/roman.scss | 84 + .../medium-editor/src/wrappers/end.js | 2 + .../medium-editor/src/wrappers/start.js | 14 + .../components/messageformat/.bower.json | 21 + .../components/messageformat/.gitignore | 45 + .../components/messageformat/.travis.yml | 4 + .../assets/components/messageformat/Makefile | 33 + .../assets/components/messageformat/README.md | 524 + .../messageformat/bin/messageformat.js | 211 + .../components/messageformat/bower.json | 9 + .../components/messageformat/component.json | 9 + .../messageformat/example/en/colors.json | 5 + .../messageformat/example/en/i18n.js | 16 + .../example/en/sub/folder/plural.json | 3 + .../messageformat/example/fr/colors.json | 5 + .../messageformat/example/fr/i18n.js | 16 + .../example/fr/sub/folder/plural.json | 3 + .../messageformat/example/index.html | 28 + .../messageformat/lib/message_parser.js | 1325 + .../messageformat/lib/message_parser.pegjs | 173 + .../messageformat/lib/messageformat.dev.js | 256 + .../lib/messageformat.include.js | 6 + .../components/messageformat/locale/af.js | 1 + .../components/messageformat/locale/am.js | 1 + .../components/messageformat/locale/ar.js | 18 + .../components/messageformat/locale/bg.js | 1 + .../components/messageformat/locale/bn.js | 1 + .../components/messageformat/locale/br.js | 18 + .../components/messageformat/locale/ca.js | 1 + .../components/messageformat/locale/cs.js | 9 + .../components/messageformat/locale/cy.js | 18 + .../components/messageformat/locale/da.js | 1 + .../components/messageformat/locale/de.js | 1 + .../components/messageformat/locale/el.js | 1 + .../components/messageformat/locale/en.js | 1 + .../components/messageformat/locale/es.js | 1 + .../components/messageformat/locale/et.js | 1 + .../components/messageformat/locale/eu.js | 1 + .../components/messageformat/locale/fa.js | 1 + .../components/messageformat/locale/fi.js | 1 + .../components/messageformat/locale/fil.js | 1 + .../components/messageformat/locale/fr.js | 1 + .../components/messageformat/locale/ga.js | 1 + .../components/messageformat/locale/gl.js | 1 + .../components/messageformat/locale/gsw.js | 1 + .../components/messageformat/locale/gu.js | 1 + .../components/messageformat/locale/he.js | 1 + .../components/messageformat/locale/hi.js | 1 + .../components/messageformat/locale/hr.js | 14 + .../components/messageformat/locale/hu.js | 1 + .../components/messageformat/locale/id.js | 1 + .../components/messageformat/locale/in.js | 1 + .../components/messageformat/locale/is.js | 1 + .../components/messageformat/locale/it.js | 1 + .../components/messageformat/locale/iw.js | 1 + .../components/messageformat/locale/ja.js | 1 + .../components/messageformat/locale/kn.js | 1 + .../components/messageformat/locale/ko.js | 1 + .../components/messageformat/locale/lag.js | 9 + .../components/messageformat/locale/ln.js | 1 + .../components/messageformat/locale/lt.js | 10 + .../components/messageformat/locale/lv.js | 9 + .../components/messageformat/locale/mk.js | 1 + .../components/messageformat/locale/ml.js | 1 + .../components/messageformat/locale/mo.js | 10 + .../components/messageformat/locale/mr.js | 1 + .../components/messageformat/locale/ms.js | 1 + .../components/messageformat/locale/mt.js | 12 + .../components/messageformat/locale/nl.js | 1 + .../components/messageformat/locale/no.js | 1 + .../components/messageformat/locale/or.js | 1 + .../components/messageformat/locale/pl.js | 15 + .../components/messageformat/locale/pt.js | 1 + .../components/messageformat/locale/ro.js | 10 + .../components/messageformat/locale/ru.js | 14 + .../components/messageformat/locale/shi.js | 9 + .../components/messageformat/locale/sk.js | 9 + .../components/messageformat/locale/sl.js | 12 + .../components/messageformat/locale/sq.js | 1 + .../components/messageformat/locale/sr.js | 14 + .../components/messageformat/locale/sv.js | 1 + .../components/messageformat/locale/sw.js | 1 + .../components/messageformat/locale/ta.js | 1 + .../components/messageformat/locale/te.js | 1 + .../components/messageformat/locale/th.js | 1 + .../components/messageformat/locale/tl.js | 1 + .../components/messageformat/locale/tr.js | 1 + .../components/messageformat/locale/uk.js | 14 + .../components/messageformat/locale/ur.js | 1 + .../components/messageformat/locale/vi.js | 1 + .../components/messageformat/locale/zh.js | 1 + .../components/messageformat/messageformat.js | 1581 + .../components/messageformat/package.json | 45 + .../components/messageformat/test/common.js | 8 + .../components/messageformat/test/index.html | 25 + .../messageformat/test/jquery.min.js | 4 + .../components/messageformat/test/tests.js | 704 + .../components/moment-timezone/.bower.json | 29 + .../assets/components/moment-timezone/LICENSE | 20 + .../components/moment-timezone/README.md | 36 + .../components/moment-timezone/bower.json | 18 + .../moment-timezone-with-data-2010-2020.js | 1173 + ...moment-timezone-with-data-2010-2020.min.js | 7 + .../builds/moment-timezone-with-data.js | 1173 + .../builds/moment-timezone-with-data.min.js | 7 + .../builds/moment-timezone.min.js | 6 + .../components/moment-timezone/changelog.md | 95 + .../components/moment-timezone/composer.json | 43 + .../moment-timezone/data/meta/latest.json | 5030 + .../moment-timezone/data/packed/latest.json | 590 + .../moment-timezone/data/unpacked/latest.json | 124075 +++++++++++++++ .../moment-timezone/moment-timezone-utils.js | 316 + .../moment-timezone/moment-timezone.js | 583 + vendor/assets/components/moment/.bower.json | 19 +- vendor/assets/components/moment/CHANGELOG.md | 24 + vendor/assets/components/moment/LICENSE | 2 +- .../assets/components/moment/Moment.js.nuspec | 28 - vendor/assets/components/moment/README.md | 3 + .../components/moment/benchmarks/clone.js | 10 - vendor/assets/components/moment/bower.json | 10 +- vendor/assets/components/moment/locale/af.js | 6 +- .../assets/components/moment/locale/ar-ma.js | 6 +- .../assets/components/moment/locale/ar-sa.js | 4 +- .../assets/components/moment/locale/ar-tn.js | 6 +- vendor/assets/components/moment/locale/ar.js | 8 +- vendor/assets/components/moment/locale/az.js | 6 +- vendor/assets/components/moment/locale/be.js | 6 +- vendor/assets/components/moment/locale/bg.js | 6 +- vendor/assets/components/moment/locale/bn.js | 8 +- vendor/assets/components/moment/locale/bo.js | 6 +- vendor/assets/components/moment/locale/br.js | 4 +- vendor/assets/components/moment/locale/bs.js | 6 +- vendor/assets/components/moment/locale/ca.js | 4 +- vendor/assets/components/moment/locale/cs.js | 6 +- vendor/assets/components/moment/locale/cv.js | 46 +- vendor/assets/components/moment/locale/cy.js | 6 +- vendor/assets/components/moment/locale/da.js | 6 +- .../assets/components/moment/locale/de-at.js | 4 +- vendor/assets/components/moment/locale/de.js | 4 +- vendor/assets/components/moment/locale/el.js | 4 +- .../assets/components/moment/locale/en-au.js | 4 +- .../assets/components/moment/locale/en-ca.js | 4 +- .../assets/components/moment/locale/en-gb.js | 4 +- vendor/assets/components/moment/locale/eo.js | 6 +- vendor/assets/components/moment/locale/es.js | 16 +- vendor/assets/components/moment/locale/et.js | 6 +- vendor/assets/components/moment/locale/eu.js | 10 +- vendor/assets/components/moment/locale/fa.js | 6 +- vendor/assets/components/moment/locale/fi.js | 8 +- vendor/assets/components/moment/locale/fo.js | 6 +- .../assets/components/moment/locale/fr-ca.js | 10 +- vendor/assets/components/moment/locale/fr.js | 6 +- vendor/assets/components/moment/locale/fy.js | 6 +- vendor/assets/components/moment/locale/gl.js | 6 +- vendor/assets/components/moment/locale/he.js | 10 +- vendor/assets/components/moment/locale/hi.js | 4 +- vendor/assets/components/moment/locale/hr.js | 10 +- vendor/assets/components/moment/locale/hu.js | 6 +- .../assets/components/moment/locale/hy-am.js | 6 +- vendor/assets/components/moment/locale/id.js | 6 +- vendor/assets/components/moment/locale/is.js | 6 +- vendor/assets/components/moment/locale/it.js | 6 +- vendor/assets/components/moment/locale/ja.js | 6 +- vendor/assets/components/moment/locale/jv.js | 82 + vendor/assets/components/moment/locale/ka.js | 4 +- vendor/assets/components/moment/locale/km.js | 6 +- vendor/assets/components/moment/locale/ko.js | 4 +- vendor/assets/components/moment/locale/lb.js | 4 +- vendor/assets/components/moment/locale/lt.js | 22 +- vendor/assets/components/moment/locale/lv.js | 55 +- vendor/assets/components/moment/locale/me.js | 108 + vendor/assets/components/moment/locale/mk.js | 6 +- vendor/assets/components/moment/locale/ml.js | 4 +- vendor/assets/components/moment/locale/mr.js | 4 +- .../assets/components/moment/locale/ms-my.js | 6 +- vendor/assets/components/moment/locale/ms.js | 81 + vendor/assets/components/moment/locale/my.js | 9 +- vendor/assets/components/moment/locale/nb.js | 6 +- vendor/assets/components/moment/locale/ne.js | 4 +- vendor/assets/components/moment/locale/nl.js | 6 +- vendor/assets/components/moment/locale/nn.js | 6 +- vendor/assets/components/moment/locale/pl.js | 13 +- .../assets/components/moment/locale/pt-br.js | 18 +- vendor/assets/components/moment/locale/pt.js | 16 +- vendor/assets/components/moment/locale/ro.js | 2 +- vendor/assets/components/moment/locale/ru.js | 6 +- vendor/assets/components/moment/locale/si.js | 64 + vendor/assets/components/moment/locale/sk.js | 6 +- vendor/assets/components/moment/locale/sl.js | 91 +- vendor/assets/components/moment/locale/sq.js | 6 +- .../components/moment/locale/sr-cyrl.js | 6 +- vendor/assets/components/moment/locale/sr.js | 6 +- vendor/assets/components/moment/locale/sv.js | 10 +- vendor/assets/components/moment/locale/ta.js | 6 +- vendor/assets/components/moment/locale/th.js | 6 +- .../assets/components/moment/locale/tl-ph.js | 6 +- vendor/assets/components/moment/locale/tr.js | 6 +- vendor/assets/components/moment/locale/tzl.js | 84 + .../components/moment/locale/tzm-latn.js | 6 +- vendor/assets/components/moment/locale/tzm.js | 6 +- vendor/assets/components/moment/locale/uk.js | 6 +- vendor/assets/components/moment/locale/uz.js | 6 +- vendor/assets/components/moment/locale/vi.js | 10 +- .../assets/components/moment/locale/zh-cn.js | 30 +- .../assets/components/moment/locale/zh-tw.js | 10 +- .../assets/components/moment/meteor/README.md | 25 - .../assets/components/moment/meteor/export.js | 3 - .../assets/components/moment/meteor/test.js | 5 - .../assets/components/moment/min/locales.js | 1103 +- .../components/moment/min/locales.min.js | 27 +- .../moment/min/moment-with-locales.js | 1594 +- .../moment/min/moment-with-locales.min.js | 41 +- .../components/moment/min/moment.min.js | 6 +- vendor/assets/components/moment/min/tests.js | 2948 +- vendor/assets/components/moment/moment.js | 490 +- .../moment/scripts/npm_prepublish.sh | 43 - .../moment/src/lib/create/check-overflow.js | 7 +- .../moment/src/lib/create/from-anything.js | 27 +- .../moment/src/lib/create/from-array.js | 3 +- .../src/lib/create/from-string-and-array.js | 11 +- .../src/lib/create/from-string-and-format.js | 21 +- .../moment/src/lib/create/from-string.js | 9 +- ...ault-parsing-flags.js => parsing-flags.js} | 9 +- .../components/moment/src/lib/create/valid.js | 25 +- .../components/moment/src/lib/duration/as.js | 18 +- .../moment/src/lib/duration/bubble.js | 39 +- .../moment/src/lib/duration/iso-string.js | 36 +- .../moment/src/lib/format/format.js | 7 +- .../moment/src/lib/locale/formats.js | 22 +- .../moment/src/lib/locale/locales.js | 4 +- .../components/moment/src/lib/locale/set.js | 2 +- .../moment/src/lib/moment/calendar.js | 4 +- .../moment/src/lib/moment/constructor.js | 7 +- .../components/moment/src/lib/moment/from.js | 3 + .../moment/src/lib/moment/min-max.js | 2 +- .../moment/src/lib/moment/prototype.js | 6 +- .../moment/src/lib/moment/to-type.js | 13 + .../components/moment/src/lib/moment/to.js | 13 + .../components/moment/src/lib/moment/valid.js | 5 +- .../components/moment/src/lib/parse/regex.js | 9 +- .../moment/src/lib/units/day-of-week.js | 31 +- .../moment/src/lib/units/day-of-year.js | 18 +- .../components/moment/src/lib/units/hour.js | 3 +- .../moment/src/lib/units/millisecond.js | 40 +- .../components/moment/src/lib/units/month.js | 3 +- .../components/moment/src/lib/units/offset.js | 31 +- .../components/moment/src/lib/units/year.js | 6 +- .../moment/src/lib/utils/abs-ceil.js | 7 + .../moment/src/lib/utils/deprecate.js | 3 +- .../moment/src/lib/utils/is-date.js | 2 +- .../components/moment/src/lib/utils/to-int.js | 8 +- .../moment/src/lib/utils/zero-fill.js | 10 +- .../assets/components/moment/src/locale/af.js | 6 +- .../components/moment/src/locale/ar-ma.js | 6 +- .../components/moment/src/locale/ar-sa.js | 4 +- .../components/moment/src/locale/ar-tn.js | 6 +- .../assets/components/moment/src/locale/ar.js | 8 +- .../assets/components/moment/src/locale/az.js | 6 +- .../assets/components/moment/src/locale/be.js | 6 +- .../assets/components/moment/src/locale/bg.js | 6 +- .../assets/components/moment/src/locale/bn.js | 8 +- .../assets/components/moment/src/locale/bo.js | 6 +- .../assets/components/moment/src/locale/br.js | 4 +- .../assets/components/moment/src/locale/bs.js | 6 +- .../assets/components/moment/src/locale/ca.js | 4 +- .../assets/components/moment/src/locale/cs.js | 6 +- .../assets/components/moment/src/locale/cv.js | 47 +- .../assets/components/moment/src/locale/cy.js | 6 +- .../assets/components/moment/src/locale/da.js | 6 +- .../components/moment/src/locale/de-at.js | 4 +- .../assets/components/moment/src/locale/de.js | 4 +- .../assets/components/moment/src/locale/el.js | 4 +- .../components/moment/src/locale/en-au.js | 4 +- .../components/moment/src/locale/en-ca.js | 4 +- .../components/moment/src/locale/en-gb.js | 4 +- .../assets/components/moment/src/locale/eo.js | 6 +- .../assets/components/moment/src/locale/es.js | 16 +- .../assets/components/moment/src/locale/et.js | 6 +- .../assets/components/moment/src/locale/eu.js | 10 +- .../assets/components/moment/src/locale/fa.js | 6 +- .../assets/components/moment/src/locale/fi.js | 8 +- .../assets/components/moment/src/locale/fo.js | 6 +- .../components/moment/src/locale/fr-ca.js | 10 +- .../assets/components/moment/src/locale/fr.js | 6 +- .../assets/components/moment/src/locale/fy.js | 6 +- .../assets/components/moment/src/locale/gl.js | 6 +- .../assets/components/moment/src/locale/he.js | 10 +- .../assets/components/moment/src/locale/hi.js | 4 +- .../assets/components/moment/src/locale/hr.js | 10 +- .../assets/components/moment/src/locale/hu.js | 6 +- .../components/moment/src/locale/hy-am.js | 6 +- .../assets/components/moment/src/locale/id.js | 6 +- .../assets/components/moment/src/locale/is.js | 6 +- .../assets/components/moment/src/locale/it.js | 6 +- .../assets/components/moment/src/locale/ja.js | 6 +- .../assets/components/moment/src/locale/jv.js | 73 + .../assets/components/moment/src/locale/ka.js | 4 +- .../assets/components/moment/src/locale/km.js | 6 +- .../assets/components/moment/src/locale/ko.js | 4 +- .../assets/components/moment/src/locale/lb.js | 4 +- .../assets/components/moment/src/locale/lt.js | 22 +- .../assets/components/moment/src/locale/lv.js | 55 +- .../assets/components/moment/src/locale/me.js | 99 + .../assets/components/moment/src/locale/mk.js | 6 +- .../assets/components/moment/src/locale/ml.js | 4 +- .../assets/components/moment/src/locale/mr.js | 4 +- .../components/moment/src/locale/ms-my.js | 6 +- .../assets/components/moment/src/locale/ms.js | 73 + .../assets/components/moment/src/locale/my.js | 9 +- .../assets/components/moment/src/locale/nb.js | 6 +- .../assets/components/moment/src/locale/ne.js | 4 +- .../assets/components/moment/src/locale/nl.js | 6 +- .../assets/components/moment/src/locale/nn.js | 6 +- .../assets/components/moment/src/locale/pl.js | 14 +- .../components/moment/src/locale/pt-br.js | 18 +- .../assets/components/moment/src/locale/pt.js | 16 +- .../assets/components/moment/src/locale/ro.js | 2 +- .../assets/components/moment/src/locale/ru.js | 6 +- .../assets/components/moment/src/locale/si.js | 56 + .../assets/components/moment/src/locale/sk.js | 6 +- .../assets/components/moment/src/locale/sl.js | 92 +- .../assets/components/moment/src/locale/sq.js | 6 +- .../components/moment/src/locale/sr-cyrl.js | 6 +- .../assets/components/moment/src/locale/sr.js | 6 +- .../assets/components/moment/src/locale/sv.js | 10 +- .../assets/components/moment/src/locale/ta.js | 6 +- .../assets/components/moment/src/locale/th.js | 6 +- .../components/moment/src/locale/tl-ph.js | 6 +- .../assets/components/moment/src/locale/tr.js | 6 +- .../components/moment/src/locale/tzl.js | 78 + .../components/moment/src/locale/tzm-latn.js | 6 +- .../components/moment/src/locale/tzm.js | 6 +- .../assets/components/moment/src/locale/uk.js | 6 +- .../assets/components/moment/src/locale/uz.js | 6 +- .../assets/components/moment/src/locale/vi.js | 10 +- .../components/moment/src/locale/zh-cn.js | 30 +- .../components/moment/src/locale/zh-tw.js | 11 +- vendor/assets/components/moment/src/moment.js | 4 +- vendor/assets/components/ngUpload/.bower.json | 8 +- vendor/assets/components/ngUpload/bower.json | 2 +- .../assets/components/ngUpload/ng-upload.js | 93 +- .../components/ngUpload/ng-upload.min.js | 2 +- vendor/assets/components/ngUpload/readme.md | 2 +- vendor/assets/components/nvd3/.bower.json | 50 + vendor/assets/components/nvd3/bower.json | 40 + vendor/assets/components/nvd3/build/nv.d3.css | 641 + vendor/assets/components/nvd3/build/nv.d3.js | 13298 ++ .../components/nvd3/build/nv.d3.min.css | 1 + .../assets/components/nvd3/build/nv.d3.min.js | 8 + vendor/assets/components/nvd3/package.js | 17 + vendor/assets/components/select2/.bower.json | 24 - vendor/assets/components/select2/.gitignore | 2 - vendor/assets/components/select2/LICENSE | 18 - vendor/assets/components/select2/README.md | 90 - vendor/assets/components/select2/bower.json | 8 - .../assets/components/select2/component.json | 66 - .../assets/components/select2/composer.json | 29 - vendor/assets/components/select2/package.json | 20 - vendor/assets/components/select2/release.sh | 79 - .../components/select2/select2-bootstrap.css | 87 - .../components/select2/select2-spinner.gif | Bin 1849 -> 0 bytes vendor/assets/components/select2/select2.css | 646 - .../components/select2/select2.jquery.json | 36 - vendor/assets/components/select2/select2.js | 3448 - .../assets/components/select2/select2.min.js | 22 - vendor/assets/components/select2/select2.png | Bin 613 -> 0 bytes .../components/select2/select2_locale_ar.js | 17 - .../components/select2/select2_locale_bg.js | 18 - .../components/select2/select2_locale_ca.js | 17 - .../components/select2/select2_locale_cs.js | 49 - .../components/select2/select2_locale_da.js | 17 - .../components/select2/select2_locale_de.js | 15 - .../components/select2/select2_locale_el.js | 17 - .../select2/select2_locale_en.js.template | 18 - .../components/select2/select2_locale_es.js | 15 - .../components/select2/select2_locale_et.js | 17 - .../components/select2/select2_locale_eu.js | 43 - .../components/select2/select2_locale_fa.js | 19 - .../components/select2/select2_locale_fi.js | 28 - .../components/select2/select2_locale_fr.js | 16 - .../components/select2/select2_locale_gl.js | 43 - .../components/select2/select2_locale_he.js | 17 - .../components/select2/select2_locale_hr.js | 22 - .../components/select2/select2_locale_hu.js | 15 - .../components/select2/select2_locale_id.js | 17 - .../components/select2/select2_locale_is.js | 15 - .../components/select2/select2_locale_it.js | 15 - .../components/select2/select2_locale_ja.js | 15 - .../components/select2/select2_locale_ka.js | 17 - .../components/select2/select2_locale_ko.js | 17 - .../components/select2/select2_locale_lt.js | 24 - .../components/select2/select2_locale_lv.js | 17 - .../components/select2/select2_locale_mk.js | 17 - .../components/select2/select2_locale_ms.js | 17 - .../components/select2/select2_locale_nl.js | 15 - .../components/select2/select2_locale_no.js | 18 - .../components/select2/select2_locale_pl.js | 22 - .../select2/select2_locale_pt-BR.js | 15 - .../select2/select2_locale_pt-PT.js | 15 - .../components/select2/select2_locale_ro.js | 15 - .../components/select2/select2_locale_rs.js | 17 - .../components/select2/select2_locale_ru.js | 21 - .../components/select2/select2_locale_sk.js | 48 - .../components/select2/select2_locale_sv.js | 17 - .../components/select2/select2_locale_th.js | 17 - .../components/select2/select2_locale_tr.js | 17 - .../components/select2/select2_locale_uk.js | 23 - .../components/select2/select2_locale_vi.js | 18 - .../select2/select2_locale_zh-CN.js | 14 - .../select2/select2_locale_zh-TW.js | 14 - .../assets/components/select2/select2x2.png | Bin 845 -> 0 bytes .../assets/components/summernote/.bower.json | 19 +- .../components/summernote/CONTRIBUTING.md | 10 +- .../assets/components/summernote/Gruntfile.js | 170 - .../assets/components/summernote/History.md | 79 - vendor/assets/components/summernote/README.md | 89 +- .../assets/components/summernote/bower.json | 9 +- .../summernote/dist/lang/summernote-ar-AR.js | 104 + .../dist/lang/summernote-ar-AR.min.js | 2 + .../summernote/dist/lang/summernote-bg-BG.js | 99 + .../dist/lang/summernote-bg-BG.min.js | 2 + .../summernote/dist/lang/summernote-ca-ES.js | 147 + .../dist/lang/summernote-ca-ES.min.js | 2 + .../summernote/dist/lang/summernote-cs-CZ.js | 103 + .../dist/lang/summernote-cs-CZ.min.js | 2 + .../summernote/dist/lang/summernote-da-DK.js | 114 + .../dist/lang/summernote-da-DK.min.js | 2 + .../summernote/dist/lang/summernote-de-DE.js | 110 + .../dist/lang/summernote-de-DE.min.js | 2 + .../summernote/dist/lang/summernote-es-ES.js | 147 + .../dist/lang/summernote-es-ES.min.js | 2 + .../summernote/dist/lang/summernote-es-EU.js | 103 + .../dist/lang/summernote-es-EU.min.js | 2 + .../summernote/dist/lang/summernote-fa-IR.js | 105 + .../dist/lang/summernote-fa-IR.min.js | 2 + .../summernote/dist/lang/summernote-fi-FI.js | 104 + .../dist/lang/summernote-fi-FI.min.js | 2 + .../summernote/dist/lang/summernote-fr-FR.js | 115 + .../dist/lang/summernote-fr-FR.min.js | 2 + .../summernote/dist/lang/summernote-he-IL.js | 107 + .../dist/lang/summernote-he-IL.min.js | 2 + .../summernote/dist/lang/summernote-hu-HU.js | 105 + .../dist/lang/summernote-hu-HU.min.js | 2 + .../summernote/dist/lang/summernote-id-ID.js | 103 + .../dist/lang/summernote-id-ID.min.js | 2 + .../summernote/dist/lang/summernote-it-IT.js | 104 + .../dist/lang/summernote-it-IT.min.js | 2 + .../summernote/dist/lang/summernote-ja-JP.js | 104 + .../dist/lang/summernote-ja-JP.min.js | 2 + .../summernote/dist/lang/summernote-ko-KR.js | 115 + .../dist/lang/summernote-ko-KR.min.js | 2 + .../summernote/dist/lang/summernote-lt-LT.js | 107 + .../dist/lang/summernote-lt-LT.min.js | 2 + .../summernote/dist/lang/summernote-nb-NO.js | 104 + .../dist/lang/summernote-nb-NO.min.js | 2 + .../summernote/dist/lang/summernote-nl-NL.js | 104 + .../dist/lang/summernote-nl-NL.min.js | 2 + .../summernote/dist/lang/summernote-pl-PL.js | 112 + .../dist/lang/summernote-pl-PL.min.js | 2 + .../summernote/dist/lang/summernote-pt-BR.js | 103 + .../dist/lang/summernote-pt-BR.min.js | 2 + .../summernote/dist/lang/summernote-pt-PT.js | 96 + .../dist/lang/summernote-pt-PT.min.js | 2 + .../summernote/dist/lang/summernote-ro-RO.js | 102 + .../dist/lang/summernote-ro-RO.min.js | 2 + .../summernote/dist/lang/summernote-ru-RU.js | 112 + .../dist/lang/summernote-ru-RU.min.js | 2 + .../summernote/dist/lang/summernote-sk-SK.js | 102 + .../dist/lang/summernote-sk-SK.min.js | 2 + .../summernote/dist/lang/summernote-sl-SI.js | 106 + .../dist/lang/summernote-sl-SI.min.js | 2 + .../dist/lang/summernote-sr-RS-Latin.js | 104 + .../dist/lang/summernote-sr-RS-Latin.min.js | 2 + .../summernote/dist/lang/summernote-sr-RS.js | 104 + .../dist/lang/summernote-sr-RS.min.js | 2 + .../summernote/dist/lang/summernote-sv-SE.js | 104 + .../dist/lang/summernote-sv-SE.min.js | 2 + .../summernote/dist/lang/summernote-th-TH.js | 106 + .../dist/lang/summernote-th-TH.min.js | 2 + .../summernote/dist/lang/summernote-tr-TR.js | 111 + .../dist/lang/summernote-tr-TR.min.js | 2 + .../summernote/dist/lang/summernote-uk-UA.js | 111 + .../dist/lang/summernote-uk-UA.min.js | 2 + .../summernote/dist/lang/summernote-vi-VN.js | 104 + .../dist/lang/summernote-vi-VN.min.js | 2 + .../summernote/dist/lang/summernote-zh-CN.js | 113 + .../dist/lang/summernote-zh-CN.min.js | 2 + .../summernote/dist/lang/summernote-zh-TW.js | 113 + .../dist/lang/summernote-zh-TW.min.js | 2 + .../summernote/dist/summernote-bs2.css | 5268 - .../summernote/dist/summernote-bs3.css | 6057 - .../components/summernote/dist/summernote.css | 2 +- .../components/summernote/dist/summernote.js | 7341 +- .../summernote/dist/summernote.min.js | 7 +- .../summernote/examples/airmode.html | 18 +- .../components/summernote/examples/bs2.html | 28 - .../summernote/examples/bs2fa4.html | 28 - .../summernote/examples/bs301fa4.html | 50 - .../summernote/examples/bs3fa4.html | 38 + .../summernote/examples/codemirror.html | 29 +- .../summernote/examples/external-api.html | 431 +- .../summernote/examples/get-button.html | 2 +- .../summernote/examples/hint-emoji.html | 62 + .../summernote/examples/hint-userdefine.html | 48 + .../components/summernote/examples/index.html | 58 - .../examples/jquery-custom-event.html | 27 +- .../components/summernote/examples/lang.html | 2 +- .../summernote/examples/nativestyle.html | 2 +- .../examples/ondialog-multitab.html | 54 +- .../summernote/examples/ondialog.html | 6 +- .../summernote/examples/plugin-fontstyle.html | 48 - .../summernote/examples/plugin-hello.html | 23 +- .../summernote/examples/plugin-video.html | 50 - .../components/summernote/examples/rtl.html | 2 +- .../summernote/examples/textarea.html | 16 +- .../summernote/grunts/grunt-build.js | 78 - vendor/assets/components/summernote/ie8.html | 23 + .../assets/components/summernote/index.html | 49 +- .../summernote/lang/summernote-ar-AR.js | 7 + .../summernote/lang/summernote-bg-BG.js | 99 + .../summernote/lang/summernote-ca-ES.js | 66 +- .../summernote/lang/summernote-cs-CZ.js | 7 + .../summernote/lang/summernote-da-DK.js | 7 + .../summernote/lang/summernote-de-DE.js | 13 + .../summernote/lang/summernote-es-ES.js | 57 +- .../summernote/lang/summernote-es-EU.js | 7 + .../summernote/lang/summernote-fa-IR.js | 7 + .../summernote/lang/summernote-fi-FI.js | 7 + .../summernote/lang/summernote-fr-FR.js | 23 +- .../summernote/lang/summernote-he-IL.js | 7 + .../summernote/lang/summernote-hu-HU.js | 7 + .../summernote/lang/summernote-id-ID.js | 7 + .../summernote/lang/summernote-it-IT.js | 7 + .../summernote/lang/summernote-ja-JP.js | 7 + .../summernote/lang/summernote-ko-KR.js | 17 +- .../summernote/lang/summernote-lt-LT.js | 107 + .../summernote/lang/summernote-nb-NO.js | 7 + .../summernote/lang/summernote-nl-NL.js | 7 + .../summernote/lang/summernote-pl-PL.js | 7 + .../summernote/lang/summernote-pt-BR.js | 7 + .../summernote/lang/summernote-pt-PT.js | 96 + .../summernote/lang/summernote-ro-RO.js | 7 + .../summernote/lang/summernote-ru-RU.js | 12 + .../summernote/lang/summernote-sk-SK.js | 7 + .../summernote/lang/summernote-sl-SI.js | 7 + .../summernote/lang/summernote-sr-RS-Latin.js | 7 + .../summernote/lang/summernote-sr-RS.js | 7 + .../summernote/lang/summernote-sv-SE.js | 7 + .../summernote/lang/summernote-th-TH.js | 15 +- .../summernote/lang/summernote-tr-TR.js | 11 +- .../summernote/lang/summernote-uk-UA.js | 12 + .../summernote/lang/summernote-vi-VN.js | 7 + .../summernote/lang/summernote-zh-CN.js | 35 +- .../summernote/lang/summernote-zh-TW.js | 24 +- vendor/assets/components/summernote/lite.html | 14 + .../assets/components/summernote/package.json | 43 +- .../plugin/hello/summernote-ext-hello.js | 82 + .../summernote-ext-specialchars.js | 316 + .../summernote/plugin/summernote-ext-hello.js | 89 - .../summernote/plugin/summernote-ext-video.js | 573 - .../summernote/src/js/EventHandler.js | 509 - .../components/summernote/src/js/Renderer.js | 984 - .../components/summernote/src/js/app.js | 70 +- .../summernote/src/js/base/core/agent.js | 64 + .../src/js/{ => base}/core/async.js | 18 +- .../summernote/src/js/{ => base}/core/dom.js | 175 +- .../summernote/src/js/{ => base}/core/func.js | 9 +- .../summernote/src/js/{ => base}/core/key.js | 32 +- .../summernote/src/js/{ => base}/core/list.js | 25 +- .../src/js/{ => base}/core/range.js | 107 +- .../src/js/{ => base}/editing/Bullet.js | 12 +- .../src/js/{ => base}/editing/History.js | 51 +- .../src/js/{ => base}/editing/Style.js | 61 +- .../src/js/{ => base}/editing/Table.js | 13 +- .../src/js/{ => base}/editing/Typing.js | 24 +- .../summernote/src/js/base/module/AutoLink.js | 66 + .../summernote/src/js/base/module/AutoSync.js | 22 + .../src/js/base/module/Clipboard.js | 106 + .../src/js/{ => base}/module/Codeview.js | 76 +- .../module/Dropzone.js} | 71 +- .../summernote/src/js/base/module/Editor.js | 764 + .../src/js/{ => base}/module/Fullscreen.js | 27 +- .../summernote/src/js/base/module/Handle.js | 117 + .../src/js/base/module/Placeholder.js | 38 + .../src/js/base/module/Statusbar.js | 40 + .../summernote/src/js/base/renderer.js | 61 + .../src/js/base/summernote-en-US.js | 149 + .../src/js/bs3/module/AirPopover.js | 75 + .../summernote/src/js/bs3/module/Buttons.js | 683 + .../src/js/bs3/module/HelpDialog.js | 80 + .../src/js/bs3/module/HintPopover.js | 237 + .../src/js/bs3/module/ImageDialog.js | 124 + .../src/js/bs3/module/ImagePopover.js | 47 + .../src/js/bs3/module/LinkDialog.js | 143 + .../src/js/bs3/module/LinkPopover.js | 72 + .../summernote/src/js/bs3/module/Toolbar.js | 68 + .../src/js/bs3/module/VideoDialog.js | 184 + .../summernote/src/js/bs3/settings.js | 267 + .../components/summernote/src/js/bs3/ui.js | 202 + .../summernote/src/js/core/agent.js | 110 - .../components/summernote/src/js/defaults.js | 345 - .../components/summernote/src/js/intro.js | 7 +- .../summernote/src/js/lite/module/Toolbar.js | 25 + .../summernote/src/js/lite/settings.js | 100 + .../components/summernote/src/js/lite/ui.js | 34 + .../summernote/src/js/module/Button.js | 169 - .../summernote/src/js/module/Clipboard.js | 86 - .../summernote/src/js/module/Editor.js | 752 - .../summernote/src/js/module/Handle.js | 101 - .../summernote/src/js/module/HelpDialog.js | 35 - .../summernote/src/js/module/ImageDialog.js | 110 - .../summernote/src/js/module/LinkDialog.js | 128 - .../summernote/src/js/module/Popover.js | 120 - .../summernote/src/js/module/Statusbar.js | 44 - .../summernote/src/js/module/Toolbar.js | 99 - .../summernote/src/js/summernote.js | 495 +- .../summernote/src/less/elements.scss | 156 + .../summernote/src/less/summernote-lite.less | 56 + .../summernote/src/less/summernote.less | 275 +- .../src/{sass => less}/summernote.scss | 300 +- vendor/assets/fonts/OpenSans-Bold.ttf | Bin 0 -> 224592 bytes vendor/assets/fonts/OpenSans-BoldItalic.ttf | Bin 0 -> 213292 bytes vendor/assets/fonts/OpenSans-Italic.ttf | Bin 0 -> 212896 bytes vendor/assets/fonts/OpenSans-Regular.ttf | Bin 0 -> 217360 bytes vendor/assets/javascripts/devise-modal.js | 12 +- vendor/assets/javascripts/dirDisqus.js | 38 +- 1766 files changed, 401929 insertions(+), 42596 deletions(-) create mode 100644 .dockerignore create mode 100644 CONTRIBUTING.md create mode 100644 Dockerfile rename LICENSE => LICENSE.md (95%) create mode 100644 Vagrantfile delete mode 100644 app/assets/images/about-fablab.jpg create mode 100644 app/assets/images/fablab-logo.png create mode 100644 app/assets/images/la_casemate-logo.png create mode 100644 app/assets/images/mastercard.png create mode 100644 app/assets/images/powered_by_stripe.png delete mode 100644 app/assets/images/select2/select2-spinner.gif delete mode 100644 app/assets/images/select2/select2.png delete mode 100644 app/assets/images/select2/select2x2.png create mode 100644 app/assets/images/visa.png create mode 100644 app/assets/javascripts/controllers/about.coffee rename public/favicon.ico => app/assets/javascripts/controllers/admin/admins.coffee (100%) create mode 100644 app/assets/javascripts/controllers/admin/authentications.coffee create mode 100644 app/assets/javascripts/controllers/admin/calendar.coffee.erb create mode 100644 app/assets/javascripts/controllers/admin/graphs.coffee create mode 100644 app/assets/javascripts/controllers/admin/groups.coffee.erb create mode 100644 app/assets/javascripts/controllers/admin/invoices.coffee.erb delete mode 100644 app/assets/javascripts/controllers/admin/members.coffee create mode 100644 app/assets/javascripts/controllers/admin/members.coffee.erb create mode 100644 app/assets/javascripts/controllers/admin/plans.coffee.erb create mode 100644 app/assets/javascripts/controllers/admin/pricing.coffee.erb create mode 100644 app/assets/javascripts/controllers/admin/settings.coffee create mode 100644 app/assets/javascripts/controllers/admin/statistics.coffee create mode 100644 app/assets/javascripts/controllers/admin/tags.coffee.erb create mode 100644 app/assets/javascripts/controllers/admin/trainings.coffee.erb create mode 100644 app/assets/javascripts/controllers/plans.coffee.erb create mode 100644 app/assets/javascripts/controllers/profile.coffee rename app/assets/javascripts/controllers/{projects.coffee => projects.coffee.erb} (64%) create mode 100644 app/assets/javascripts/controllers/trainings.coffee.erb create mode 100644 app/assets/javascripts/directives/confirmation_needed.coffee create mode 100644 app/assets/javascripts/directives/stripe-angular.js create mode 100644 app/assets/javascripts/directives/validators.coffee create mode 100644 app/assets/javascripts/services/_t.coffee create mode 100644 app/assets/javascripts/services/abuse.coffee create mode 100644 app/assets/javascripts/services/admin.coffee create mode 100644 app/assets/javascripts/services/authProvider.coffee create mode 100644 app/assets/javascripts/services/availability.coffee create mode 100644 app/assets/javascripts/services/credit.coffee create mode 100644 app/assets/javascripts/services/customAsset.coffee create mode 100644 app/assets/javascripts/services/elastic.js.erb create mode 100644 app/assets/javascripts/services/invoice.coffee create mode 100644 app/assets/javascripts/services/plan.coffee create mode 100644 app/assets/javascripts/services/price.coffee create mode 100644 app/assets/javascripts/services/pricing.coffee create mode 100644 app/assets/javascripts/services/reservation.coffee create mode 100644 app/assets/javascripts/services/setting.coffee create mode 100644 app/assets/javascripts/services/slot.coffee create mode 100644 app/assets/javascripts/services/statistics.coffee create mode 100644 app/assets/javascripts/services/subscription.coffee create mode 100644 app/assets/javascripts/services/tag.coffee create mode 100644 app/assets/javascripts/services/training.coffee create mode 100644 app/assets/javascripts/services/trainings_pricing.coffee create mode 100644 app/assets/javascripts/services/translations.coffee create mode 100644 app/assets/javascripts/services/user.coffee create mode 100644 app/assets/stylesheets/modules/invoice.scss create mode 100644 app/assets/templates/admin/admins/new.html.erb create mode 100644 app/assets/templates/admin/authentications/_form.html.erb create mode 100644 app/assets/templates/admin/authentications/_oauth2.html.erb create mode 100644 app/assets/templates/admin/authentications/_oauth2_mapping.html.erb create mode 100644 app/assets/templates/admin/authentications/edit.html.erb create mode 100644 app/assets/templates/admin/authentications/index.html.erb create mode 100644 app/assets/templates/admin/authentications/new.html.erb create mode 100644 app/assets/templates/admin/calendar/calendar.html.erb create mode 100644 app/assets/templates/admin/calendar/eventModal.html.erb create mode 100644 app/assets/templates/admin/events/reservations.html.erb create mode 100644 app/assets/templates/admin/groups/index.html.erb create mode 100644 app/assets/templates/admin/invoices/avoirModal.html.erb create mode 100644 app/assets/templates/admin/invoices/index.html.erb create mode 100644 app/assets/templates/admin/plans/_form.html.erb create mode 100644 app/assets/templates/admin/plans/edit.html.erb create mode 100644 app/assets/templates/admin/plans/new.html.erb create mode 100644 app/assets/templates/admin/pricing/index.html.erb create mode 100644 app/assets/templates/admin/settings/index.html create mode 100644 app/assets/templates/admin/statistics/graphs.html.erb create mode 100644 app/assets/templates/admin/statistics/index.html.erb create mode 100644 app/assets/templates/admin/subscriptions/create_modal.html.erb create mode 100644 app/assets/templates/admin/subscriptions/expired_at_modal.html.erb create mode 100644 app/assets/templates/admin/tags/index.html.erb create mode 100644 app/assets/templates/admin/trainings/index.html.erb create mode 100644 app/assets/templates/admin/trainings/modal_edit.html.erb create mode 100644 app/assets/templates/admin/trainings/validTrainingModal.html.erb create mode 100644 app/assets/templates/dashboard/events.html.erb create mode 100644 app/assets/templates/dashboard/invoices.html.erb create mode 100644 app/assets/templates/dashboard/trainings.html.erb create mode 100644 app/assets/templates/events/modify_event_reservation_modal.html.erb create mode 100644 app/assets/templates/machines/request_training_modal.html.erb create mode 100644 app/assets/templates/machines/reserve.html.erb create mode 100644 app/assets/templates/machines/training_reservation_modal.html.erb create mode 100644 app/assets/templates/plans/_plan.html.erb create mode 100644 app/assets/templates/plans/index.html.erb create mode 100644 app/assets/templates/plans/payment_modal.html.erb create mode 100644 app/assets/templates/profile/_token.html.erb create mode 100644 app/assets/templates/profile/complete.html.erb create mode 100644 app/assets/templates/shared/_admin_form.html create mode 100644 app/assets/templates/shared/_partner_new_modal.html create mode 100644 app/assets/templates/shared/confirm_modify_slot_modal.html.erb create mode 100644 app/assets/templates/shared/signalAbuseModal.html.erb create mode 100644 app/assets/templates/shared/valid_reservation_modal.html.erb create mode 100644 app/assets/templates/stripe/payment_modal.html.erb create mode 100644 app/assets/templates/trainings/reserve.html.erb create mode 100644 app/controllers/api/abuses_controller.rb create mode 100644 app/controllers/api/admins_controller.rb create mode 100644 app/controllers/api/auth_providers_controller.rb create mode 100644 app/controllers/api/availabilities_controller.rb create mode 100644 app/controllers/api/credits_controller.rb create mode 100644 app/controllers/api/custom_assets_controller.rb create mode 100644 app/controllers/api/invoices_controller.rb create mode 100644 app/controllers/api/plans_controller.rb create mode 100644 app/controllers/api/prices_controller.rb create mode 100644 app/controllers/api/pricing_controller.rb create mode 100644 app/controllers/api/reservations_controller.rb create mode 100644 app/controllers/api/settings_controller.rb create mode 100644 app/controllers/api/slots_controller.rb create mode 100644 app/controllers/api/statistics_controller.rb create mode 100644 app/controllers/api/stylesheets_controller.rb create mode 100644 app/controllers/api/subscriptions_controller.rb create mode 100644 app/controllers/api/tags_controller.rb create mode 100644 app/controllers/api/trainings_controller.rb create mode 100644 app/controllers/api/trainings_pricings_controller.rb create mode 100644 app/controllers/api/translations_controller.rb create mode 100644 app/controllers/api/users_controller.rb create mode 100644 app/controllers/concerns/fablab_configuration.rb create mode 100644 app/controllers/users/omniauth_callbacks_controller.rb create mode 100644 app/exceptions/duplicate_index_error.rb create mode 100644 app/flow_workers/members_flow_worker.rb create mode 100644 app/models/abuse.rb create mode 100644 app/models/auth_provider.rb create mode 100644 app/models/availability_tag.rb create mode 100644 app/models/avoir.rb create mode 100644 app/models/concerns/stat_concern.rb create mode 100644 app/models/concerns/stat_reservation_concern.rb create mode 100644 app/models/credit.rb create mode 100644 app/models/custom_asset.rb create mode 100644 app/models/custom_asset_file.rb create mode 100644 app/models/database_provider.rb create mode 100644 app/models/invoice.rb create mode 100644 app/models/invoice_item.rb create mode 100644 app/models/machines_availability.rb create mode 100644 app/models/o_auth2_mapping.rb create mode 100644 app/models/o_auth2_provider.rb create mode 100644 app/models/offer_day.rb create mode 100644 app/models/partner_plan.rb create mode 100644 app/models/plan.rb create mode 100644 app/models/plan_file.rb create mode 100644 app/models/plan_image.rb create mode 100644 app/models/price.rb create mode 100644 app/models/reservation.rb create mode 100644 app/models/setting.rb create mode 100644 app/models/slot.rb create mode 100644 app/models/statistic_field.rb create mode 100644 app/models/statistic_graph.rb create mode 100644 app/models/statistic_index.rb create mode 100644 app/models/statistic_sub_type.rb create mode 100644 app/models/statistic_type.rb create mode 100644 app/models/statistic_type_sub_type.rb create mode 100644 app/models/stats/account.rb create mode 100644 app/models/stats/event.rb create mode 100644 app/models/stats/machine.rb create mode 100644 app/models/stats/project.rb create mode 100644 app/models/stats/subscription.rb create mode 100644 app/models/stats/training.rb create mode 100644 app/models/stats/user.rb create mode 100644 app/models/stylesheet.rb create mode 100644 app/models/subscription.rb create mode 100644 app/models/tag.rb create mode 100644 app/models/training.rb create mode 100644 app/models/trainings_availability.rb create mode 100644 app/models/trainings_pricing.rb create mode 100644 app/models/user_tag.rb create mode 100644 app/models/user_training.rb create mode 100644 app/models/users_credit.rb create mode 100644 app/pdfs/pdf/invoice.rb create mode 100644 app/policies/admin_policy.rb create mode 100644 app/policies/auth_provider_policy.rb create mode 100644 app/policies/avoir_policy.rb create mode 100644 app/policies/credit_policy.rb create mode 100644 app/policies/custom_asset_policy.rb create mode 100644 app/policies/group_policy.rb create mode 100644 app/policies/invoice_policy.rb create mode 100644 app/policies/partner_plan_policy.rb create mode 100644 app/policies/plan_policy.rb create mode 100644 app/policies/price_policy.rb create mode 100644 app/policies/pricing_policy.rb create mode 100644 app/policies/reservation_policy.rb create mode 100644 app/policies/setting_policy.rb create mode 100644 app/policies/slot_policy.rb create mode 100644 app/policies/statistic_policy.rb create mode 100644 app/policies/subscription_policy.rb create mode 100644 app/policies/tag_policy.rb create mode 100644 app/policies/training_policy.rb create mode 100644 app/services/statistic_service.rb create mode 100644 app/sweepers/stylesheet_sweeper.rb create mode 100644 app/uploaders/custom_assets_uploader.rb create mode 100644 app/uploaders/event_image_uploader.rb create mode 100644 app/uploaders/plan_file_uploader.rb create mode 100644 app/uploaders/plan_image_uploader.rb create mode 100644 app/views/api/abuses/create.json.jbuilder create mode 100644 app/views/api/admins/_admin.json.jbuilder create mode 100644 app/views/api/admins/create.json.jbuilder create mode 100644 app/views/api/admins/index.json.jbuilder create mode 100644 app/views/api/auth_providers/_auth_provider.json.jbuilder create mode 100644 app/views/api/auth_providers/active.json.jbuilder create mode 100644 app/views/api/auth_providers/index.json.jbuilder create mode 100644 app/views/api/auth_providers/mapping_fields.json.jbuilder create mode 100644 app/views/api/auth_providers/show.json.jbuilder create mode 100644 app/views/api/availabilities/index.json.jbuilder create mode 100644 app/views/api/availabilities/machine.json.jbuilder create mode 100644 app/views/api/availabilities/reservations.json.jbuilder create mode 100644 app/views/api/availabilities/show.json.jbuilder create mode 100644 app/views/api/availabilities/trainings.json.jbuilder create mode 100644 app/views/api/credits/index.json.jbuilder create mode 100644 app/views/api/credits/show.json.jbuilder create mode 100644 app/views/api/custom_assets/show.json.jbuilder create mode 100644 app/views/api/groups/_group.json.jbuilder create mode 100644 app/views/api/groups/create.json.jbuilder create mode 100644 app/views/api/groups/update.json.jbuilder create mode 100644 app/views/api/invoices/avoir.json.jbuilder create mode 100644 app/views/api/invoices/index.json.jbuilder create mode 100644 app/views/api/invoices/show.json.jbuilder create mode 100644 app/views/api/members/export_reservations.xls.erb create mode 100644 app/views/api/members/export_subscriptions.xls.erb create mode 100644 app/views/api/notifications/_notify_admin_abuse_reported.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_invoicing_changed.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_member_create_reservation.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_profile_complete.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_slot_is_canceled.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_slot_is_modified.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_subscribed_plan.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_subscription_canceled.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_subscription_extended.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_subscription_is_expired.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_subscription_will_expire_in_7_days.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_user_group_changed.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_user_merged.json.jbuilder create mode 100644 app/views/api/notifications/_notify_admin_when_user_is_imported.json.jbuilder create mode 100644 app/views/api/notifications/_notify_member_create_reservation.json.jbuilder create mode 100644 app/views/api/notifications/_notify_member_slot_is_canceled.json.jbuilder create mode 100644 app/views/api/notifications/_notify_member_slot_is_modified.json.jbuilder create mode 100644 app/views/api/notifications/_notify_member_subscribed_plan.json.jbuilder create mode 100644 app/views/api/notifications/_notify_member_subscribed_plan_is_changed.json.jbuilder create mode 100644 app/views/api/notifications/_notify_member_subscription_canceled.json.jbuilder create mode 100644 app/views/api/notifications/_notify_member_subscription_extended.json.jbuilder create mode 100644 app/views/api/notifications/_notify_member_subscription_is_expired.json.jbuilder create mode 100644 app/views/api/notifications/_notify_member_subscription_will_expire_in_7_days.json.jbuilder create mode 100644 app/views/api/notifications/_notify_partner_subscribed_plan.json.jbuilder create mode 100644 app/views/api/notifications/_notify_user_auth_migration.json.jbuilder create mode 100644 app/views/api/notifications/_notify_user_profile_complete.json.jbuilder create mode 100644 app/views/api/notifications/_notify_user_training_valid.json.jbuilder create mode 100644 app/views/api/notifications/_notify_user_user_group_changed.json.jbuilder create mode 100644 app/views/api/notifications/_notify_user_when_avoir_ready.json.jbuilder create mode 100644 app/views/api/notifications/_notify_user_when_invoice_ready.json.jbuilder create mode 100644 app/views/api/notifications/_undefined_notification.json.jbuilder create mode 100644 app/views/api/plans/_plan.json.jbuilder create mode 100644 app/views/api/plans/index.json.jbuilder create mode 100644 app/views/api/plans/shallow_index.json.jbuilder create mode 100644 app/views/api/plans/show.json.jbuilder create mode 100644 app/views/api/prices/_price.json.jbuilder create mode 100644 app/views/api/prices/compute.json.jbuilder create mode 100644 app/views/api/prices/index.json.jbuilder create mode 100644 app/views/api/prices/update.json.jbuilder create mode 100644 app/views/api/pricing/index.json.jbuilder create mode 100644 app/views/api/reservations/_reservation.json.jbuilder create mode 100644 app/views/api/reservations/index.json.jbuilder create mode 100644 app/views/api/reservations/show.json.jbuilder create mode 100644 app/views/api/settings/_setting.json.jbuilder create mode 100644 app/views/api/settings/index.json.jbuilder create mode 100644 app/views/api/settings/show.json.jbuilder create mode 100644 app/views/api/settings/update.json.jbuilder create mode 100644 app/views/api/shared/_plan.json.jbuilder create mode 100644 app/views/api/slots/cancel.json.jbuilder create mode 100644 app/views/api/slots/show.json.jbuilder create mode 100644 app/views/api/statistics/index.json.jbuilder create mode 100644 app/views/api/subscriptions/_subscription.json.jbuilder create mode 100644 app/views/api/subscriptions/show.json.jbuilder create mode 100644 app/views/api/subscriptions/update.json.jbuilder create mode 100644 app/views/api/tags/index.json.jbuilder create mode 100644 app/views/api/tags/show.json.jbuilder create mode 100644 app/views/api/trainings/index.json.jbuilder create mode 100644 app/views/api/trainings/show.json.jbuilder create mode 100644 app/views/api/trainings_pricings/_trainings_pricing.json.jbuilder create mode 100644 app/views/api/trainings_pricings/index.json.jbuilder create mode 100644 app/views/api/trainings_pricings/update.json.jbuilder create mode 100644 app/views/api/users/create.json.jbuilder create mode 100644 app/views/api/users/index.json.jbuilder delete mode 100644 app/views/devise/registrations/new.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_abuse_reported.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_invoicing_changed.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_member_create_reservation.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_profile_complete.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_slot_is_canceled.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_slot_is_modified.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_subscribed_plan.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_subscription_canceled.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_subscription_extended.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_subscription_is_expired.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_subscription_will_expire_in_7_days.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_user_group_changed.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_user_merged.html.erb create mode 100644 app/views/notifications_mailer/notify_admin_when_user_is_imported.html.erb create mode 100644 app/views/notifications_mailer/notify_member_avoir_ready.html.erb create mode 100644 app/views/notifications_mailer/notify_member_create_reservation.html.erb create mode 100644 app/views/notifications_mailer/notify_member_invoice_ready.html.erb create mode 100644 app/views/notifications_mailer/notify_member_slot_is_canceled.html.erb create mode 100644 app/views/notifications_mailer/notify_member_slot_is_modified.html.erb create mode 100644 app/views/notifications_mailer/notify_member_subscribed_plan.html.erb create mode 100644 app/views/notifications_mailer/notify_member_subscribed_plan_is_changed.html.erb create mode 100644 app/views/notifications_mailer/notify_member_subscription_canceled.html.erb create mode 100644 app/views/notifications_mailer/notify_member_subscription_extended.html.erb create mode 100644 app/views/notifications_mailer/notify_member_subscription_is_expired.html.erb create mode 100644 app/views/notifications_mailer/notify_member_subscription_will_expire_in_7_days.html.erb create mode 100644 app/views/notifications_mailer/notify_partner_subscribed_plan.html.erb create mode 100644 app/views/notifications_mailer/notify_user_auth_migration.html.erb create mode 100644 app/views/notifications_mailer/notify_user_profile_complete.html.erb create mode 100644 app/views/notifications_mailer/notify_user_training_valid.html.erb create mode 100644 app/views/notifications_mailer/notify_user_user_group_changed.html.erb create mode 100644 app/views/notifications_mailer/shared/_hello.html.erb delete mode 100644 app/views/users_mailer/notify_member_account_is_created.html.erb create mode 100644 app/views/users_mailer/notify_user_account_created.html.erb create mode 100644 app/workers/indexer_worker.rb create mode 100644 app/workers/invoice_worker.rb create mode 100644 app/workers/statistic_worker.rb create mode 100644 app/workers/stripe_worker.rb create mode 100644 app/workers/subscription_expire_worker.rb delete mode 100755 bin/setup create mode 100644 config/application.yml.default delete mode 100644 config/disqus_api.yml create mode 100644 config/initializers/activerecord.rb create mode 100644 config/initializers/elasticsearch.rb create mode 100644 config/initializers/mail.rb delete mode 100644 config/initializers/mandrill.rb create mode 100644 config/initializers/postgresql_database_tasks.rb create mode 100644 config/initializers/stripe.rb create mode 100644 config/locales/app.admin.en.yml create mode 100644 config/locales/app.admin.fr.yml create mode 100644 config/locales/app.logged.en.yml create mode 100644 config/locales/app.logged.fr.yml create mode 100644 config/locales/app.public.en.yml create mode 100644 config/locales/app.public.fr.yml create mode 100644 config/locales/app.shared.en.yml create mode 100644 config/locales/app.shared.fr.yml create mode 100644 config/locales/mails.en.yml create mode 100644 config/locales/mails.fr.yml create mode 100644 config/locales/rails.en.yml create mode 100644 config/locales/rails.fr.yml create mode 100644 config/newrelic.yml create mode 100644 config/nginx_puma.conf create mode 100644 config/schedule.yml delete mode 100644 contrib/docker/Dockerfile delete mode 100644 contrib/docker/Makefile delete mode 100644 contrib/docker/README.md delete mode 100644 contrib/docker/fabmanager.conf delete mode 100644 contrib/docker/fabmanager.yml rename db/migrate/{20150429084753_devise_create_users.rb => 20140409083104_devise_create_users.rb} (80%) rename db/migrate/{20150429085252_rolify_create_roles.rb => 20140409083610_rolify_create_roles.rb} (100%) rename db/migrate/{20150429085948_create_profiles.rb => 20140409153915_create_profiles.rb} (82%) rename db/migrate/{20150429090704_create_projects.rb => 20140410101026_create_projects.rb} (52%) rename db/migrate/{20150429091737_create_machines.rb => 20140410140516_create_machines.rb} (67%) rename db/migrate/{20150429092109_create_assets.rb => 20140410162151_create_assets.rb} (78%) rename db/migrate/{20150429092624_create_projects_machines.rb => 20140411152729_create_projects_machines.rb} (51%) create mode 100644 db/migrate/20140414141134_add_is_allow_contact_to_user.rb create mode 100644 db/migrate/20140415104151_create_project_user.rb create mode 100644 db/migrate/20140415123625_add_author_id_to_project.rb rename db/migrate/{20150429093251_create_project_steps.rb => 20140416130838_create_project_steps.rb} (67%) rename db/migrate/{20150429093734_create_availabilities.rb => 20140422085949_create_availabilities.rb} (87%) create mode 100644 db/migrate/20140422090412_create_machines_availabilities.rb create mode 100644 db/migrate/20140513152025_add_title_to_project_step.rb create mode 100644 db/migrate/20140516083543_create_reservations.rb create mode 100644 db/migrate/20140516083909_create_slots.rb create mode 100644 db/migrate/20140516093335_add_reservable_to_reservation.rb rename db/migrate/{20150429094525_create_addresses.rb => 20140522115617_create_addresses.rb} (100%) rename db/migrate/{20150429095139_create_components.rb => 20140522175539_create_components.rb} (97%) rename db/migrate/{20150429095313_create_themes.rb => 20140522175714_create_themes.rb} (100%) rename db/migrate/{20150429095339_create_licences.rb => 20140522180032_create_licences.rb} (100%) rename db/migrate/{20150429095632_create_projects_components.rb => 20140522180930_create_projects_components.rb} (52%) rename db/migrate/{20150429100014_create_projects_themes.rb => 20140522181011_create_projects_themes.rb} (51%) create mode 100644 db/migrate/20140522181148_add_tags_to_project.rb rename db/migrate/{20150429100123_add_licence_id_to_projects.rb => 20140523083230_add_licence_id_to_project.rb} (53%) create mode 100644 db/migrate/20140526144327_add_state_to_project.rb rename db/migrate/{20150429100442_create_notifications.rb => 20140527092045_create_notifications.rb} (84%) create mode 100644 db/migrate/20140528134944_add_is_valid_to_project_user.rb create mode 100644 db/migrate/20140528140257_add_valid_token_to_project_user.rb create mode 100644 db/migrate/20140529145140_create_user_trainings.rb rename db/migrate/{20150429100750_create_groups.rb => 20140603084413_create_groups.rb} (100%) create mode 100644 db/migrate/20140603085817_create_plans.rb create mode 100644 db/migrate/20140603164215_create_trainings_pricings.rb rename db/migrate/{20150429100906_add_group_to_users.rb => 20140604094514_add_group_to_user.rb} (58%) create mode 100644 db/migrate/20140604113611_create_trainings.rb create mode 100644 db/migrate/20140604113919_create_trainings_machine.rb create mode 100644 db/migrate/20140604132045_create_trainings_availabilities.rb create mode 100644 db/migrate/20140605125131_add_availability_id_to_reservation.rb create mode 100644 db/migrate/20140605142133_change_machine_id_to_training_id_from_user_training.rb create mode 100644 db/migrate/20140605151442_change_machine_to_training_from_trainings_pricing.rb create mode 100644 db/migrate/20140606133116_create_machines_pricings.rb create mode 100644 db/migrate/20140609092700_remove_availability_id_form_reservations.rb create mode 100644 db/migrate/20140609092827_add_availability_to_slot.rb create mode 100644 db/migrate/20140610153123_add_stp_customer_id_to_users.rb create mode 100644 db/migrate/20140610170446_create_subscriptions.rb create mode 100644 db/migrate/20140613150651_add_stp_invoice_id_to_reservation.rb create mode 100644 db/migrate/20140620131525_add_start_at_to_subscription.rb rename db/migrate/{20150429101220_create_friendly_id_slugs.rb => 20140622121724_create_friendly_id_slugs.rb} (100%) create mode 100644 db/migrate/20140622122944_add_slug_to_projects.rb create mode 100644 db/migrate/20140622145648_add_strip_id_to_groups.rb create mode 100644 db/migrate/20140623023557_add_slug_to_machines.rb create mode 100644 db/migrate/20140624123359_create_credits.rb create mode 100644 db/migrate/20140624123814_create_users_credits.rb create mode 100644 db/migrate/20140624124338_add_training_credit_nb_to_plan.rb create mode 100644 db/migrate/20140703100457_change_start_at_to_expired_at_from_subscription.rb create mode 100644 db/migrate/20140703231208_add_username_to_user.rb create mode 100644 db/migrate/20140703233420_add_index_username_to_user.rb create mode 100644 db/migrate/20140703233942_remove_username_from_profiles.rb create mode 100644 db/migrate/20140703235739_add_slug_to_users.rb create mode 100644 db/migrate/20140710144142_create_events.rb rename db/migrate/{20150429102704_create_categories.rb => 20140710144427_create_categories.rb} (100%) rename db/migrate/{20150429102754_create_events_categories.rb => 20140710144610_create_events_categories.rb} (55%) create mode 100644 db/migrate/20140711084809_change_event.rb create mode 100644 db/migrate/20140715095503_add_nb_reserve_places_and_nb_reserve_reduced_places_to_reservation.rb create mode 100644 db/migrate/20140717143735_add_recurrence_id_to_event.rb create mode 100644 db/migrate/20140722162046_create_invoices.rb create mode 100644 db/migrate/20140722162309_create_invoice_items.rb create mode 100644 db/migrate/20140723075942_add_user_id_to_invoice.rb create mode 100644 db/migrate/20140723171547_remove_description_from_invoice_items.rb create mode 100644 db/migrate/20140723172610_add_invoiced_to_invoice_items.rb create mode 100644 db/migrate/20140724125605_remove_invoiced_from_invoice_items.rb create mode 100644 db/migrate/20140724131808_add_description_to_invoice_items.rb create mode 100644 db/migrate/20140724132655_add_subscription_id_to_invoice_items.rb create mode 100644 db/migrate/20140728110430_add_reference_to_invoice.rb create mode 100644 db/migrate/20141002111736_add_canceled_at_to_subscription.rb create mode 100644 db/migrate/20141110131407_add_to_cancel_to_subscription.rb create mode 100644 db/migrate/20141215142044_remove_to_cancel_from_subscription.rb create mode 100644 db/migrate/20141215153643_create_offer_days.rb create mode 100644 db/migrate/20141217141648_add_nb_total_places_to_trainings.rb create mode 100644 db/migrate/20141217172843_add_nb_total_places_to_availability.rb create mode 100644 db/migrate/20150107103903_add_slug_to_trainings.rb create mode 100644 db/migrate/20150108082541_add_published_at_to_projects.rb create mode 100644 db/migrate/20150112160349_create_statistic_indices.rb create mode 100644 db/migrate/20150112160405_create_statistic_types.rb create mode 100644 db/migrate/20150112160425_create_statistic_sub_types.rb create mode 100644 db/migrate/20150113112757_add_simple_to_statistic_sub_type.rb create mode 100644 db/migrate/20150114111132_remove_simple_from_statistic_sub_type.rb create mode 100644 db/migrate/20150114111243_add_simple_to_statistic_type.rb create mode 100644 db/migrate/20150114141926_create_statistic_fields.rb create mode 100644 db/migrate/20150114142032_remove_additional_field_from_statistic_index.rb create mode 100644 db/migrate/20150115143750_add_type_to_statistic_field.rb create mode 100644 db/migrate/20150119082931_rename_statistic_field_type_to_data_type.rb create mode 100644 db/migrate/20150119092557_remove_statistic_type_id_from_statistic_sub_type.rb create mode 100644 db/migrate/20150119093811_create_statistic_type_sub_types.rb create mode 100644 db/migrate/20150119160758_add_table_to_statistic_index.rb create mode 100644 db/migrate/20150119161004_create_statistic_graphs.rb create mode 100644 db/migrate/20150127101521_add_ca_to_statistic_index.rb create mode 100644 db/migrate/20150127155141_add_avoir_mode_to_invoice.rb create mode 100644 db/migrate/20150127161235_add_avoir_date_to_invoice.rb create mode 100644 db/migrate/20150127172510_add_invoice_id_to_invoice.rb create mode 100644 db/migrate/20150128132219_add_subscription_to_expire_to_invoices.rb create mode 100644 db/migrate/20150218154032_add_receiver_type_and_is_send_to_notification.rb create mode 100644 db/migrate/20150428075148_add_is_active_attribute_to_users.rb create mode 100644 db/migrate/20150428091057_create_settings.rb delete mode 100644 db/migrate/20150429092847_create_project_users.rb delete mode 100644 db/migrate/20150429102505_create_events.rb create mode 100644 db/migrate/20150506090921_add_description_to_trainings.rb create mode 100644 db/migrate/20150507075506_add_ex_start_at_to_slots.rb create mode 100644 db/migrate/20150507075620_add_ex_end_at_to_slots.rb create mode 100644 db/migrate/20150512123546_add_attributes_to_plan.rb create mode 100644 db/migrate/20150520132030_create_prices.rb create mode 100644 db/migrate/20150520133409_migrate_data_from_machines_pricings_to_prices.rb create mode 100644 db/migrate/20150526130729_add_base_name_to_plans.rb create mode 100644 db/migrate/20150527153312_add_canceled_at_to_slot.rb create mode 100644 db/migrate/20150529113555_add_default_for_training_nb_credit_to_plans.rb create mode 100644 db/migrate/20150601125944_change_stripe_id_to_short_name_from_groups.rb create mode 100644 db/migrate/20150603104502_change_short_name_to_slug_from_group.rb create mode 100644 db/migrate/20150603104658_add_unique_index_to_slug_from_group.rb create mode 100644 db/migrate/20150603133050_drop_machines_pricings.rb create mode 100644 db/migrate/20150604081757_add_ui_weight_to_plans.rb create mode 100644 db/migrate/20150604131525_add_meta_data_to_notifications.rb create mode 100644 db/migrate/20150608142234_add_invoicing_disabled_to_users.rb create mode 100644 db/migrate/20150609094336_add_interval_count_to_plans.rb create mode 100644 db/migrate/20150615135539_migrate_plan_stats.rb create mode 100644 db/migrate/20150617085623_create_custom_assets.rb create mode 100644 db/migrate/20150701090642_create_stylesheets.rb create mode 100644 db/migrate/20150702150754_create_tags.rb create mode 100644 db/migrate/20150702151009_create_user_tags.rb create mode 100644 db/migrate/20150706102547_create_availability_tags.rb create mode 100644 db/migrate/20150707135343_add_offered_to_slots.rb create mode 100644 db/migrate/20150713090542_change_amount_type_in_invoice_items.rb create mode 100644 db/migrate/20150713151115_add_invoice_item_id_to_invoice_item.rb create mode 100644 db/migrate/20150715135751_add_description_to_invoices.rb create mode 100644 db/migrate/20150915144448_create_auth_providers.rb create mode 100644 db/migrate/20150915144939_create_o_auth2_providers.rb create mode 100644 db/migrate/20150915152943_create_o_auth2_mappings.rb create mode 100644 db/migrate/20150916091131_change_auth_provider_to_polymorphic.rb create mode 100644 db/migrate/20150916093159_create_database_providers.rb create mode 100644 db/migrate/20150921135557_add_sso_id_to_user.rb create mode 100644 db/migrate/20150921135817_add_local_model_to_o_auth2_mapping.rb create mode 100644 db/migrate/20150922095921_migrate_endpoints_to_urls_from_o_auth2_providers.rb create mode 100644 db/migrate/20150922100528_remove_resource_url_from_o_auth2_mappings.rb create mode 100644 db/migrate/20150924093917_migrate_url_to_endpoints_from_o_auth2_providers.rb create mode 100644 db/migrate/20150924094138_add_resource_endpoint_to_o_auth2_mappings.rb create mode 100644 db/migrate/20150924094427_rename_resource_endpoint_from_o_auth2_mappings.rb create mode 100644 db/migrate/20150924141714_add_omniauth_to_users.rb create mode 100644 db/migrate/20151005133841_add_profile_url_to_o_auth2_providers.rb create mode 100644 db/migrate/20151008152219_add_auth_token_to_users.rb create mode 100644 db/migrate/20151105125623_create_abuses.rb create mode 100644 db/migrate/20151210113548_add_merged_at_to_users.rb create mode 100644 db/migrate/20160119131623_add_destroying_to_availability.rb create mode 100644 db/test_seeds.rb create mode 100644 doc/controllers_brief.svg create mode 100644 doc/controllers_complete.svg create mode 100644 doc/diagram.mwb create mode 100644 doc/diagram.mwb.bak create mode 100644 doc/diagram.png create mode 100644 doc/diagram_eer_fablab.pdf create mode 100644 doc/elasticsearch.md create mode 100644 doc/models_brief.svg create mode 100644 doc/models_complete.svg create mode 100644 doc/sso_authentication.md create mode 100644 docker/README.md create mode 100644 docker/database.yml create mode 100644 docker/env.example create mode 100644 docker/nginx.conf.example create mode 100644 docker/nginx_with_ssl.conf.example create mode 100644 docker/supervisor.conf create mode 100644 lib/assets/javascripts/fullcalendar/fullcalendar.js create mode 100644 lib/omni_auth/omni_auth.rb create mode 100644 lib/omni_auth/strategies/sso_oauth2_provider.rb create mode 100644 lib/tasks/fablab.rake delete mode 100644 public/Charte_FABLAB.pdf create mode 100644 public/about-fablab.jpg create mode 100644 spec/factories/abuses.rb create mode 100644 spec/factories/auth_providers.rb create mode 100644 spec/factories/availability_tags.rb create mode 100644 spec/factories/avoirs.rb create mode 100644 spec/factories/credits.rb rename spec/factories/{roles.rb => custom_assets.rb} (58%) create mode 100644 spec/factories/database_providers.rb create mode 100644 spec/factories/invoices.rb create mode 100644 spec/factories/o_auth2_mappings.rb create mode 100644 spec/factories/o_auth2_providers.rb create mode 100644 spec/factories/plans.rb create mode 100644 spec/factories/prices.rb create mode 100644 spec/factories/reservations.rb create mode 100644 spec/factories/stylesheets.rb create mode 100644 spec/factories/subscriptions.rb create mode 100644 spec/factories/tags.rb create mode 100644 spec/factories/trainings.rb create mode 100644 spec/factories/user_tags.rb create mode 100644 spec/mailers/.keep create mode 100644 spec/mailers/previews/devise_mailer_preview.rb create mode 100644 spec/mailers/previews/notifications_mailer_preview.rb create mode 100644 spec/mailers/previews/users_mailer_preview.rb create mode 100644 spec/models/abuse_spec.rb create mode 100644 spec/models/auth_provider_spec.rb create mode 100644 spec/models/availability_tag_spec.rb create mode 100644 spec/models/avoir_spec.rb create mode 100644 spec/models/credit_spec.rb create mode 100644 spec/models/custom_asset_spec.rb create mode 100644 spec/models/database_provider_spec.rb create mode 100644 spec/models/invoice_spec.rb create mode 100644 spec/models/o_auth2_mapping_spec.rb create mode 100644 spec/models/o_auth2_provider_spec.rb create mode 100644 spec/models/plan_spec.rb create mode 100644 spec/models/price_spec.rb create mode 100644 spec/models/reservation_spec.rb create mode 100644 spec/models/stylesheet_spec.rb create mode 100644 spec/models/subscription_spec.rb rename spec/models/{role_spec.rb => tag_spec.rb} (69%) create mode 100644 spec/models/training_spec.rb create mode 100644 spec/models/user_tag_spec.rb create mode 100644 test/controllers/.keep create mode 100644 test/fixtures/.keep create mode 100644 test/helpers/.keep create mode 100644 test/integration/.keep create mode 100644 test/models/.keep create mode 100644 test/test_helper.rb create mode 100644 vendor/assets/components/angular-animate/.bower.json create mode 100644 vendor/assets/components/angular-animate/README.md create mode 100644 vendor/assets/components/angular-animate/angular-animate.js create mode 100644 vendor/assets/components/angular-animate/angular-animate.min.js create mode 100644 vendor/assets/components/angular-animate/angular-animate.min.js.map create mode 100644 vendor/assets/components/angular-animate/bower.json create mode 100644 vendor/assets/components/angular-animate/index.js create mode 100644 vendor/assets/components/angular-animate/package.json create mode 100644 vendor/assets/components/angular-base64-upload/.bower.json create mode 100644 vendor/assets/components/angular-base64-upload/Gruntfile.js create mode 100644 vendor/assets/components/angular-base64-upload/README.md create mode 100644 vendor/assets/components/angular-base64-upload/banner.png create mode 100644 vendor/assets/components/angular-base64-upload/bower.json create mode 100644 vendor/assets/components/angular-base64-upload/demo/README.md create mode 100644 vendor/assets/components/angular-base64-upload/demo/index.html create mode 100644 vendor/assets/components/angular-base64-upload/demo/placeholder.png create mode 100644 vendor/assets/components/angular-base64-upload/demo/server.php create mode 100644 vendor/assets/components/angular-base64-upload/dist/angular-base64-upload.js create mode 100644 vendor/assets/components/angular-base64-upload/dist/angular-base64-upload.min.js create mode 100644 vendor/assets/components/angular-base64-upload/karma-unit.js create mode 100644 vendor/assets/components/angular-base64-upload/package.json create mode 100644 vendor/assets/components/angular-base64-upload/src/angular-base64-upload.js create mode 100644 vendor/assets/components/angular-bootstrap-.gitignore create mode 100644 vendor/assets/components/angular-bootstrap-.npmignore create mode 100644 vendor/assets/components/angular-bootstrap-README.md create mode 100644 vendor/assets/components/angular-bootstrap-index.js create mode 100644 vendor/assets/components/angular-bootstrap-package.json create mode 100644 vendor/assets/components/angular-bootstrap-switch/.bower.json create mode 100644 vendor/assets/components/angular-bootstrap-switch/.bowerrc create mode 100644 vendor/assets/components/angular-bootstrap-switch/.editorconfig create mode 100644 vendor/assets/components/angular-bootstrap-switch/.gitattributes rename vendor/assets/components/{angular-ui-select2 => angular-bootstrap-switch}/.gitignore (55%) create mode 100644 vendor/assets/components/angular-bootstrap-switch/.jshintrc create mode 100644 vendor/assets/components/angular-bootstrap-switch/.npmignore create mode 100644 vendor/assets/components/angular-bootstrap-switch/.travis.yml create mode 100644 vendor/assets/components/angular-bootstrap-switch/CHANGELOG.md create mode 100644 vendor/assets/components/angular-bootstrap-switch/CONTRIBUTING.md create mode 100644 vendor/assets/components/angular-bootstrap-switch/Gruntfile.js create mode 100644 vendor/assets/components/angular-bootstrap-switch/LICENSE create mode 100644 vendor/assets/components/angular-bootstrap-switch/README.md create mode 100644 vendor/assets/components/angular-bootstrap-switch/bower.json create mode 100644 vendor/assets/components/angular-bootstrap-switch/bsSwitch.prefix create mode 100644 vendor/assets/components/angular-bootstrap-switch/bsSwitch.suffix create mode 100644 vendor/assets/components/angular-bootstrap-switch/common/module.js create mode 100644 vendor/assets/components/angular-bootstrap-switch/dist/angular-bootstrap-switch.js create mode 100644 vendor/assets/components/angular-bootstrap-switch/dist/angular-bootstrap-switch.min.js create mode 100644 vendor/assets/components/angular-bootstrap-switch/example/index.html create mode 100644 vendor/assets/components/angular-bootstrap-switch/example/scripts/app.js create mode 100644 vendor/assets/components/angular-bootstrap-switch/example/scripts/controllers/main.js create mode 100644 vendor/assets/components/angular-bootstrap-switch/example/styles/main.css create mode 100644 vendor/assets/components/angular-bootstrap-switch/karma-chrome.conf.js create mode 100644 vendor/assets/components/angular-bootstrap-switch/karma.conf.js create mode 100644 vendor/assets/components/angular-bootstrap-switch/package.json create mode 100644 vendor/assets/components/angular-bootstrap-switch/src/directives/bsSwitch.js create mode 100644 vendor/assets/components/angular-bootstrap-switch/test/.jshintrc create mode 100644 vendor/assets/components/angular-bootstrap-switch/test/runner.html create mode 100644 vendor/assets/components/angular-bootstrap-switch/test/spec/directives/bsSwitchSpec.js create mode 100644 vendor/assets/components/angular-bootstrap-ui-bootstrap-csp.css create mode 100644 vendor/assets/components/angular-cookies/.bower.json create mode 100644 vendor/assets/components/angular-cookies/README.md create mode 100644 vendor/assets/components/angular-cookies/angular-cookies.js create mode 100644 vendor/assets/components/angular-cookies/angular-cookies.min.js create mode 100644 vendor/assets/components/angular-cookies/angular-cookies.min.js.map create mode 100644 vendor/assets/components/angular-cookies/bower.json create mode 100644 vendor/assets/components/angular-cookies/index.js create mode 100644 vendor/assets/components/angular-cookies/package.json create mode 100644 vendor/assets/components/angular-growl-v2/.bower.json rename vendor/assets/components/{angular-growl => angular-growl-v2}/LICENSE (100%) create mode 100644 vendor/assets/components/angular-growl-v2/bower.json create mode 100644 vendor/assets/components/angular-growl-v2/build/angular-growl.css create mode 100644 vendor/assets/components/angular-growl-v2/build/angular-growl.js create mode 100644 vendor/assets/components/angular-growl-v2/build/angular-growl.min.css create mode 100644 vendor/assets/components/angular-growl-v2/build/angular-growl.min.js delete mode 100644 vendor/assets/components/angular-growl/.bower.json delete mode 100644 vendor/assets/components/angular-growl/README.md delete mode 100644 vendor/assets/components/angular-growl/build/angular-growl.js delete mode 100644 vendor/assets/components/angular-growl/build/angular-growl.min.css delete mode 100644 vendor/assets/components/angular-growl/build/angular-growl.min.js delete mode 100644 vendor/assets/components/angular-growl/doc/screenshot.jpg create mode 100644 vendor/assets/components/angular-medium-editor/.bower.json create mode 100644 vendor/assets/components/angular-medium-editor/.editorconfig create mode 100644 vendor/assets/components/angular-medium-editor/.gitignore create mode 100644 vendor/assets/components/angular-medium-editor/.jshintrc create mode 100644 vendor/assets/components/angular-medium-editor/.travis.yml create mode 100644 vendor/assets/components/angular-medium-editor/CONTRIBUTING.md create mode 100644 vendor/assets/components/angular-medium-editor/Gruntfile.js create mode 100644 vendor/assets/components/angular-medium-editor/README.md create mode 100644 vendor/assets/components/angular-medium-editor/bower.json create mode 100644 vendor/assets/components/angular-medium-editor/demo/css/demo.css create mode 100644 vendor/assets/components/angular-medium-editor/demo/css/normalize.css create mode 100644 vendor/assets/components/angular-medium-editor/demo/demo.js create mode 100644 vendor/assets/components/angular-medium-editor/demo/img/medium-editor.jpg create mode 100644 vendor/assets/components/angular-medium-editor/demo/index.html create mode 100644 vendor/assets/components/angular-medium-editor/dist/angular-medium-editor.js create mode 100644 vendor/assets/components/angular-medium-editor/dist/angular-medium-editor.min.js create mode 100644 vendor/assets/components/angular-medium-editor/package.json create mode 100644 vendor/assets/components/angular-medium-editor/src/angular-medium-editor.js create mode 100644 vendor/assets/components/angular-minicolors/.bower.json create mode 100644 vendor/assets/components/angular-minicolors/LICENSE create mode 100644 vendor/assets/components/angular-minicolors/README.md create mode 100644 vendor/assets/components/angular-minicolors/angular-minicolors.js create mode 100644 vendor/assets/components/angular-minicolors/bower.json create mode 100644 vendor/assets/components/angular-resource/.bower.json create mode 100644 vendor/assets/components/angular-resource/README.md create mode 100644 vendor/assets/components/angular-resource/angular-resource.js create mode 100644 vendor/assets/components/angular-resource/angular-resource.min.js create mode 100644 vendor/assets/components/angular-resource/angular-resource.min.js.map create mode 100644 vendor/assets/components/angular-resource/bower.json create mode 100644 vendor/assets/components/angular-resource/index.js create mode 100644 vendor/assets/components/angular-resource/package.json create mode 100644 vendor/assets/components/angular-sanitize/.bower.json create mode 100644 vendor/assets/components/angular-sanitize/README.md create mode 100644 vendor/assets/components/angular-sanitize/angular-sanitize.js create mode 100644 vendor/assets/components/angular-sanitize/angular-sanitize.min.js create mode 100644 vendor/assets/components/angular-sanitize/angular-sanitize.min.js.map create mode 100644 vendor/assets/components/angular-sanitize/bower.json create mode 100644 vendor/assets/components/angular-sanitize/index.js create mode 100644 vendor/assets/components/angular-sanitize/package.json create mode 100644 vendor/assets/components/angular-summernote/gulpfile.js create mode 100644 vendor/assets/components/angular-touch/.bower.json create mode 100644 vendor/assets/components/angular-touch/README.md create mode 100644 vendor/assets/components/angular-touch/angular-touch.js create mode 100644 vendor/assets/components/angular-touch/angular-touch.min.js create mode 100644 vendor/assets/components/angular-touch/angular-touch.min.js.map create mode 100644 vendor/assets/components/angular-touch/bower.json create mode 100644 vendor/assets/components/angular-touch/index.js create mode 100644 vendor/assets/components/angular-touch/package.json create mode 100644 vendor/assets/components/angular-translate-interpolation-messageformat/.bower.json create mode 100644 vendor/assets/components/angular-translate-interpolation-messageformat/README.md create mode 100644 vendor/assets/components/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.js create mode 100644 vendor/assets/components/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.min.js create mode 100644 vendor/assets/components/angular-translate-interpolation-messageformat/bower.json create mode 100644 vendor/assets/components/angular-translate-interpolation-messageformat/package.json create mode 100644 vendor/assets/components/angular-translate-loader-partial/.bower.json create mode 100644 vendor/assets/components/angular-translate-loader-partial/README.md create mode 100644 vendor/assets/components/angular-translate-loader-partial/angular-translate-loader-partial.js create mode 100644 vendor/assets/components/angular-translate-loader-partial/angular-translate-loader-partial.min.js create mode 100644 vendor/assets/components/angular-translate-loader-partial/bower.json create mode 100644 vendor/assets/components/angular-translate-loader-partial/package.json create mode 100644 vendor/assets/components/angular-translate/.bower.json create mode 100644 vendor/assets/components/angular-translate/README.md create mode 100644 vendor/assets/components/angular-translate/angular-translate.js create mode 100644 vendor/assets/components/angular-translate/angular-translate.min.js create mode 100644 vendor/assets/components/angular-translate/bower.json create mode 100644 vendor/assets/components/angular-ui-calendar/.bower.json rename vendor/assets/components/{angular-ui-select2 => angular-ui-calendar}/LICENSE (97%) create mode 100644 vendor/assets/components/angular-ui-calendar/README.md create mode 100644 vendor/assets/components/angular-ui-calendar/bower.json create mode 100644 vendor/assets/components/angular-ui-calendar/src/calendar.js create mode 100644 vendor/assets/components/angular-ui-select/.bower.json create mode 100644 vendor/assets/components/angular-ui-select/CHANGELOG.md create mode 100644 vendor/assets/components/angular-ui-select/LICENSE create mode 100644 vendor/assets/components/angular-ui-select/README.md create mode 100644 vendor/assets/components/angular-ui-select/bower.json create mode 100644 vendor/assets/components/angular-ui-select/dist/select.css create mode 100644 vendor/assets/components/angular-ui-select/dist/select.js create mode 100644 vendor/assets/components/angular-ui-select/dist/select.min.css create mode 100644 vendor/assets/components/angular-ui-select/dist/select.min.js create mode 100644 vendor/assets/components/angular-ui-select/package.json delete mode 100644 vendor/assets/components/angular-ui-select2/.bower.json delete mode 100644 vendor/assets/components/angular-ui-select2/.bowerrc delete mode 100644 vendor/assets/components/angular-ui-select2/.jshintrc delete mode 100644 vendor/assets/components/angular-ui-select2/.travis.yml delete mode 100644 vendor/assets/components/angular-ui-select2/CONTRIBUTING.md delete mode 100644 vendor/assets/components/angular-ui-select2/Gruntfile.js delete mode 100644 vendor/assets/components/angular-ui-select2/README.md delete mode 100644 vendor/assets/components/angular-ui-select2/bower.json delete mode 100644 vendor/assets/components/angular-ui-select2/docs/index.html delete mode 100644 vendor/assets/components/angular-ui-select2/docs/styles.css delete mode 100644 vendor/assets/components/angular-ui-select2/package.json delete mode 100644 vendor/assets/components/angular-ui-select2/src/select2.js delete mode 100644 vendor/assets/components/angular-ui-select2/test/karma.conf.js delete mode 100644 vendor/assets/components/angular-ui-select2/test/select2Spec.js create mode 100644 vendor/assets/components/angular/index.js create mode 100644 vendor/assets/components/bootstrap-switch/.bower.json create mode 100644 vendor/assets/components/bootstrap-switch/.bowerrc create mode 100644 vendor/assets/components/bootstrap-switch/.gitignore create mode 100644 vendor/assets/components/bootstrap-switch/CHANGELOG.md create mode 100644 vendor/assets/components/bootstrap-switch/LICENSE create mode 100644 vendor/assets/components/bootstrap-switch/README.md create mode 100644 vendor/assets/components/bootstrap-switch/bower.json create mode 100644 vendor/assets/components/bootstrap-switch/dist/css/bootstrap2/bootstrap-switch.css create mode 100644 vendor/assets/components/bootstrap-switch/dist/css/bootstrap2/bootstrap-switch.min.css create mode 100644 vendor/assets/components/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css create mode 100644 vendor/assets/components/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.min.css create mode 100644 vendor/assets/components/bootstrap-switch/dist/js/bootstrap-switch.js create mode 100644 vendor/assets/components/bootstrap-switch/dist/js/bootstrap-switch.min.js create mode 100644 vendor/assets/components/bootstrap-switch/documentation-2.html create mode 100644 vendor/assets/components/bootstrap-switch/events.html create mode 100644 vendor/assets/components/bootstrap-switch/examples.html create mode 100644 vendor/assets/components/bootstrap-switch/karma.json create mode 100644 vendor/assets/components/bootstrap-switch/main.html create mode 100644 vendor/assets/components/bootstrap-switch/methods.html create mode 100644 vendor/assets/components/bootstrap-switch/options.html create mode 100644 vendor/assets/components/bootstrap-switch/src/coffee/bootstrap-switch.coffee create mode 100644 vendor/assets/components/bootstrap-switch/src/coffee/bootstrap-switch.tests.coffee create mode 100644 vendor/assets/components/bootstrap-switch/src/less/bootstrap2/bootstrap-switch.less create mode 100644 vendor/assets/components/bootstrap-switch/src/less/bootstrap2/build.less create mode 100644 vendor/assets/components/bootstrap-switch/src/less/bootstrap2/mixins.less create mode 100644 vendor/assets/components/bootstrap-switch/src/less/bootstrap2/variables.less create mode 100644 vendor/assets/components/bootstrap-switch/src/less/bootstrap3/bootstrap-switch.less create mode 100644 vendor/assets/components/bootstrap-switch/src/less/bootstrap3/build.less create mode 100644 vendor/assets/components/bootstrap-switch/src/less/bootstrap3/mixins.less create mode 100644 vendor/assets/components/bootstrap-switch/src/less/bootstrap3/variables.less create mode 100644 vendor/assets/components/d3/.bower.json create mode 100644 vendor/assets/components/d3/.gitattributes create mode 100644 vendor/assets/components/d3/CONTRIBUTING.md create mode 100644 vendor/assets/components/d3/LICENSE create mode 100644 vendor/assets/components/d3/README.md create mode 100644 vendor/assets/components/d3/bower.json create mode 100644 vendor/assets/components/d3/d3.js create mode 100644 vendor/assets/components/d3/d3.min.js create mode 100644 vendor/assets/components/d3/package.js create mode 100644 vendor/assets/components/elasticsearch/.bower.json create mode 100644 vendor/assets/components/elasticsearch/README.md create mode 100644 vendor/assets/components/elasticsearch/bower.json create mode 100644 vendor/assets/components/elasticsearch/elasticsearch.angular.js create mode 100644 vendor/assets/components/elasticsearch/elasticsearch.angular.min.js create mode 100644 vendor/assets/components/elasticsearch/elasticsearch.jquery.js create mode 100644 vendor/assets/components/elasticsearch/elasticsearch.jquery.min.js create mode 100644 vendor/assets/components/elasticsearch/elasticsearch.js create mode 100644 vendor/assets/components/elasticsearch/elasticsearch.min.js create mode 100644 vendor/assets/components/elasticsearch/package.json create mode 100644 vendor/assets/components/fullcalendar/.bower.json create mode 100644 vendor/assets/components/fullcalendar/bower.json create mode 100644 vendor/assets/components/fullcalendar/changelog.md create mode 100644 vendor/assets/components/fullcalendar/dist/fullcalendar.css create mode 100644 vendor/assets/components/fullcalendar/dist/fullcalendar.js create mode 100644 vendor/assets/components/fullcalendar/dist/fullcalendar.min.css create mode 100644 vendor/assets/components/fullcalendar/dist/fullcalendar.min.js create mode 100644 vendor/assets/components/fullcalendar/dist/fullcalendar.print.css create mode 100644 vendor/assets/components/fullcalendar/dist/gcal.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang-all.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/ar-ma.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/ar-sa.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/ar-tn.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/ar.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/bg.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/ca.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/cs.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/da.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/de-at.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/de.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/el.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/en-au.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/en-ca.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/en-gb.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/es.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/fa.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/fi.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/fr-ca.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/fr.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/he.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/hi.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/hr.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/hu.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/id.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/is.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/it.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/ja.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/ko.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/lt.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/lv.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/nb.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/nl.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/pl.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/pt-br.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/pt.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/ro.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/ru.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/sk.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/sl.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/sr-cyrl.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/sr.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/sv.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/th.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/tr.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/uk.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/vi.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/zh-cn.js create mode 100644 vendor/assets/components/fullcalendar/dist/lang/zh-tw.js create mode 100644 vendor/assets/components/fullcalendar/license.txt create mode 100644 vendor/assets/components/fullcalendar/readme.md create mode 100644 vendor/assets/components/holderjs/test/phantom.js create mode 100644 vendor/assets/components/jquery-minicolors/.bower.json create mode 100644 vendor/assets/components/jquery-minicolors/bower.json create mode 100644 vendor/assets/components/jquery-minicolors/component.json create mode 100644 vendor/assets/components/jquery-minicolors/composer.json create mode 100644 vendor/assets/components/jquery-minicolors/index.html create mode 100644 vendor/assets/components/jquery-minicolors/jquery.minicolors.css create mode 100644 vendor/assets/components/jquery-minicolors/jquery.minicolors.js create mode 100644 vendor/assets/components/jquery-minicolors/jquery.minicolors.min.js create mode 100644 vendor/assets/components/jquery-minicolors/jquery.minicolors.png create mode 100755 vendor/assets/components/jquery-minicolors/readme.md create mode 100644 vendor/assets/components/jquery-minicolors/without-bootstrap.html create mode 100644 vendor/assets/components/medium-editor/.bower.json create mode 100644 vendor/assets/components/medium-editor/LICENSE create mode 100644 vendor/assets/components/medium-editor/bower.json create mode 100644 vendor/assets/components/medium-editor/dist/css/medium-editor.css create mode 100644 vendor/assets/components/medium-editor/dist/css/medium-editor.min.css create mode 100644 vendor/assets/components/medium-editor/dist/css/themes/bootstrap.css create mode 100644 vendor/assets/components/medium-editor/dist/css/themes/bootstrap.min.css create mode 100644 vendor/assets/components/medium-editor/dist/css/themes/default.css create mode 100644 vendor/assets/components/medium-editor/dist/css/themes/default.min.css create mode 100644 vendor/assets/components/medium-editor/dist/css/themes/flat.css create mode 100644 vendor/assets/components/medium-editor/dist/css/themes/flat.min.css create mode 100644 vendor/assets/components/medium-editor/dist/css/themes/mani.css create mode 100644 vendor/assets/components/medium-editor/dist/css/themes/mani.min.css create mode 100644 vendor/assets/components/medium-editor/dist/css/themes/roman.css create mode 100644 vendor/assets/components/medium-editor/dist/css/themes/roman.min.css create mode 100644 vendor/assets/components/medium-editor/dist/js/medium-editor.js create mode 100644 vendor/assets/components/medium-editor/dist/js/medium-editor.min.js create mode 100644 vendor/assets/components/medium-editor/src/sass/_clearfix.scss create mode 100644 vendor/assets/components/medium-editor/src/sass/_pop-upwards.scss create mode 100644 vendor/assets/components/medium-editor/src/sass/medium-editor.scss create mode 100644 vendor/assets/components/medium-editor/src/sass/themes/bootstrap.scss create mode 100644 vendor/assets/components/medium-editor/src/sass/themes/default.scss create mode 100644 vendor/assets/components/medium-editor/src/sass/themes/flat.scss create mode 100644 vendor/assets/components/medium-editor/src/sass/themes/mani.scss create mode 100644 vendor/assets/components/medium-editor/src/sass/themes/roman.scss create mode 100644 vendor/assets/components/medium-editor/src/wrappers/end.js create mode 100644 vendor/assets/components/medium-editor/src/wrappers/start.js create mode 100644 vendor/assets/components/messageformat/.bower.json create mode 100644 vendor/assets/components/messageformat/.gitignore create mode 100644 vendor/assets/components/messageformat/.travis.yml create mode 100644 vendor/assets/components/messageformat/Makefile create mode 100644 vendor/assets/components/messageformat/README.md create mode 100755 vendor/assets/components/messageformat/bin/messageformat.js create mode 100644 vendor/assets/components/messageformat/bower.json create mode 100644 vendor/assets/components/messageformat/component.json create mode 100644 vendor/assets/components/messageformat/example/en/colors.json create mode 100644 vendor/assets/components/messageformat/example/en/i18n.js create mode 100644 vendor/assets/components/messageformat/example/en/sub/folder/plural.json create mode 100644 vendor/assets/components/messageformat/example/fr/colors.json create mode 100644 vendor/assets/components/messageformat/example/fr/i18n.js create mode 100644 vendor/assets/components/messageformat/example/fr/sub/folder/plural.json create mode 100644 vendor/assets/components/messageformat/example/index.html create mode 100644 vendor/assets/components/messageformat/lib/message_parser.js create mode 100644 vendor/assets/components/messageformat/lib/message_parser.pegjs create mode 100644 vendor/assets/components/messageformat/lib/messageformat.dev.js create mode 100644 vendor/assets/components/messageformat/lib/messageformat.include.js create mode 100644 vendor/assets/components/messageformat/locale/af.js create mode 100644 vendor/assets/components/messageformat/locale/am.js create mode 100644 vendor/assets/components/messageformat/locale/ar.js create mode 100644 vendor/assets/components/messageformat/locale/bg.js create mode 100644 vendor/assets/components/messageformat/locale/bn.js create mode 100644 vendor/assets/components/messageformat/locale/br.js create mode 100644 vendor/assets/components/messageformat/locale/ca.js create mode 100644 vendor/assets/components/messageformat/locale/cs.js create mode 100644 vendor/assets/components/messageformat/locale/cy.js create mode 100644 vendor/assets/components/messageformat/locale/da.js create mode 100644 vendor/assets/components/messageformat/locale/de.js create mode 100644 vendor/assets/components/messageformat/locale/el.js create mode 100644 vendor/assets/components/messageformat/locale/en.js create mode 100644 vendor/assets/components/messageformat/locale/es.js create mode 100644 vendor/assets/components/messageformat/locale/et.js create mode 100644 vendor/assets/components/messageformat/locale/eu.js create mode 100644 vendor/assets/components/messageformat/locale/fa.js create mode 100644 vendor/assets/components/messageformat/locale/fi.js create mode 100644 vendor/assets/components/messageformat/locale/fil.js create mode 100644 vendor/assets/components/messageformat/locale/fr.js create mode 100644 vendor/assets/components/messageformat/locale/ga.js create mode 100644 vendor/assets/components/messageformat/locale/gl.js create mode 100644 vendor/assets/components/messageformat/locale/gsw.js create mode 100644 vendor/assets/components/messageformat/locale/gu.js create mode 100644 vendor/assets/components/messageformat/locale/he.js create mode 100644 vendor/assets/components/messageformat/locale/hi.js create mode 100644 vendor/assets/components/messageformat/locale/hr.js create mode 100644 vendor/assets/components/messageformat/locale/hu.js create mode 100644 vendor/assets/components/messageformat/locale/id.js create mode 100644 vendor/assets/components/messageformat/locale/in.js create mode 100644 vendor/assets/components/messageformat/locale/is.js create mode 100644 vendor/assets/components/messageformat/locale/it.js create mode 100644 vendor/assets/components/messageformat/locale/iw.js create mode 100644 vendor/assets/components/messageformat/locale/ja.js create mode 100644 vendor/assets/components/messageformat/locale/kn.js create mode 100644 vendor/assets/components/messageformat/locale/ko.js create mode 100644 vendor/assets/components/messageformat/locale/lag.js create mode 100644 vendor/assets/components/messageformat/locale/ln.js create mode 100644 vendor/assets/components/messageformat/locale/lt.js create mode 100644 vendor/assets/components/messageformat/locale/lv.js create mode 100644 vendor/assets/components/messageformat/locale/mk.js create mode 100644 vendor/assets/components/messageformat/locale/ml.js create mode 100644 vendor/assets/components/messageformat/locale/mo.js create mode 100644 vendor/assets/components/messageformat/locale/mr.js create mode 100644 vendor/assets/components/messageformat/locale/ms.js create mode 100644 vendor/assets/components/messageformat/locale/mt.js create mode 100644 vendor/assets/components/messageformat/locale/nl.js create mode 100644 vendor/assets/components/messageformat/locale/no.js create mode 100644 vendor/assets/components/messageformat/locale/or.js create mode 100644 vendor/assets/components/messageformat/locale/pl.js create mode 100644 vendor/assets/components/messageformat/locale/pt.js create mode 100644 vendor/assets/components/messageformat/locale/ro.js create mode 100644 vendor/assets/components/messageformat/locale/ru.js create mode 100644 vendor/assets/components/messageformat/locale/shi.js create mode 100644 vendor/assets/components/messageformat/locale/sk.js create mode 100644 vendor/assets/components/messageformat/locale/sl.js create mode 100644 vendor/assets/components/messageformat/locale/sq.js create mode 100644 vendor/assets/components/messageformat/locale/sr.js create mode 100644 vendor/assets/components/messageformat/locale/sv.js create mode 100644 vendor/assets/components/messageformat/locale/sw.js create mode 100644 vendor/assets/components/messageformat/locale/ta.js create mode 100644 vendor/assets/components/messageformat/locale/te.js create mode 100644 vendor/assets/components/messageformat/locale/th.js create mode 100644 vendor/assets/components/messageformat/locale/tl.js create mode 100644 vendor/assets/components/messageformat/locale/tr.js create mode 100644 vendor/assets/components/messageformat/locale/uk.js create mode 100644 vendor/assets/components/messageformat/locale/ur.js create mode 100644 vendor/assets/components/messageformat/locale/vi.js create mode 100644 vendor/assets/components/messageformat/locale/zh.js create mode 100644 vendor/assets/components/messageformat/messageformat.js create mode 100644 vendor/assets/components/messageformat/package.json create mode 100644 vendor/assets/components/messageformat/test/common.js create mode 100644 vendor/assets/components/messageformat/test/index.html create mode 100644 vendor/assets/components/messageformat/test/jquery.min.js create mode 100644 vendor/assets/components/messageformat/test/tests.js create mode 100644 vendor/assets/components/moment-timezone/.bower.json create mode 100644 vendor/assets/components/moment-timezone/LICENSE create mode 100644 vendor/assets/components/moment-timezone/README.md create mode 100644 vendor/assets/components/moment-timezone/bower.json create mode 100644 vendor/assets/components/moment-timezone/builds/moment-timezone-with-data-2010-2020.js create mode 100644 vendor/assets/components/moment-timezone/builds/moment-timezone-with-data-2010-2020.min.js create mode 100644 vendor/assets/components/moment-timezone/builds/moment-timezone-with-data.js create mode 100644 vendor/assets/components/moment-timezone/builds/moment-timezone-with-data.min.js create mode 100644 vendor/assets/components/moment-timezone/builds/moment-timezone.min.js create mode 100644 vendor/assets/components/moment-timezone/changelog.md create mode 100644 vendor/assets/components/moment-timezone/composer.json create mode 100644 vendor/assets/components/moment-timezone/data/meta/latest.json create mode 100644 vendor/assets/components/moment-timezone/data/packed/latest.json create mode 100644 vendor/assets/components/moment-timezone/data/unpacked/latest.json create mode 100644 vendor/assets/components/moment-timezone/moment-timezone-utils.js create mode 100644 vendor/assets/components/moment-timezone/moment-timezone.js delete mode 100644 vendor/assets/components/moment/Moment.js.nuspec delete mode 100644 vendor/assets/components/moment/benchmarks/clone.js create mode 100644 vendor/assets/components/moment/locale/jv.js create mode 100644 vendor/assets/components/moment/locale/me.js create mode 100644 vendor/assets/components/moment/locale/ms.js create mode 100644 vendor/assets/components/moment/locale/si.js create mode 100644 vendor/assets/components/moment/locale/tzl.js delete mode 100644 vendor/assets/components/moment/meteor/README.md delete mode 100644 vendor/assets/components/moment/meteor/export.js delete mode 100644 vendor/assets/components/moment/meteor/test.js delete mode 100755 vendor/assets/components/moment/scripts/npm_prepublish.sh rename vendor/assets/components/moment/src/lib/create/{default-parsing-flags.js => parsing-flags.js} (69%) create mode 100644 vendor/assets/components/moment/src/lib/moment/to.js create mode 100644 vendor/assets/components/moment/src/lib/utils/abs-ceil.js create mode 100644 vendor/assets/components/moment/src/locale/jv.js create mode 100644 vendor/assets/components/moment/src/locale/me.js create mode 100644 vendor/assets/components/moment/src/locale/ms.js create mode 100644 vendor/assets/components/moment/src/locale/si.js create mode 100644 vendor/assets/components/moment/src/locale/tzl.js create mode 100644 vendor/assets/components/nvd3/.bower.json create mode 100644 vendor/assets/components/nvd3/bower.json create mode 100644 vendor/assets/components/nvd3/build/nv.d3.css create mode 100644 vendor/assets/components/nvd3/build/nv.d3.js create mode 100644 vendor/assets/components/nvd3/build/nv.d3.min.css create mode 100644 vendor/assets/components/nvd3/build/nv.d3.min.js create mode 100644 vendor/assets/components/nvd3/package.js delete mode 100644 vendor/assets/components/select2/.bower.json delete mode 100644 vendor/assets/components/select2/.gitignore delete mode 100644 vendor/assets/components/select2/LICENSE delete mode 100644 vendor/assets/components/select2/README.md delete mode 100644 vendor/assets/components/select2/bower.json delete mode 100644 vendor/assets/components/select2/component.json delete mode 100644 vendor/assets/components/select2/composer.json delete mode 100644 vendor/assets/components/select2/package.json delete mode 100755 vendor/assets/components/select2/release.sh delete mode 100644 vendor/assets/components/select2/select2-bootstrap.css delete mode 100644 vendor/assets/components/select2/select2-spinner.gif delete mode 100644 vendor/assets/components/select2/select2.css delete mode 100644 vendor/assets/components/select2/select2.jquery.json delete mode 100644 vendor/assets/components/select2/select2.js delete mode 100644 vendor/assets/components/select2/select2.min.js delete mode 100644 vendor/assets/components/select2/select2.png delete mode 100644 vendor/assets/components/select2/select2_locale_ar.js delete mode 100644 vendor/assets/components/select2/select2_locale_bg.js delete mode 100644 vendor/assets/components/select2/select2_locale_ca.js delete mode 100644 vendor/assets/components/select2/select2_locale_cs.js delete mode 100644 vendor/assets/components/select2/select2_locale_da.js delete mode 100644 vendor/assets/components/select2/select2_locale_de.js delete mode 100644 vendor/assets/components/select2/select2_locale_el.js delete mode 100644 vendor/assets/components/select2/select2_locale_en.js.template delete mode 100644 vendor/assets/components/select2/select2_locale_es.js delete mode 100644 vendor/assets/components/select2/select2_locale_et.js delete mode 100644 vendor/assets/components/select2/select2_locale_eu.js delete mode 100644 vendor/assets/components/select2/select2_locale_fa.js delete mode 100644 vendor/assets/components/select2/select2_locale_fi.js delete mode 100644 vendor/assets/components/select2/select2_locale_fr.js delete mode 100644 vendor/assets/components/select2/select2_locale_gl.js delete mode 100644 vendor/assets/components/select2/select2_locale_he.js delete mode 100644 vendor/assets/components/select2/select2_locale_hr.js delete mode 100644 vendor/assets/components/select2/select2_locale_hu.js delete mode 100644 vendor/assets/components/select2/select2_locale_id.js delete mode 100644 vendor/assets/components/select2/select2_locale_is.js delete mode 100644 vendor/assets/components/select2/select2_locale_it.js delete mode 100644 vendor/assets/components/select2/select2_locale_ja.js delete mode 100644 vendor/assets/components/select2/select2_locale_ka.js delete mode 100644 vendor/assets/components/select2/select2_locale_ko.js delete mode 100644 vendor/assets/components/select2/select2_locale_lt.js delete mode 100644 vendor/assets/components/select2/select2_locale_lv.js delete mode 100644 vendor/assets/components/select2/select2_locale_mk.js delete mode 100644 vendor/assets/components/select2/select2_locale_ms.js delete mode 100644 vendor/assets/components/select2/select2_locale_nl.js delete mode 100644 vendor/assets/components/select2/select2_locale_no.js delete mode 100644 vendor/assets/components/select2/select2_locale_pl.js delete mode 100644 vendor/assets/components/select2/select2_locale_pt-BR.js delete mode 100644 vendor/assets/components/select2/select2_locale_pt-PT.js delete mode 100644 vendor/assets/components/select2/select2_locale_ro.js delete mode 100644 vendor/assets/components/select2/select2_locale_rs.js delete mode 100644 vendor/assets/components/select2/select2_locale_ru.js delete mode 100644 vendor/assets/components/select2/select2_locale_sk.js delete mode 100644 vendor/assets/components/select2/select2_locale_sv.js delete mode 100644 vendor/assets/components/select2/select2_locale_th.js delete mode 100644 vendor/assets/components/select2/select2_locale_tr.js delete mode 100644 vendor/assets/components/select2/select2_locale_uk.js delete mode 100644 vendor/assets/components/select2/select2_locale_vi.js delete mode 100644 vendor/assets/components/select2/select2_locale_zh-CN.js delete mode 100755 vendor/assets/components/select2/select2_locale_zh-TW.js delete mode 100644 vendor/assets/components/select2/select2x2.png delete mode 100644 vendor/assets/components/summernote/Gruntfile.js delete mode 100644 vendor/assets/components/summernote/History.md create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ar-AR.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ar-AR.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-bg-BG.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-bg-BG.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ca-ES.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ca-ES.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-cs-CZ.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-cs-CZ.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-da-DK.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-da-DK.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-de-DE.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-de-DE.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-es-ES.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-es-ES.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-es-EU.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-es-EU.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-fa-IR.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-fa-IR.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-fi-FI.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-fi-FI.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-fr-FR.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-fr-FR.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-he-IL.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-he-IL.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-hu-HU.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-hu-HU.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-id-ID.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-id-ID.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-it-IT.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-it-IT.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ja-JP.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ja-JP.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ko-KR.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ko-KR.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-lt-LT.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-lt-LT.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-nb-NO.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-nb-NO.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-nl-NL.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-nl-NL.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-pl-PL.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-pl-PL.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-pt-BR.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-pt-BR.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-pt-PT.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-pt-PT.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ro-RO.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ro-RO.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ru-RU.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-ru-RU.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-sk-SK.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-sk-SK.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-sl-SI.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-sl-SI.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-sr-RS-Latin.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-sr-RS-Latin.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-sr-RS.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-sr-RS.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-sv-SE.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-sv-SE.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-th-TH.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-th-TH.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-tr-TR.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-tr-TR.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-uk-UA.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-uk-UA.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-vi-VN.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-vi-VN.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-zh-CN.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-zh-CN.min.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-zh-TW.js create mode 100644 vendor/assets/components/summernote/dist/lang/summernote-zh-TW.min.js delete mode 100644 vendor/assets/components/summernote/dist/summernote-bs2.css delete mode 100644 vendor/assets/components/summernote/dist/summernote-bs3.css delete mode 100644 vendor/assets/components/summernote/examples/bs2.html delete mode 100644 vendor/assets/components/summernote/examples/bs2fa4.html delete mode 100644 vendor/assets/components/summernote/examples/bs301fa4.html create mode 100644 vendor/assets/components/summernote/examples/bs3fa4.html create mode 100644 vendor/assets/components/summernote/examples/hint-emoji.html create mode 100644 vendor/assets/components/summernote/examples/hint-userdefine.html delete mode 100644 vendor/assets/components/summernote/examples/index.html delete mode 100644 vendor/assets/components/summernote/examples/plugin-fontstyle.html delete mode 100644 vendor/assets/components/summernote/examples/plugin-video.html delete mode 100644 vendor/assets/components/summernote/grunts/grunt-build.js create mode 100644 vendor/assets/components/summernote/ie8.html create mode 100644 vendor/assets/components/summernote/lang/summernote-bg-BG.js create mode 100644 vendor/assets/components/summernote/lang/summernote-lt-LT.js create mode 100644 vendor/assets/components/summernote/lang/summernote-pt-PT.js create mode 100644 vendor/assets/components/summernote/lite.html create mode 100644 vendor/assets/components/summernote/plugin/hello/summernote-ext-hello.js create mode 100644 vendor/assets/components/summernote/plugin/specialchars/summernote-ext-specialchars.js delete mode 100644 vendor/assets/components/summernote/plugin/summernote-ext-hello.js delete mode 100644 vendor/assets/components/summernote/plugin/summernote-ext-video.js delete mode 100644 vendor/assets/components/summernote/src/js/EventHandler.js delete mode 100644 vendor/assets/components/summernote/src/js/Renderer.js create mode 100644 vendor/assets/components/summernote/src/js/base/core/agent.js rename vendor/assets/components/summernote/src/js/{ => base}/core/async.js (76%) rename vendor/assets/components/summernote/src/js/{ => base}/core/dom.js (89%) rename vendor/assets/components/summernote/src/js/{ => base}/core/func.js (94%) rename vendor/assets/components/summernote/src/js/{ => base}/core/key.js (65%) rename vendor/assets/components/summernote/src/js/{ => base}/core/list.js (88%) rename vendor/assets/components/summernote/src/js/{ => base}/core/range.js (85%) rename vendor/assets/components/summernote/src/js/{ => base}/editing/Bullet.js (96%) rename vendor/assets/components/summernote/src/js/{ => base}/editing/History.js (56%) rename vendor/assets/components/summernote/src/js/{ => base}/editing/Style.js (72%) rename vendor/assets/components/summernote/src/js/{ => base}/editing/Table.js (76%) rename vendor/assets/components/summernote/src/js/{ => base}/editing/Typing.js (71%) create mode 100644 vendor/assets/components/summernote/src/js/base/module/AutoLink.js create mode 100644 vendor/assets/components/summernote/src/js/base/module/AutoSync.js create mode 100644 vendor/assets/components/summernote/src/js/base/module/Clipboard.js rename vendor/assets/components/summernote/src/js/{ => base}/module/Codeview.js (52%) rename vendor/assets/components/summernote/src/js/{module/DragAndDrop.js => base/module/Dropzone.js} (52%) create mode 100644 vendor/assets/components/summernote/src/js/base/module/Editor.js rename vendor/assets/components/summernote/src/js/{ => base}/module/Fullscreen.js (63%) create mode 100644 vendor/assets/components/summernote/src/js/base/module/Handle.js create mode 100644 vendor/assets/components/summernote/src/js/base/module/Placeholder.js create mode 100644 vendor/assets/components/summernote/src/js/base/module/Statusbar.js create mode 100644 vendor/assets/components/summernote/src/js/base/renderer.js create mode 100644 vendor/assets/components/summernote/src/js/base/summernote-en-US.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/module/AirPopover.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/module/Buttons.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/module/HelpDialog.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/module/HintPopover.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/module/ImageDialog.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/module/ImagePopover.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/module/LinkDialog.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/module/LinkPopover.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/module/Toolbar.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/module/VideoDialog.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/settings.js create mode 100644 vendor/assets/components/summernote/src/js/bs3/ui.js delete mode 100644 vendor/assets/components/summernote/src/js/core/agent.js delete mode 100644 vendor/assets/components/summernote/src/js/defaults.js create mode 100644 vendor/assets/components/summernote/src/js/lite/module/Toolbar.js create mode 100644 vendor/assets/components/summernote/src/js/lite/settings.js create mode 100644 vendor/assets/components/summernote/src/js/lite/ui.js delete mode 100644 vendor/assets/components/summernote/src/js/module/Button.js delete mode 100644 vendor/assets/components/summernote/src/js/module/Clipboard.js delete mode 100644 vendor/assets/components/summernote/src/js/module/Editor.js delete mode 100644 vendor/assets/components/summernote/src/js/module/Handle.js delete mode 100644 vendor/assets/components/summernote/src/js/module/HelpDialog.js delete mode 100644 vendor/assets/components/summernote/src/js/module/ImageDialog.js delete mode 100644 vendor/assets/components/summernote/src/js/module/LinkDialog.js delete mode 100644 vendor/assets/components/summernote/src/js/module/Popover.js delete mode 100644 vendor/assets/components/summernote/src/js/module/Statusbar.js delete mode 100644 vendor/assets/components/summernote/src/js/module/Toolbar.js create mode 100644 vendor/assets/components/summernote/src/less/elements.scss create mode 100644 vendor/assets/components/summernote/src/less/summernote-lite.less rename vendor/assets/components/summernote/src/{sass => less}/summernote.scss (67%) create mode 100644 vendor/assets/fonts/OpenSans-Bold.ttf create mode 100644 vendor/assets/fonts/OpenSans-BoldItalic.ttf create mode 100644 vendor/assets/fonts/OpenSans-Italic.ttf create mode 100644 vendor/assets/fonts/OpenSans-Regular.ttf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..2d17e8d5a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,33 @@ +# Ignore bundler config. +.bundle +vendor/cache + +config/database.yml +config/application.yml + +# Ignore the default SQLite database. +db/*.sqlite3 +db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +log +tmp + +public/uploads +public/assets + +*.DS_Store +.idea + +# PDF invoices +invoices + +.DS_Store + +.vagrant +Vagrantfile + +.git* + +Dockerfile +docker-compose* diff --git a/.gitignore b/.gitignore index 3fd51a377..c54c203e9 100644 --- a/.gitignore +++ b/.gitignore @@ -13,17 +13,24 @@ /db/*.sqlite3-journal # Ignore all logfiles and tempfiles. -/log/* -!/log/.keep +/log/*.log /tmp -# uploads and public assets /public/uploads /public/assets -# MacOS and IDE files +# Ignore application configuration +/config/application.yml + +*.DS_Store .idea + +# PDF invoices +/invoices/* + +/config/database.yml +/config/application.yml + .DS_Store -# machine specific database config -/config/database.yml +.vagrant diff --git a/.ruby-gemset b/.ruby-gemset index 9fc221411..54e5f893f 100644 --- a/.ruby-gemset +++ b/.ruby-gemset @@ -1 +1 @@ -fabmanager +fablab diff --git a/.ruby-version b/.ruby-version index 58f65ad57..8274681f8 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.2.1 +ruby-2.2.3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..da8498683 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,132 @@ +# Contributing to FabManager + +♥ [FabManager](http://www.fab-manager.com) and want to get involved? +Thanks! There are plenty of ways you can help! + +Please take a moment to review this document in order to make the contribution process easy and effective for everyone +involved. + +Following these guidelines helps to communicate that you respect the time of the developers managing and developing +this open source project. In return, they should reciprocate that respect in addressing your issue or assessing +patches and features. + + +## Using the issue tracker + +The [issue tracker](https://github.com/LaCasemate/fab-manager/issues) is the preferred channel for [bug reports](#bugs), +[features requests](#features) and [submitting pull requests](#pull-requests), but please respect the following +restrictions: + +* Please **do not** use the issue tracker for personal support requests (use [the forum](http://www.fab-manager.com/forum)). + +* Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others. + +* Please **do not** open issues or pull requests regarding the code in plugins or third parties software, (open them + in their respective repositories). + + + +## Bug reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely +helpful - thank you! + +Guidelines for bug reports: + +1. **Use the GitHub issue search** — check if the issue has already been reported. + +2. **Check if the issue has been fixed** — try to reproduce it using the latest `master` or development + branch in the repository. + +3. **Isolate the problem** — ideally create a [reduced test case](https://css-tricks.com/reduced-test-cases/) + and a live example. + +A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed +as possible in your report. What is your environment? What steps will reproduce the issue? What browser(s) and OS +experience the problem? What would you expect to be the outcome? All these details will help people to fix any potential +bugs. + +Example: + +> Short and descriptive example bug report title +> +> A summary of the issue and the browser/OS environment in which it occurs. If suitable, include the steps required +> to reproduce the bug. +> +> 1. This is the first step +> 2. This is the second step +> 3. Further steps, etc. +> +> `` - a link to the reduced test case +> +> Any other information you want to share that is relevant to the issue being reported. This might include the lines of +> code that you have identified as causing the bug, and potential solutions (and your opinions on their merits). + + + +## Feature requests + +Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the +project. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature. +Please provide as much detail and context as possible. + + + +## Pull requests + +Good pull requests - patches, improvements, new features - are a fantastic help. They should remain focused in scope +and avoid containing unrelated commits. + +**Please ask first** before embarking on any significant pull request (e.g. implementing features, refactoring code, +porting to a different language), otherwise you risk spending a lot of time working on something that the project's +developers might not want to merge into the project. + +Please adhere to the coding conventions used throughout a project (indentation, accurate comments, etc.) and any other +requirements (such as test coverage). + +Adhering to the following process is the best way to get your work included in the project: + +1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork, and configure the remotes: + + ```bash + # Clone your fork of the repo into the current directory + git clone https://github.com//fab-manager.git + # Navigate to the newly cloned directory + cd fab-manager + # Assign the original repo to a remote called "upstream" + git remote add upstream https://github.com/LaCasemate/fab-manager.git + ``` + +2. If you cloned a while ago, get the latest changes from upstream: + + ```bash + git checkout master + git pull upstream master + ``` + +3. Create a new topic branch (off the main project development branch) to contain your feature, change, or fix: + + ```bash + git checkout -b + ``` + +4. Commit your changes in logical chunks. Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) + or your code is unlikely be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/about-git-rebase/) + feature to tidy up your commits before making them public. + +5. Locally merge (or rebase) the upstream development branch into your topic branch: + + ```bash + git pull [--rebase] upstream master + ``` + +6. Push your topic branch up to your fork: + + ```bash + git push origin + ``` + +7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title and description. + +**IMPORTANT**: By submitting a patch, you agree to allow the project owners to license your work under the terms of +the [GNU Affero General Public License](LICENSE.md). \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..cbb71eeca --- /dev/null +++ b/Dockerfile @@ -0,0 +1,71 @@ +FROM ruby:2.2 +MAINTAINER peng@sleede.com + +# cf: nginx Dockerfile : https://github.com/nginxinc/docker-nginx +RUN apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 +RUN echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >> /etc/apt/sources.list + +ENV NGINX_VERSION 1.9.7-1~jessie + +# Install apt based dependencies required to run Rails as +# well as RubyGems. As the Ruby image itself is based on a +# Debian image, we use apt-get to install those. +RUN apt-get update && \ + apt-get install -y \ + nginx=${NGINX_VERSION} \ + nodejs \ + supervisor + +# throw errors if Gemfile has been modified since Gemfile.lock +RUN bundle config --global frozen 1 + +# Run Bundle in a cache efficient way +WORKDIR /tmp +COPY Gemfile /tmp/ +COPY Gemfile.lock /tmp/ +RUN bundle install + +# Clean up APT when done. +#RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + + +# Nginx +# Remove the default site +RUN rm /etc/nginx/conf.d/default.conf + +# forward request and error logs to docker log collector +RUN ln -sf /dev/stdout /var/log/nginx/access.log +RUN ln -sf /dev/stderr /var/log/nginx/error.log + + +# Web app +RUN mkdir -p /usr/src/app +RUN mkdir -p /usr/src/app/config +RUN mkdir -p /usr/src/app/invoices +RUN mkdir -p /usr/src/app/log +RUN mkdir -p /usr/src/app/public/uploads +RUN mkdir -p /usr/src/app/public/assets +RUN mkdir -p /usr/src/app/tmp/sockets +RUN mkdir -p /usr/src/app/tmp/pids + +WORKDIR /usr/src/app + +COPY docker/database.yml /usr/src/app/config/database.yml + +COPY . /usr/src/app + +# Volumes +VOLUME /usr/src/app/invoices +VOLUME /usr/src/app/public/uploads +VOLUME /usr/src/app/public/assets +VOLUME /var/log/supervisor + +# Expose port 80 and ssl 443 to the Docker host, so we can access it +# from the outside. +EXPOSE 80 443 + +# The main command to run when the container starts. Also +# tell the Rails dev server to bind to all interfaces by +# default. +COPY docker/supervisor.conf /etc/supervisor/conf.d/fablab.conf +CMD ["/usr/bin/supervisord"] diff --git a/Gemfile b/Gemfile index 1d0f8e89a..7479895fc 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '4.2.1' +gem 'rails', '4.2.5' # Use SCSS for stylesheets gem 'sass-rails', '5.0.1' gem 'compass-rails', '2.0.4' @@ -11,21 +11,22 @@ gem 'uglifier', '>= 1.3.0' # Use CoffeeScript for .js.coffee assets and views gem 'coffee-rails', '~> 4.1.0' # See https://github.com/sstephenson/execjs#readme for more supported runtimes -gem 'therubyracer', platforms: :ruby +gem 'therubyracer', '= 0.12.0', platforms: :ruby # Use jquery as the JavaScript library gem 'jquery-rails' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. -gem 'sdoc', '~> 0.4.0', group: :doc +gem 'sdoc', '~> 0.4.0', group: :doc #TODO remove unused ? gem 'forgery' gem 'responders', '~> 2.0' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console - gem 'byebug' + # comment over to use visual debugger (eg. RubyMine), uncomment to use manual debugging + # gem 'byebug' # Access an IRB console on exception pages or by using <%= console %> in views gem 'web-console', '~> 2.0' @@ -38,6 +39,8 @@ group :development, :test do gem 'spring-commands-rspec' gem 'guard-rspec', require: false + + gem 'railroady' end group :development do @@ -51,6 +54,9 @@ group :development do gem 'capistrano' gem 'rvm-capistrano', require: false gem 'capistrano-sidekiq', require: false + gem 'capistrano-maintenance', '0.0.5', require: false + + gem 'active_record_query_trace' end group :test do @@ -70,6 +76,9 @@ gem 'pg' gem 'devise' gem 'devise-async' +gem 'omniauth' +gem 'omniauth-oauth2' + gem 'rolify' gem 'kaminari' @@ -79,7 +88,8 @@ gem 'figaro' gem 'bootstrap-sass' gem 'font-awesome-rails' -gem 'angularjs-rails' +#using bower instead +#gem 'angularjs-rails' # Image processing ruby wrapper for ImageMagick gem 'mini_magick' @@ -101,13 +111,32 @@ gem 'sinatra', require: false # Recurring jobs for Sidekiq gem 'sidekiq-cron' +gem 'stripe', '1.30.2' + gem 'recurrence' -# Fork de la gem avec support Attachments -gem 'mandrill_dm', github: 'AbleTech/mandrill_dm' +gem 'newrelic_rpm' -gem 'disqus_api' +# PDF +gem 'prawn' +gem 'prawn-table' + +gem 'elasticsearch-rails' +gem 'elasticsearch-model' +gem 'elasticsearch-persistence' gem 'notify_with' gem 'pundit' + +gem 'oj' + +gem 'actionpack-page_caching' +gem 'rails-observers' + +gem 'chroma' + + +gem 'protected_attributes' + +gem 'message_format' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index ec4903259..7972ef6e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,56 +1,55 @@ -GIT - remote: git://github.com/AbleTech/mandrill_dm.git - revision: 2bbb35dd81887bb915f606699d63a723b450711d - specs: - mandrill_dm (1.1.0) - mandrill-api (~> 1.0.51) - GEM remote: https://rubygems.org/ specs: aasm (4.1.0) - actionmailer (4.2.1) - actionpack (= 4.2.1) - actionview (= 4.2.1) - activejob (= 4.2.1) + actionmailer (4.2.5) + actionpack (= 4.2.5) + actionview (= 4.2.5) + activejob (= 4.2.5) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.1) - actionview (= 4.2.1) - activesupport (= 4.2.1) + actionpack (4.2.5) + actionview (= 4.2.5) + activesupport (= 4.2.5) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.1) - actionview (4.2.1) - activesupport (= 4.2.1) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionpack-page_caching (1.0.2) + actionpack (>= 4.0.0, < 5) + actionview (4.2.5) + activesupport (= 4.2.5) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.1) - activejob (4.2.1) - activesupport (= 4.2.1) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + active_record_query_trace (1.4) + activejob (4.2.5) + activesupport (= 4.2.5) globalid (>= 0.3.0) - activemodel (4.2.1) - activesupport (= 4.2.1) + activemodel (4.2.5) + activesupport (= 4.2.5) builder (~> 3.1) - activerecord (4.2.1) - activemodel (= 4.2.1) - activesupport (= 4.2.1) + activerecord (4.2.5) + activemodel (= 4.2.5) + activesupport (= 4.2.5) arel (~> 6.0) - activesupport (4.2.1) + activesupport (4.2.5) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) addressable (2.3.8) - angularjs-rails (1.3.15) - arel (6.0.0) + arel (6.0.3) autoprefixer-rails (5.1.8) execjs json awesome_print (1.6.1) + axiom-types (0.1.1) + descendants_tracker (~> 0.0.4) + ice_nine (~> 0.11.0) + thread_safe (~> 0.3, >= 0.3.1) bcrypt (3.1.10) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) @@ -59,14 +58,15 @@ GEM sass (>= 3.2.19) buftok (0.2.0) builder (3.2.2) - byebug (4.0.4) - columnize (= 0.9.0) + camertron-eprun (1.1.0) capistrano (2.15.5) highline net-scp (>= 1.0.0) net-sftp (>= 2.0.0) net-ssh (>= 2.0.14) net-ssh-gateway (>= 1.1.0) + capistrano-maintenance (0.0.5) + capistrano (~> 2.0) capistrano-sidekiq (0.5.2) capistrano sidekiq @@ -77,8 +77,12 @@ GEM mime-types (>= 1.16) celluloid (0.16.0) timers (~> 4.0.0) + chroma (0.0.1) chunky_png (1.3.4) + cldr-plurals-runtime-rb (1.0.1) coderay (1.1.0) + coercible (1.0.0) + descendants_tracker (~> 0.0.1) coffee-rails (4.1.0) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) @@ -86,7 +90,6 @@ GEM coffee-script-source execjs coffee-script-source (1.9.1) - columnize (0.9.0) compass (1.0.3) chunky_png (~> 1.2) compass-core (~> 1.0.2) @@ -103,9 +106,11 @@ GEM compass (~> 1.0.0) sass-rails (<= 5.0.1) sprockets (< 2.13) - connection_pool (2.1.3) + connection_pool (2.2.0) database_cleaner (1.4.1) debug_inspector (0.0.2) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) devise (3.4.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -116,13 +121,30 @@ GEM devise-async (0.9.0) devise (~> 3.2) diff-lcs (1.2.5) - disqus_api (0.0.4) - activesupport (>= 3.0.0) - faraday (>= 0.8) - faraday_middleware (>= 0.9) + domain_name (0.5.25) + unf (>= 0.0.5, < 1.0.0) + elasticsearch (1.0.12) + elasticsearch-api (= 1.0.12) + elasticsearch-transport (= 1.0.12) + elasticsearch-api (1.0.12) + multi_json + elasticsearch-model (0.1.7) + activesupport (> 3) + elasticsearch (> 0.4) + hashie + elasticsearch-persistence (0.1.7) + activemodel (> 3) + activesupport (> 3) + elasticsearch (> 0.4) + elasticsearch-model (>= 0.1) + hashie + virtus + elasticsearch-rails (0.1.7) + elasticsearch-transport (1.0.12) + faraday + multi_json equalizer (0.0.11) erubis (2.7.0) - excon (0.45.1) execjs (2.4.0) factory_girl (4.5.0) activesupport (>= 3.0.0) @@ -133,8 +155,6 @@ GEM i18n (~> 0.5) faraday (0.9.1) multipart-post (>= 1.2, < 3) - faraday_middleware (0.9.1) - faraday (>= 0.7.4, < 0.10) ffi (1.9.8) figaro (1.1.0) thor (~> 0.14) @@ -146,7 +166,7 @@ GEM formatador (0.2.5) friendly_id (5.1.0) activerecord (>= 4.0.0) - globalid (0.3.3) + globalid (0.3.6) activesupport (>= 4.1.0) guard (2.12.5) formatador (>= 0.2.4) @@ -162,13 +182,17 @@ GEM guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) + hashie (3.4.2) highline (1.7.1) hike (1.2.3) hitimes (1.2.2) http (0.6.4) http_parser.rb (~> 0.6.0) + http-cookie (1.0.2) + domain_name (~> 0.5) http_parser.rb (0.6.0) i18n (0.7.0) + ice_nine (0.11.1) jbuilder (2.2.12) activesupport (>= 3.0.0, < 5) multi_json (~> 1.2) @@ -176,7 +200,8 @@ GEM rails-dom-testing (~> 1.0) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (1.8.2) + json (1.8.3) + jwt (1.5.1) kaminari (0.16.3) actionpack (>= 3.0.0) activesupport (>= 3.0.0) @@ -185,27 +210,27 @@ GEM addressable (~> 2.3) letter_opener (1.3.0) launchy (~> 2.2) - libv8 (3.16.14.7) + libv8 (3.16.14.11) listen (2.10.0) celluloid (~> 0.16.0) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) - loofah (2.0.1) + loofah (2.0.3) nokogiri (>= 1.5.9) lumberjack (1.0.9) mail (2.6.3) mime-types (>= 1.16, < 3) - mandrill-api (1.0.53) - excon (>= 0.16.0, < 1.0) - json (>= 1.7.7, < 2.0) memoizable (0.4.2) thread_safe (~> 0.3, >= 0.3.1) + message_format (0.0.3) + twitter_cldr (~> 3.1) method_source (0.8.2) - mime-types (2.4.3) + mime-types (2.99) mini_magick (4.2.0) mini_portile (0.6.2) - minitest (5.5.1) - multi_json (1.11.0) + minitest (5.8.3) + multi_json (1.11.2) + multi_xml (0.5.5) multipart-post (2.0.0) naught (1.0.0) nenv (0.2.0) @@ -216,7 +241,9 @@ GEM net-ssh (2.9.2) net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) - nokogiri (1.6.6.2) + netrc (0.10.3) + newrelic_rpm (3.11.1.284) + nokogiri (1.6.6.4) mini_portile (~> 0.6.0) notiffany (0.0.6) nenv (~> 0.1) @@ -225,8 +252,28 @@ GEM jbuilder (~> 2.0) rails (>= 4.2.0) responders (~> 2.0) + oauth2 (1.0.0) + faraday (>= 0.8, < 0.10) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (~> 1.2) + oj (2.12.8) + omniauth (1.2.2) + hashie (>= 1.2, < 4) + rack (~> 1.0) + omniauth-oauth2 (1.3.1) + oauth2 (~> 1.0) + omniauth (~> 1.2) orm_adapter (0.5.0) + pdf-core (0.5.1) pg (0.18.1) + prawn (2.0.1) + pdf-core (~> 0.5.1) + ttfunk (~> 1.4.0) + prawn-table (0.2.1) + protected_attributes (1.1.3) + activemodel (>= 4.0.1, < 5.0) pry (0.10.1) coderay (~> 1.1.0) method_source (~> 0.8.1) @@ -235,38 +282,41 @@ GEM rack (>= 1.1, < 2.0) pundit (1.0.0) activesupport (>= 3.0.0) - rack (1.6.0) + rack (1.6.4) rack-protection (1.5.3) rack rack-test (0.6.3) rack (>= 1.0) - rails (4.2.1) - actionmailer (= 4.2.1) - actionpack (= 4.2.1) - actionview (= 4.2.1) - activejob (= 4.2.1) - activemodel (= 4.2.1) - activerecord (= 4.2.1) - activesupport (= 4.2.1) + railroady (1.4.0) + rails (4.2.5) + actionmailer (= 4.2.5) + actionpack (= 4.2.5) + actionview (= 4.2.5) + activejob (= 4.2.5) + activemodel (= 4.2.5) + activerecord (= 4.2.5) + activesupport (= 4.2.5) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.1) + railties (= 4.2.5) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.6) + rails-dom-testing (1.0.7) activesupport (>= 4.2.0.beta, < 5.0) nokogiri (~> 1.6.0) rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.2) loofah (~> 2.0) + rails-observers (0.1.2) + activemodel (~> 4.0) rails_12factor (0.0.3) rails_serve_static_assets rails_stdout_logging rails_serve_static_assets (0.0.4) rails_stdout_logging (0.0.3) - railties (4.2.1) - actionpack (= 4.2.1) - activesupport (= 4.2.1) + railties (4.2.5) + actionpack (= 4.2.5) + activesupport (= 4.2.5) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) raindrops (0.13.0) @@ -281,9 +331,13 @@ GEM redis (3.2.1) redis-namespace (1.5.2) redis (~> 3.0, >= 3.0.4) - ref (1.0.5) + ref (2.0.0) responders (2.1.0) railties (>= 4.2.0, < 5) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) rolify (4.0.0) rspec (3.2.0) rspec-core (~> 3.2.0) @@ -324,7 +378,7 @@ GEM activerecord (~> 4) activesupport (~> 4) shellany (0.0.1) - sidekiq (3.3.3) + sidekiq (3.3.4) celluloid (>= 0.16.0) connection_pool (>= 2.1.1) json @@ -340,19 +394,22 @@ GEM rack-protection (~> 1.4) tilt (>= 1.3, < 3) slop (3.6.0) - spring (1.3.3) + spring (1.3.5) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (2.12.3) + sprockets (2.12.4) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.2.4) + sprockets-rails (2.3.3) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) - therubyracer (0.12.1) + stripe (1.30.2) + json (~> 1.8.1) + rest-client (~> 1.4) + therubyracer (0.12.0) libv8 (~> 3.16.14.0) ref thor (0.19.1) @@ -360,6 +417,7 @@ GEM tilt (1.4.1) timers (4.0.1) hitimes + ttfunk (1.4.0) twitter (5.14.0) addressable (~> 2.3) buftok (~> 0.2.0) @@ -373,6 +431,11 @@ GEM simple_oauth (~> 0.3.0) twitter-text (1.11.0) unf (~> 0.1.0) + twitter_cldr (3.2.1) + camertron-eprun + cldr-plurals-runtime-rb (~> 1.0.0) + json + tzinfo tzinfo (1.2.2) thread_safe (~> 0.1) uglifier (2.7.1) @@ -385,6 +448,11 @@ GEM kgio (~> 2.6) rack raindrops (~> 0.7) + virtus (1.0.5) + axiom-types (~> 0.1) + coercible (~> 1.0) + descendants_tracker (~> 0.0, >= 0.0.3) + equalizer (~> 0.0, >= 0.0.9) warden (1.2.3) rack (>= 1.0) web-console (2.1.2) @@ -398,19 +466,23 @@ PLATFORMS DEPENDENCIES aasm - angularjs-rails + actionpack-page_caching + active_record_query_trace awesome_print bootstrap-sass - byebug capistrano + capistrano-maintenance (= 0.0.5) capistrano-sidekiq carrierwave + chroma coffee-rails (~> 4.1.0) compass-rails (= 2.0.4) database_cleaner devise devise-async - disqus_api + elasticsearch-model + elasticsearch-persistence + elasticsearch-rails factory_girl_rails faker figaro @@ -423,13 +495,22 @@ DEPENDENCIES jquery-rails kaminari letter_opener - mandrill_dm! + message_format mini_magick + newrelic_rpm notify_with + oj + omniauth + omniauth-oauth2 pg + prawn + prawn-table + protected_attributes puma pundit - rails (= 4.2.1) + railroady + rails (= 4.2.5) + rails-observers rails_12factor recurrence responders (~> 2.0) @@ -444,9 +525,13 @@ DEPENDENCIES sinatra spring spring-commands-rspec - therubyracer + stripe (= 1.30.2) + therubyracer (= 0.12.0) twitter twitter-text uglifier (>= 1.3.0) unicorn web-console (~> 2.0) + +BUNDLED WITH + 1.10.6 diff --git a/LICENSE b/LICENSE.md similarity index 95% rename from LICENSE rename to LICENSE.md index 9591157b0..f2d5d21c0 100644 --- a/LICENSE +++ b/LICENSE.md @@ -1,3 +1,53 @@ +Copyright (C) 2015 La Casemate + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + +FabManager uses some external components, which are licenced under the +terms of [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0): + +- [jasny-bootstrap](https://github.com/jasny/bootstrap/) +- [elasticsearch](https://github.com/elasticsearch/bower-elasticsearch-js) +- [nvd3](https://github.com/novus/nvd3) +- [angular-bootstrap-switch](https://github.com/frapontillo/angular-bootstrap-switch) +- [elasticsearch-rails](https://github.com/elastic/elasticsearch-rails) +- [elasticsearch-model](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model) +- [elasticsearch-persistence](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence) +- font [Open Sans](http://www.fontsquirrel.com/fonts/open-sans) + + +Some other used libraries/components are licenced under the terms of the +[General Public License version 2](http://www.gnu.org/licenses/old-licenses/gpl-2.0-faq.en.html): + +- [ruby](https://www.ruby-lang.org) +- [railroady](https://github.com/preston/railroady) +- [unicorn](https://github.com/defunkt/unicorn) +- [prawn](https://github.com/prawnpdf/prawn) +- [prawn-table](https://github.com/prawnpdf/prawn-table) + + +Errors and omissions excepted, the other external libraries used in this +project are licenced under the terms of the [MIT Licence](https://opensource.org/licenses/MIT). +Please refer to the libraries documentation for more informations about +their licences. + +Complete lists of used libraries are available in `bower.json` for the +EcmaScript libraries and in `Gemfile` for Ruby libraries. + + + + GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 @@ -616,47 +666,4 @@ an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. - + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/Procfile b/Procfile index fdfc448f5..d6f589654 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -web: bundle exec rails server puma -p $PORT +web: bundle exec rails server puma -p $PORT -b0.0.0.0 worker: bundle exec sidekiq -C ./config/sidekiq.yml diff --git a/README.md b/README.md index ccb9bfb58..178b96fbe 100644 --- a/README.md +++ b/README.md @@ -1,148 +1,533 @@ -# README +# FabManager -This project is the FabLab Manager web application. +FabManager is the FabLab management solution. It is web-based, open-source and totally free. -The purpose of this web application is to allow users to document their FabLab projects. The FabLab also have the ability -to plan some events (workshops or courses) and to expose them to its users. -This product can be extended to be used as a complete internal management system for a FabLab. - -The underlying technologies are: -- `Ruby on Rails` for the backend application (server RESTful API) -- `AngularJS` for the frontend application (web-based graphical user interface) +##### Table of Contents +1. [Software stack](#software-stack) +2. [Contributing](#contributing) +3. [Setup a development environment](#setup-a-development-environment) +3.1 [General Guidelines](#general-guidelines) +3.2 [Environment Configuration](#environment-configuration) +4. [PostgreSQL](#postgresql) +4.1 [Install PostgreSQL 9.4 on Ubuntu/Debian](#postgresql-on-debian) +4.2 [Install and launch PostgreSQL on MacOS X](#postgresql-on-macosx) +4.3 [Setup the FabManager database in PostgreSQL](#setup-fabmanager-in-postgresql) +5. [ElasticSearch](#elasticsearch) +5.1 [Install ElasticSearch on Ubuntu/Debian](#elasticsearch-on-debian) +5.2 [Install ElasticSearch on MacOS X](#elasticsearch-on-macosx) +5.3 [Setup ElasticSearch for the FabManager](#setup-fabmanager-in-elasticsearch) +6. [Internationalization (i18n)](#i18n) +6.1 [Translation](#i18n-translation) +6.1.1 [Front-end translations](#i18n-translation-front) +6.1.2 [Back-end translations](#i18n-translation-back) +6.2 [Configuration](#i18n-configuration) +6.2.1 [Settings](#i18n-settings) +6.2.2 [Applying changes](#i18n-apply) +7. [Known issues](#known-issues) +8. [Related Documentation](#related-documentation) -## 1. Configuration + +## Software stack -The following files must be filled with the correct configuration to allow FabManager to run correctly: +FabManager is a Ruby on Rails / AngularJS web application that runs on the following software: -- config/environments/production.rb - - `mandrill` -> change this if you're using a different mailing system - -- config/environments/staging.rb - - `config.action_mailer.default_url_options` -> change the URL according to the staging deployment url - - `mandrill` -> change this if you're using a different mailing system +- Ubuntu/Debian +- Ruby 2.2.3 +- Git 1.9.1+ +- Redis 2.8.4+ +- Sidekiq 3.3.4+ +- Elasticsearch 1.7 +- PostgreSQL 9.4 -- config/application.yml - - `DEVISE_KEY` -> generate any secret phrase to secure the Devise authentication. You can use the `$ rake secret` command for this purpose. - - `SECRET_KEY_BASE` -> generate any secret phrase here to prevent XSS attacks. You can use the `$ rake secret` command for this purpose. - - `DEFAULT_MAIL_FROM` -> default e-mail address from which the emails are sent - - `MANDRILL_USERNAME` -> if you plan to use mandrill - - `MANDRILL_APIKEY` -> if you plan to use mandrill - - `TWITTER_NAME` -> twitter api configuration - - `TWITTER_CONSUMER_KEY` -> twitter api configuration - - `TWITTER_CONSUMER_SECRET` -> twitter api configuration - - `TWITTER_ACCESS_TOKEN` -> twitter api configuration - - `TWITTER_ACCESS_TOKEN_SECRET` -> twitter api configuration - - `GOOGLE_ANALYTICS_ACCOUNT` -> Google analytics account identifier (if you want to use GA) - - `APPLICATION_ROOT_URL` -> The public URL where you application is deployed in production (eg. fablab.lacasemate.com) + +## Contributing -- config/mandrill.rb - You may change this if you don't want to use mandrill as your production mailing system +Contributions are welcome. Please read [the contribution guidelines](CONTRIBUTING.md) for more information about the contribution process. -- config/database.yml.default - Copy/Paste this file to `config/database.yml` and modify the configuration according to your postgreSQL configuration +**IMPORTANT**: **do not** update Arshaw/fullCalendar.js as it contains a hack for the remove-event cross. -- config/disqus_api.yml - Insert here your identifiers for the Disqus API + +## Setup a development environment + +### General Guidelines - -## 2. Setup a development environment - -1. Install RVM with latest ruby version - See http://rvm.io/rvm/install +1. Install RVM with the ruby version specified in the [.ruby-version file](.ruby-version). + For more details about the process, Please read the [official RVM documentation](http://rvm.io/rvm/install). 2. Retrieve the project from Git - `$ git clone git@github.com:LaCasemate/fab-manager.git` -3. Install the dependencies - - Ubuntu: `$ sudo apt-get install libpq-dev postgresql redis-server imagemagick` - - MacOS: `$ brew install postgresql redis imagemagick` + ```bash + git clone https://github.com/LaCasemate/fab-manager.git + ``` + +3. Install the software dependencies. + - For Ubuntu/Debian: + + ```bash + sudo apt-get install libpq-dev postgresql-9.4 redis-server imagemagick + ``` + - For MacOS X: -4. Init the RVM instance and check it was correctly configured - ``` - $ cd fab-manager - $ rvm current - ``` + ```bash + brew install postgresql redis imagemagick + ``` + +4. Init the RVM instance and check it was correctly configured + + ```bash + cd fab-manager + rvm current + # Must print ruby-X.Y.Z@fablab (where X.Y.Z match the version in .ruby-version) + ``` -5. Setup the project requirements - `$ bundle install` +5. Install bundler in the current RVM gemset + + ```bash + gem install bundler + ``` + +6. Install the required ruby gems + + ```bash + bundle install + ``` -6. Build the database. You may have to configure your postgreSQL instance before, as described in chapter `3.2 Setup the FabManager database in PostgreSQL` - `$ rake db:setup` +7. Build the database. You may have to follow the steps described in [the PostgreSQL installation chapter](#postgresql) before, if you don't already have a working installation of PostgreSQL. + + ```bash + rake db:setup + ``` -7. Create the pids folder used by sidekiq. If you want to use a different location, you can configure it in `config/sidekiq.yml` - `$ mkdir -p tmp/pids` +8. Create the pids folder used by Sidekiq. If you want to use a different location, you can configure it in `config/sidekiq.yml` + + ```bash + mkdir -p tmp/pids + ``` + +9. Create the default configuration file **and configure it !** (see the [Environment Configuration](#environment-configuration) section) + + ```bash + cp config/application.yml.default config/application.yml + vi config/application.yml + # or use your favorite editor instead of vi (nano, ne...) + ``` -8. Configure the application environment variables, as explained in chapter `1. Configuration` - -9. Start the development web server - `$ foreman s -p 3000` +10. Start the development web server + + ```bash + foreman s -p 3000 + ``` + +11. You should now be able to access your local development FabManager instance by accessing `http://localhost:3000` in your web browser. + +12. You can login as the default administrator using the following credentials: + - user: admin@fab-manager.com + - password: adminadmin + + +### Environment Configuration + +The settings in `config/application.yml` configure the environment variables of the application. +If you are in a development environment, your can keep the default values, otherwise, in production, values must be configured carefully. + +#### POSTGRES_HOST + +DNS name or IP address of the server hosting the PostgreSQL database of the application (see [PostgreSQL](#postgresql)). + +#### POSTGRES_PASSWORD + +Password for the PostgreSQL user, as specified in `database.yml`. +Please see [Setup the FabManager database in PostgreSQL](#setup-fabmanager-in-postgresql) for informations on how to create a user and set his password. + +#### REDIS_HOST + +DNS name or IP address of the server hosting the redis database. + +#### ELASTICSEARCH_HOST + +DNS name or IP address of the server hosting the elasticSearch database. + +#### SECRET_KEY_BASE + +Used by the authentication system to generate random tokens, eg. for resetting passwords. +Used by Rails to generate the integrity of signed cookies. +You can generate such a random key by running `rake secret`. + +#### STRIPE_API_KEY & STRIPE_PUBLISHABLE_KEY + +Key an secret used to identify you Stripe account through the API. +Retrieve them from https://dashboard.stripe.com/account/apikeys. + +#### STRIPE_CURRENCY + +Currency used by stripe to charge the final customer. +See https://support.stripe.com/questions/which-currencies-does-stripe-support for a list of available 3-letters ISO code. + +#### INVOICE_PREFIX + +When payments are done on the platform, an invoice will be generate as a PDF file. +This value configure the prefix of the PDF file name. + +#### FABLAB_WITHOUT_PLANS + +If set to 'true', the subscription plans will be fully disabled and invisible in the application. + +#### DEFAULT_MAIL_FROM + +When sending notification mails, the platform will use this address to identify the sender. + +#### DELIVERY_METHOD + +Configure the Rails' Action Mailer delivery method. +See http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration for more details. + +#### DEFAULT_HOST, DEFAULT_PROTOCOL, SMTP_ADDRESS, SMTP_PORT, SMTP_USER_NAME & SMTP_PASSWORD + +When DELIVERY_METHOD is set to **smtp**, configure the SMTP server parameters. +See http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration for more details. +DEFAULT_HOST is also used to configure Google Analytics. + +#### GA_ID + +Identifier of your Google Analytics account. + +#### DISQUS_SHORTNAME + +Unique identifier of your [Disqus](http://www.disqus.com) forum. +Disquq forums are used to allow visitors to comment on projects. +See https://help.disqus.com/customer/portal/articles/466208-what-s-a-shortname- for more informations. + +#### TWITTER_NAME + +Identifier of the Twitter account, for witch the last tweet will be displayed on the home page. + +#### TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, TWITTER_ACCESS_TOKEN & TWITTER_ACCESS_TOKEN_SECRET + +Keys and secrets to access the twitter API. + +#### Settings related to i18n +See the [Settings](#i18n-settings) section of the [Internationalization (i18n)](#i18n) paragraph for a detailed description of these parameters. + +## PostgreSQL -## 3. PostgreSQL + +### Install PostgreSQL 9.4 on Ubuntu/Debian -### 3.1 Launch PostgreSQL on MacOS - - $ ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents - $ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist - - The first command will start postgresql at login with launchd. The second will load postgresql now. +1. Create the file `/etc/apt/sources.list.d/pgdg.list`, and append it one the following lines: + - `deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main` (Ubuntu 14.04 Trusty) + - `deb http://apt.postgresql.org/pub/repos/apt/ jessie-pgdg main` (Debian 8 Jessie) + -### 3.2 Setup the FabManager database in PostgreSQL +2. Import the repository signing key, and update the package lists + + ```bash + wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + sudo apt-get update + ``` + +3. Install PostgreSQL 9.4 + + ```bash + sudo apt-get install postgresql-9.4 + ``` + + +### Install and launch PostgreSQL on MacOS X + +This assumes you have [Homebrew](http://brew.sh/) installed on your system. +Otherwise, please follow the official instructions on the project's website. + + +1. Update brew and install PostgreSQL + + ```bash + brew update + brew install postgres + ``` + +2. Launch PostgreSQL + + ```bash + # Start postgresql at login with launchd + ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents + # Load PostgreSQL now + launchctl load ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist + ``` + + +### Setup the FabManager database in PostgreSQL + +Before running `rake db:setup`, you have to make sure that the user configured in [config/database.yml](config/database.yml) for the `development` environment exists. +To create it, please follow these instructions: 1. Login as the postgres user - `$ sudo -i -u postgres` -2. Run the postgreSQL administration command line interface - `$ psql` + ```bash + sudo -i -u postgres + ``` + +2. Run the PostgreSQL administration command line interface + + ```bash + psql + ``` -3. Create a new user in postgres (in this example, the user will be named "sleede") - `# CREATE USER sleede;` +3. Create a new user in PostgreSQL (in this example, the user will be named `sleede`) + + ```sql + CREATE USER sleede; + ``` 4. Grant him the right to create databases - `# ALTER ROLE sleede WITH CREATEDB;` + + ```sql + ALTER ROLE sleede WITH CREATEDB; + ``` -5. Then create the fablab database - `# CREATE DATABASE fabmanager_development OWNER sleede;` +5. Then, create the fablab_development and fablab_test databases + + ```sql + CREATE DATABASE fablab_development OWNER sleede; + CREATE DATABASE fablab_test OWNER sleede; + ``` 6. To finish, attribute a password to this user - `# ALTER USER sleede WITH ENCRYPTED PASSWORD 'sleede';` - - -## 4. Known issue + ```sql + ALTER USER sleede WITH ENCRYPTED PASSWORD 'sleede'; + ``` - You may encounter the following error message when running the application for the first time: + +## ElasticSearch - ```bash - Uncaught exception: FATAL: authentification peer échouée pour l'utilisateur « USERNAME » - Exiting - .rvm/gems/ruby-2.2.1@fabmanager/gems/activerecord-4.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:651:in `initialize' - ... - ``` - - To solve this issue, edit your `/etc/postgresql/9.4/main/pg_hba.conf` as root and replace the following: +ElasticSearch is a powerful search engine based on Apache Lucene combined with a NoSQL database used as a cache to index data and quickly process complex requests on it. + +In FabManager, it is used for the admin's statistics module and to perform searches in projects. + + +### Install ElasticSearch on Ubuntu/Debian + +For a more detailed guide concerning the ElasticSearch installation, please check the [official documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup.html) + +1. Install the OpenJDK's Java Runtime Environment (JRE). ElasticSearch recommends that you install Java 8 update 20 or later. + Please check that your distribution's version meet this requirement. ```bash - # comment over or replace... - local all all peer - # ...by the following: - local all all trust + sudo apt-get install openjdk-8-jre ``` - Then, restart postgreSQL to validate the modification (`sudo service postgresql restart`). +1. Create the file `/etc/apt/sources.list.d/elasticsearch-1.x.list`, and append it the following line: + `deb http://packages.elastic.co/elasticsearch/1.x/debian stable main` + +2. Import the repository signing key, and update the package lists + + ```bash + wget -qO - https://packages.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add - + sudo apt-get update + ``` + +3. Install ElasticSearch 1.7 + + ```bash + sudo apt-get install elasticsearch + ``` + +4. To automatically start ElasticSearch during bootup, then, depending if your system is compatible with SysV (eg. Ubuntu 14.04) or uses systemd (eg. Debian 8), you will need to run: + + ```bash + # System V + sudo update-rc.d elasticsearch defaults 95 10 + # *** OR *** (systemd) + sudo /bin/systemctl daemon-reload + sudo /bin/systemctl enable elasticsearch.service + ``` + + +### Install ElasticSearch on MacOS X + +This assumes you have [Homebrew](http://brew.sh/) installed on your system. +Otherwise, please follow the official instructions on the project's website. + +```bash +brew update +brew install homebrew/versions/elasticsearch17 +``` + + +### Setup ElasticSearch for the FabManager + +1. Launch the associated rake tasks in the project folder. + This will create the fields mappings in ElasticSearch DB + + ```bash + rake fablab:es_build_stats + ``` + +2. Every nights, the statistics for the day that just ended are built automatically at 01:00 (AM). + See [schedule.yml](config/schedule.yml) to modify this behavior. + If the scheduled task wasn't executed for any reason (eg. you are in a dev environment and your computer was turned off at 1 AM), you can force the statistics data generation in ElasticSearch, running the following commands in a rails console. + + ```bash + rails c + ``` + + ```ruby + # Here for the 200 last days + 200.times.each do |i| + StatisticService.new.generate_statistic({start_date: i.day.ago.beginning_of_day,end_date: i.day.ago.end_of_day}) + end + ``` + + +## Internationalization (i18n) + +The FabManager application can only run in a single language but this language can easily be changed. + + +### Translation + +Check the files located in `config/locales`: + +- Front app translations (angular.js) are located in `config/locales/app.scope.XX.yml`. + Where scope has one the following meaning : + - admin: translations of the administrator views (manage and configure the FabLab). + - logged: translations of the end-user's views accessible only to connected users. + - public: translation of end-user's views publicly accessible to anyone. + - shared: translations shared by many views (like forms or buttons). +- Back app translations (Ruby on Rails) are located in `config/locales/XX.yml`. +- Emails translations are located in `config/locales/mails.XX.yml`. +- Messages related to the authentication system are located in `config/locales/devise.XX.yml`. + +If you plan to translate the application to a new locale, please consider that the reference translation is French. +Indeed, in some cases, the English texts/sentences can seems confuse or lack of context as they were originally translated from French. + +To prevent syntax mistakes while translating locale files, we **STRONGLY advise** you to use a text editor witch support syntax coloration for YML and Ruby. + + +#### Front-end translations + +Front-end translations uses [angular-translate](http://angular-translate.github.io) with some interpolations interpreted by angular.js and other interpreted by [MessageFormat](https://github.com/SlexAxton/messageformat.js/). +**These two kinds of interpolation use a near but different syntax witch SHOULD NOT be confused.** +Please refer to the official [angular-translate documentation](http://angular-translate.github.io/docs/#/guide/14_pluralization) before translating. + + +#### Back-end translations + +Back-end translations uses the [Ruby on Rails syntax](http://guides.rubyonrails.org/i18n.html) but some complex interpolations are interpreted by [MessageFormat](https://github.com/format-message/message-format-rb) and are marked as it in comments. +**DO NOT confuse the syntaxes.** + +In each cases, some inline comments are included in the localisation files. +They can be recognized as they start with the sharp character (#). +These comments are not required to be translated, they are intended to help the translator to have some context informations about the sentence to translate. + +### Configuration + +Locales configurations are made in `config/application.yml`. +If you are in a development environment, your can keep the default values, otherwise, in production, values must be configured carefully. + + +#### Settings +##### RAILS_LOCALE + +Be sure that `config/locales/rails.XX.yml` exists, where `XX` match your configured rails_locale. +You can find templates of these files at https://github.com/svenfuchs/rails-i18n/tree/rails-4-x/rails/locale. + +Be aware that **this file MUST contain the CURRENCY symbol used to generate invoices** (among other things). +Default is **en**. + +##### MOMENT_LOCALE + +Configure the moment.js library for l10n. + +See `vendor/assets/components/moment/locale/*.js` for a list of available locales. +Default is **en** (even if it's not listed). + +##### SUMMERNOTE_LOCALE + +Configure the javascript summernote editor for l10n. + +See `vendor/assets/components/summernote/lang/summernote-*.js` for a list of available locales. +Default is **en-US** (even if it's not listed). + +##### ANGULAR_LOCALE + +Configure the locale for angular-i18n. + +Please, be aware that **the configured locale will imply the CURRENCY displayed to front-end users.** + +_Eg.: configuring **fr-fr** will set the currency symbol to **€** but **fr-ca** will set **$** as currency symbol, so setting the `angular_locale` to simple **fr** (without country indication) will probably not do what you expect._ + +See `vendor/assets/components/angular-i18n/angular-locale_*.js` for a list of available locales. Default is **en**. + +##### MESSAGEFORMAT_LOCALE + +Configure the messageformat.js library, used by angular-translate. + +See vendor/assets/components/messageformat/locale/*.js for a list of available locales. + +##### FULLCALENDAR_LOCALE + +Configure the fullCalendar JS agenda library. + +See `vendor/assets/components/fullcalendar/dist/lang/*.js` for a list of available locales. Default is **en** (even if it's not listed). + +##### ELASTICSEARCH_LANGUAGE_ANALYZER + +This configure the language analyzer for indexing and searching in projects with ElasticSearch. +See https://www.elastic.co/guide/en/elasticsearch/reference/1.7/analysis-lang-analyzer.html for a list of available analyzers (check that the doc version match your installed elasticSearch version). + +##### TIME_ZONE + +In Rails: set Time.zone default to the specified zone and make Active Record auto-convert to this zone. Run `rake time:zones:all` for a list of available time zone names. +Default is **UTC**. + +##### WEEK_STARTING_DAY + +Configure the first day of the week in your locale zone (generally monday or sunday). + +##### D3_DATE_FORMAT +Date format for dates displayed in statistics charts. +See https://github.com/mbostock/d3/wiki/Time-Formatting#format for available formats. -## 5. Related Documentation -- Angular-Bootstrap: http://angular-ui.github.io/bootstrap/ + +#### Applying changes + +After modifying any values concerning the localisation, restart the application (ie. web server) to apply these changes in the i18n configuration. -## 6. Translations -- French translation is available on the branches [master](../../tree/master) and [dev](../../tree/dev) -- English translation is available on the branch [english](../../tree/english) + +## Known issues + +- When browsing a machine page, you may encounter an "InterceptError" in the console and the loading bar will stop loading before reaching its ending. + This may append if the machine was created through a seed file without any image. + To solve this, simply add an image to the machine's profile and refresh the web page. + +- When starting the Ruby on Rails server (eg. `foreman s`) you may receive the following error: + + worker.1 | invalid url: redis::6379 + web.1 | Exiting + worker.1 | ...lib/redis/client.rb...:in `_parse_options' + + This may happens when the `application.yml` file is missing. + To solve this issue copy `config/application.yml.default` to `config/application.yml`. + This is required before the first start. + + + +## Related Documentation + +- [Ruby 2.2.3](http://ruby-doc.org/core-2.2.3/) +- [Ruby on Rails](http://api.rubyonrails.org) +- [AngularJS](https://docs.angularjs.org/api) +- [Angular-Bootstrap](http://angular-ui.github.io/bootstrap/) +- [ElasticSearch 1.7](https://www.elastic.co/guide/en/elasticsearch/reference/1.7/index.html) + diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 000000000..0635c8db5 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,73 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure(2) do |config| + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. + + # Every Vagrant development environment requires a box. You can search for + # boxes at https://atlas.hashicorp.com/search. + config.vm.box = "ubuntu/trusty64" + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + # config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + config.vm.network "forwarded_port", guest: 3000, host: 3000 # rails/puma + config.vm.network "forwarded_port", guest: 9200, host: 9200 # elasticsearch + config.vm.network "forwarded_port", guest: 5432, host: 5432 # postgreSQL + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + # config.vm.network "private_network", ip: "192.168.33.10" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + config.vm.provider "virtualbox" do |vb| + # Display the VirtualBox GUI when booting the machine + # vb.gui = true + + # Customize the amount of memory on the VM: + vb.memory = 512 + end + # + # View the documentation for the provider you are using for more + # information on available options. + + # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies + # such as FTP and Heroku are also available. See the documentation at + # https://docs.vagrantup.com/v2/push/atlas.html for more information. + # config.push.define "atlas" do |push| + # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" + # end + + # Enable provisioning with a shell script. Additional provisioners such as + # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the + # documentation for more information about their specific syntax and use. + # config.vm.provision "shell", inline: <<-SHELL + # sudo apt-get update + # sudo apt-get install -y apache2 + # SHELL +end diff --git a/app/assets/images/about-fablab.jpg b/app/assets/images/about-fablab.jpg deleted file mode 100644 index 4f8abb2dd85c01e7b014ca63423c9844fe17439c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20842 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 diff --git a/app/assets/images/fablab-logo.png b/app/assets/images/fablab-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fda34d2f7a34f20603eb58858762fb79c1ec5a06 GIT binary patch literal 4453 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 C5aMe9 literal 0 HcmV?d00001 diff --git a/app/assets/images/fablab-w.png b/app/assets/images/fablab-w.png index e3f4e140cface97999d878080d081dd100781a97..8dd35ef8c1c59d824a2339a00a8b0594e0befc81 100644 GIT binary patch delta 2500 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 S=M%tEYN$S^eWCsA-TwgoOTtS4 literal 14825 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 diff --git a/app/assets/images/fablab.png b/app/assets/images/fablab.png index 680b29cad2cf16f2b6a074b395b5ae8bd4c84afe..879416cd1c83e0fb728b8de5cc4c595ee88d2b95 100644 GIT binary patch delta 2819 zcmZWrdo&Y{`<}VqBiCgZKDnjjS|hn^Qk2bQm~w3*DpHyyWA3A+!iO+SB)MBDCauXe zce#dzT!*>mQoj2A_xpbTdCz;^_c`Zz&U@Z-o~K75NeYsHxN<>Q03-ka0E91E!R!D4 z4&%cf&Bt?C|Cw0Wdt`|Onu*l>P0N5rE5!Gy88{eoC8*#z5cp!CO+L?A#g zj(U}K#gt_?wVI#c1^8r6pYk&7aGycSc~PW=4mbd5p;&pM`A8op!1*{=4P=Di+6iJa>A&|eko@+J@{*FQ%Nb>kt^U*< zeG%)lFU3u?y%Yo7&-uO9Pq(Stheb#}=+Ri_PwNbf zv=_cOtJgF~M)mUkwn~Z4)7<-MQHIGT;tf#SDYV&wqZKth?79k$#h^5JmozDbT@vh6SA-2gqLdSJcX_`sc6z^G7gz zqe=2a*R!f(F3PqXm+0IUFo4a)lc&1=0lyX?F1b1Bk}?saW^0QTxzpSaW{uR_yN=h5 zELAmp+%tn>kh3ZVou$vHI=AoB}3E5A3U$k^ivV6&WwUz6*sO~UrFY0f5=P>ZYsF15w9l>5q>Vr09dV5+xjN?3S2PQr^v-LOr+{{NE4rZZVAV;E z$_TB{!CH1YO%-%=8M;wHb&`hTeXSWzslaosah(nI;oqm*7TNjwO?RsGu0wM=Lo0dq z?xg-se&FYsPyG@+u`#Z{?7SM~?K=XQOVQAwOyo58ee6?O=4pOE`O=QHn>_3Lp4lVy z(nXLKal$y#@_RPZgWQTAOjhL^KNznl>r8rg0Jg69X4}+ybl^u!bbrqWW35Sv0Q>eV z4|h!8{f8nmNX>Uc)|aktk!uvOr)|{aaIVp+?6G%+>&1myqiS+XEdj5cGDIo_>4~2G zIvE5Uhf+IIN>Beit^UZwDyf8S*|olV-lqlj=&Qb8i28&7}UT0U=vPPxO@+e^TVvq#o@Wb#^pvb((-MmW+(evtav9H53PH zS;bN{hz|z018?BD!Y_p6E5|Ct;EFY&PDV70wk};cZ*Q|8-{Rrz`LelwnZ&23*c0wy ziz;=v`QEf`KlQKV?TJX+qL8^k5Vn*r3*W=suW=4BbG}^aBp!SFh^DrdWJkNb146Lj zX{(%k!q~2N@KX*RtD?^H4`By8QBUxSH(D87J)tt^H(s>$X`d-U z+~IRFUAfZFShoPb5J7faWl;))bi|CsbJUOv%E!bo{Y?j&@kGkKAzcqsxUWnHg_sRX zKXtxnqReCLg>(5*uAA~8ztrx@rKS%#vIw7w@MyN5*gz7f@j3S<>w0vNw1W3WS(2MTbP&)tL)A3&b}#2G0l6jTmo%fiy19?cw>I-%eVv5I*v|0y z2f3dSzr}#*zDcn$k_-AGTNByi^Gc4zu=Mf)na!J%6=^IGWIVFh^GT8_LbpQ5$9a|P ze|mth_-S;}m*$4jqrgN7vonG3ROWuXRZdfg$KiqGyf0=J-+8#owa51q1wBRO3A`&; zijiEvA*t0uA$b#&pv0;()!VN?!t7W*?N9%g<3ng4>FZhRXY~YXkJW&@5tKnkNtCY% z{pc#8tDP|GT0MM5g%PH&98wZbj^eU)q`k8wG+*n>1sXc!G~#QXR5P* zYnzn5l{z**@9`42Cw6hj0Zz8?8^$f^teze0pqjD2=ciROePmHZOGKuh(3W4+5Q!p0 z50>tVS+s84@&3A;%yx`pUD`#bGlKE{XEc?8P|ysrW9Mb-!%U^zmd3L}E6Hr37RFJ! z1L4YEsRcViYaE=Xp;pv}#bzo!v(Gq$yjEWLX@} z(+!gJN3 zzhe`|hPQ=AEA15aFF3RRx`d`XS?o`T8mucL_RUyX8WlRp{R{XRi<%^<^RqoUiE+oN z{!&imHY1PdE5wr+ypotf9%qJVInNO(V;rfm=*GpCia)=y)`EL2qV8o4ik$ zvx2wfHy3Uo%0oM+kgBMko1hQx!33v36~d!7P;HVb^JljV2p{QW=>O)1q0bKgH+zI7 zMX+j}Phdm<+I^`v?8I&Kv~_08aYy?w_mnE8{%-6^=sc_a>Wzrnrr2&zhHIrwvD2`B z%Mi?B5-kx`rsVlRgQcW?u=ELKA7`^T7PA+0x9g3p148WeMDXhVX$ek1NCZUd|D_H5 ze`OzGj~_uKUp^$crLB#)b+4e_(hUR2G#Lv$t$%H*u!o9^iQ`QH`3@+Zd~VX$$2Yxr z-(k$09nmMu1y0VtE+xgBAFfM(GcHo9d`15KM)3CZezV>3o zgOP%Qo&qSkz^aF2Da!RDHE9X)-sm#*)RaW<>7CCmVpH6NQ+G*I2es@0%H zsYSNAIW%a(L?Zg9`OW9shfnyC^to420yWZdLeZTj6*YjIRWY^uY zGU_rA2t?M|3GWV$ecbA84}K6hL}%g<fU^EUb80(HFevJjLaEK5dkBvp4qN1XZQKm>1=Mc&SgTbJT(I_<92=p-G3Ya_! z--yZ8{>Fd@xKs|E&7-rJ@Kr|2K~^LWhX6DEa|;aicUmU*Yni};q4*Rw$^>b=x}|T9 zB+~zPWiY;bb9wH-U-|w|Vy>rv4WQfsE-R8l1rIJ*do>gr>&O8pJQl~3#R~scMREv> z$Kr;t*l|F}3=J#j~i941me|hNb<5b@(B6wILX6FftzC&<_JNB8SC* zf7v*e{^MLMf5`V0OZ#yyRzJj|z+_OX$NOK${mlgS(CYMi^MZ@-?GG@)p67tg9hQ}M z90FNa=Ztso9D(3d?Q%q0TV>h!*w_)RkB?Ke>^^T1715tfrViYflh_<@ zJdm^8dEj+^hip%8a#_I0Y*pF!gltnoJm(K%087ZGIEK02&)Hep%Bl{6+1s zT>Wh4_1URfLU_k{f&bdc(COyuX9pBJZFXBKcyHM^He*0yyWW(?7B0#|%Uv>&=!x_= zeq(3pD?_B)6-2iZbji*y@tyxD=RsM_!tfk&&J2st#u4xk9=t43ixR*M=|mZ zUGM$wLE=8<>zu7FNmGCCw7bs#gF(cT&7o_DsxLl!dossK6JC*3x=znttMI8@wk}`P z;iTN7y0RNUAqq<|1^kQ#(W@0(qTgK&tmpCBPhE?SO$E@wTkWC~ew#?bvC7g^rIMbj z)ADnvqV+N{E%ZH7qNpJ~T`A%(`*3+Xm-TEmDLV=eZ;!lQy~}5p`MDS4`cSJmtCrau zRr6D~%R|(MZiUAUCpREcFa>2<)6H4sc8bNMql6csQ+5(zY;H{2gM-NpgIb;&$ny2x z@v+WawI+iB^}ZpAHPj1QU(!?Qdi&RaqEjj5qTO;2$Ww*CxRJfQFpIXE;8pb8fIGIA zU+Uc78K}*GnRwZ@o;>#XA45-c6^J!&Ut^EPrg=?i zPv4@i)~0xlbyP+hTCs}JnIkw+6>r6EcBy(XH$&HOXAaBvXS1!aiLm155pH;Q)fM1` z(w&=wZZIh%bad#$$PF2#OV@s)r7>$R8;yyHclxLGoo^QP`3|P)?9l!>@g*F5HFY9D z(lWVKG|IV(kdB;Ouqu*6Trlt7{H#Hm`jD{)IMG{sg%c}*A8HM2JF~`B(OPKRBV)GB95tn7wJLgFT zWis6#;9@z2N1uMS2ESWk{jIxXLe=&WseJ3m3l?cF1zmH2dK;w}E5io&&&&wRoHXM@ zliFO0&Kl1B)A=qAj}-Jlr3NX*{+7Y5ts=i<-qiu9=fF?i+6511vZhYuOJ!OmEZ=@* z3=Q45SgsbDNuO8ix7Iuv-u`I;daNy1{Zh?@QmQN5H+Pwolva{`D@sOlgy#MG{8sU2 z^bu3ZuQ!zmZ8L%Cr-Zvuz5>`xl$PA~gYmW|pAeO^2CXp_g#GO~ai;rVengA(fo1hZ zpKJ4tiGgN{;v0Kv+alfw>e7?+Wm{e`%?0;9)q6tEnK^0t3f;#w4L_9U-FZez$mpmT zeo(5vHY9I&Us(HZ$+&2hf`WOkt_ejqUmfF?rzz1Ym8WAh#m|~tOH*Q1^e!ecLnY6X z<3247?$}FRy4}T;R%AGcT8@gn2>Y`{oc;tAjEc9%N`TeIh1Tfc@aKH8h^vSCpE z@v(*9YZqDmfw;T)aayInI>hPUi8>HaB0yOHB?2l9N(87DphSRb@wd!p&aFtRLbw}b VVY$TS$E!bi&IDI{>8_yo{{Rc@PGkT8 diff --git a/app/assets/images/la_casemate-logo.png b/app/assets/images/la_casemate-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0779d3b809fe7279b6fc5b45696c31eb07b1c165 GIT binary patch literal 20809 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 D27d(p literal 0 HcmV?d00001 diff --git a/app/assets/images/select2/select2-spinner.gif b/app/assets/images/select2/select2-spinner.gif deleted file mode 100644 index 5b33f7e54f4e55b6b8774d86d96895db9af044b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1849 zcma*odr(tX9tZI2z31lM+(&YVk%mZ}5P~KlG2s=WSbGzm0!x7^P##Mnh7t-jP!X0Q zk_SQ}Po-L1tlDK;6l?(>v)e5ZBQx4|Y-Q?nr@Px3?9h(3ZWr3^tj=`TP57gKr87N$ zp2wWee1GRRCwo_xahnw)5cxNPJbCg2L6DV|6`#+yw6v6!mDS$f9-JvFD^n;GQ&UrZ zzh5jCkByB101O60U0q#p_1BM>Cv-vP?&s4@g_((4_1L=L$(a91)0=J91Gas#R{McE znYG^9*0A5YZ>#;~+Wkn(W5B0^yELIYLP!K}mB~<)AM@1&nqekynuaEGqPrzoH|KodRXJy)%+w_fu3nE5>@Bd_b zqC$EQ;{c`T&?EsNO|igL9gC7Ygxv?aQUEXMq?~>wg{EyW;VcJ37CUF#HjrT=KQO_* zS>M9yydXk18D(+QDJ1>r);Lav_uYKp$T?4vr{Q$lTo&pKv^?(>L-)G2*lwH!Ah7k? z7oH<8h-(KTKt5V6$8gF)C7Io&P5=SjTh)=zV=E2EUhQZP##L8S{d%UK>>+y82>+FV+#^BzW7u3F)Bb>=lYQ%%j`F>ASe zo*cw@V#u6T`A2He;70mR(V&iV&-7{qP~=SRf&jm9-T{*ZeZ}$rd0#6c&fLG^xJcf5 z+p<`wJYgW+_s*V{uI$nMB;%8`S_3>PfGOj3Rq}@Cx^+j?rk92fANSFDBYnOqQ>Vdj z)(|$AhP4t&Lb=Gvo2#3Gl%9<=Gv`Mz?Po@P4iLF!x}GUWJICDlFk-hS^Whyh7x~VH z@0vD1>HYD4&e+~yzS*-sFR{9`{QEEZO1zg7>R&7cHts-6j!xHVdA8eI+ZlVzd%`es zJT@$#GX(gvCJ1oJN%yLBK}{V=V;seo;!w|Yte!W1%5qLNFWqvZW>h&IiH+oPT=b@E zPhGzv5=(Un*X>v`>%8h_nj^NdYcE6NHS_ifkCV$*D)Tqrbu`s;<=t<4 zAHNqNV?6(g<1PY-w@#I-WYFViz?9TrkMr)u0g`O`u|>T;k|2sV*YF^punvT;$SuTy{j3Gv)yqD!R_CF>yR)MzmmYS5v+~R zXAdD%ng9?df;wd8GxR#%3O+gz};Vo;)sK%Bj-q>Oq%R7JU-KD?vYu>#2UjaDo z&8$>5xW~?KPD_#XFToU1hIb*VOMidUr6iYiO0N|i-7s`T8!cFT`rN!^1Pt78J93i6 z5HI1wIM$94m{3SLDvISDe6$ZG1;eq_D9RTaaC>=cO{@Bs>$IlPCPJJ$h$)-3vzNUQ6OsN#_zWxey!_9%hxwH2_dEJi=yY|1c7nDm2_Lm!Cof8-R_+9UkS zcBE(o47yE)oMR(Q=dp1a2wTX5KvvGyLqlWTa7V&!A*|w|)ax~1_~aJ0=_Lilg*0iQk7#ZD EAHN$8j{pDw diff --git a/app/assets/images/select2/select2.png b/app/assets/images/select2/select2.png deleted file mode 100644 index 1d804ffb99699b9e030f1010314de0970b5a000d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 613 zcmV-r0-F7aP)#WY!I$JQV$)A5aAS1BM||2XVJl=+L1^1S1H% zM-&lx?NZpUrHhn>fk<>POqf2sh40}xxGZfc+t+#Eb(qHy9_3*1(U%t9t)QDnI#YAL(|ACV(>)>6WD-t!8tutHkdb^#3`HzoJG3A2@T`% zA|K@o*b!`R#(7)PWrMFn2))Ca3MR4(zaT`Zr61*kZK5NPnZwQszxh$fyv3?&4c>$q z2m=+yc0dRXRAsPDxF6sD;@rK4JGdR_``1S~o6Xi@2&aR6hcSrEp9HVRzEqVDqBn<1%hR=D4e1f^ra^A|34Cjc=Gny{F(o#MrvPYgZuTJOz(n)-F<| zj()qR;C={)N<0RRvDZ^@6ND+W*}gh-Lip(MDt!(zMSO)!j2j+*hxgzC-e3$@(O2p* zu;+gddm(cZwXTCLx*Ky4THOa*^b^F`woveIeCK^0aR|TJ00000NkvXXu0mjfA#WC6 diff --git a/app/assets/images/select2/select2x2.png b/app/assets/images/select2/select2x2.png deleted file mode 100644 index 4bdd5c961d452c49dfa0789c2c7ffb82c238fc24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 845 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 Y(LFv+R{45R^vYe_f@DK1G(8^vKR?Vrc>n+a literal 0 HcmV?d00001 diff --git a/app/assets/javascripts/app.js.erb b/app/assets/javascripts/app.js.erb index a908c3b56..6d87c37c8 100644 --- a/app/assets/javascripts/app.js.erb +++ b/app/assets/javascripts/app.js.erb @@ -16,48 +16,73 @@ Application.Directives = angular.module('application.directives', []); angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ngAnimate', 'ngCookies', 'ui.router', 'ui.bootstrap', 'ngUpload', 'duScroll', 'application.filters','application.services', 'application.directives', - 'application.constants', 'application.controllers', 'application.router', 'ui.select2', 'angularMoment', - 'Devise', 'DeviseModal', 'angular-growl', 'xeditable', 'checklist-model', 'unsavedChanges', 'angular-loading-bar', - 'ngTouch', 'angular-google-analytics', 'angularUtils.directives.dirDisqus', 'summernote']). -config(['$locationProvider', '$httpProvider', 'AuthProvider', "growlProvider", "unsavedWarningsConfigProvider", "AnalyticsProvider", "datepickerPopupConfig", - function($locationProvider, $httpProvider, AuthProvider, growlProvider, unsavedWarningsConfigProvider, AnalyticsProvider, datepickerPopupConfig) { + 'frapontillo.bootstrap-switch', 'application.constants', 'application.controllers', 'application.router', + 'ui.select', 'ui.calendar', 'angularMoment', 'Devise', 'DeviseModal', 'angular-growl', 'xeditable', + 'checklist-model', 'unsavedChanges', 'angular-loading-bar', 'ngTouch', 'angular-google-analytics', + 'angularUtils.directives.dirDisqus', 'summernote', 'elasticsearch', 'angular-medium-editor', 'naif.base64', + 'minicolors', 'pascalprecht.translate']). +config(['$httpProvider', 'AuthProvider', "growlProvider", "unsavedWarningsConfigProvider", "AnalyticsProvider", "uibDatepickerPopupConfig", "$provide", "$translateProvider", + function($httpProvider, AuthProvider, growlProvider, unsavedWarningsConfigProvider, AnalyticsProvider, uibDatepickerPopupConfig, $provide, $translateProvider) { - <% if Rails.env.production? and ENV["GOOGLE_ANALYTICS_ACCOUNT"] != 'UA-YOUR_ID_HERE' and ENV["GOOGLE_ANALYTICS_ACCOUNT"] != nil %> - AnalyticsProvider.setAccount('<%= ENV["GOOGLE_ANALYTICS_ACCOUNT"] %>'); + // Google analytics + <% if Rails.env.production? %> + AnalyticsProvider.setAccount(Fablab.gaId); // track all routes (or not) AnalyticsProvider.trackPages(true); - AnalyticsProvider.setDomainName('<%= ENV["APPLICATION_ROOT_URL"] %>'); + AnalyticsProvider.setDomainName(Fablab.defaultHost); AnalyticsProvider.useAnalytics(true); AnalyticsProvider.setPageEvent('$stateChangeSuccess'); <% else %> - AnalyticsProvider.setAccount('DISABLED'); + AnalyticsProvider.setAccount('DISABLED'); <% end %> - datepickerPopupConfig.closeText = "Fermer"; - datepickerPopupConfig.cleartext = "Effacer"; - datepickerPopupConfig.currentText = "Aujourd'hui"; + // Custom messages for the date-picker widget + uibDatepickerPopupConfig.closeText = Fablab.translations.app.shared.buttons.close; + uibDatepickerPopupConfig.clearText = Fablab.translations.app.shared.buttons.clear; + uibDatepickerPopupConfig.currentText = Fablab.translations.app.shared.buttons.today; - // custom message for angular-unsavedChanges - unsavedWarningsConfigProvider.navigateMessage = "Vous perdrez les modifications non enregistrées si vous quittez cette page"; - unsavedWarningsConfigProvider.reloadMessage = "Vous perdrez les modifications non enregistrées si vous rechargez cette page"; + // Custom messages for angular-unsavedChanges + unsavedWarningsConfigProvider.navigateMessage = Fablab.translations.app.shared.messages.you_will_lose_any_unsaved_modification_if_you_quit_this_page; + unsavedWarningsConfigProvider.reloadMessage = Fablab.translations.app.shared.messages.you_will_lose_any_unsaved_modification_if_you_reload_this_page; + // Set how long the popup messages (growl) will remain growlProvider.globalTimeToLive(5000); - growlProvider.globalEnableHtml(true); - $locationProvider.hashPrefix('!'); + // Configure the i18n module to load the partial translations from the given API URL + $translateProvider.useLoader('$translatePartialLoader', { + urlTemplate: '/api/translations/{lang}/{part}' + }); + // Enable the cache to speed-up the loading times on already seen pages + $translateProvider.useLoaderCache(true); + // Secure i18n module against XSS attacks by escaping the output + $translateProvider.useSanitizeValueStrategy('escapeParameters'); + // Enable the MessageFormat interpolation (used for pluralization) + $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); + // Set the langage of the instance (from ruby configuration) + $translateProvider.preferredLanguage(Fablab.locale); - }]).run(["$rootScope", "$log", "AuthService", "Auth", "amMoment", "$state", "editableOptions", "$location", "Analytics", function($rootScope, $log, AuthService, Auth, amMoment, $state, editableOptions, $location, Analytics){ + }]).run(["$rootScope", "$log", "AuthService", "Auth", "amMoment", "$state", "editableOptions", + function($rootScope, $log, AuthService, Auth, amMoment, $state, editableOptions) { - amMoment.changeLocale('fr'); + // Angular-moment (date-time manipulations library) + amMoment.changeLocale(Fablab.moment_locale); + // Angular-xeditable (click-to-edit elements, used in admin backoffice) editableOptions.theme = 'bs3'; + // Alter the UI-Router's $state, registering into some informations concerning the previous $state. + // This is used to allow the user to navigate to the previous state $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ $state.prevState = fromState; $state.prevParams = fromParams; }); + // Global config: if true, the whole 'Plans & Subscriptions' feature will be disabled in the application + $rootScope.fablabWithoutPlans = Fablab.withoutPlans; + + // Global function to allow the user to navigate to the previous screen (ie. $state). + // If no previous $state were recorded, navigate to the home page $rootScope.backPrevLocation = function(event){ event.preventDefault(); event.stopPropagation(); @@ -67,8 +92,9 @@ config(['$locationProvider', '$httpProvider', 'AuthProvider', "growlProvider", " $state.go($state.prevState, $state.prevParams); }; + // Configuration of the summernote editor (used in project edition) $rootScope.summernoteOpts = { - lang: 'fr-FR', + lang: Fablab.summernote_locale, height: 200, toolbar: [ ['style', ['style']], @@ -85,25 +111,16 @@ config(['$locationProvider', '$httpProvider', 'AuthProvider', "growlProvider", " maximumImageFileSize: 4096 }; - }]).filter('array', function() { - return function(arrayLength) { - if (arrayLength) { - arrayLength = Math.ceil(arrayLength); - var arr = new Array(arrayLength), i = 0; - for (; i < arrayLength; i++) { - arr[i] = i; - } - return arr; - } - }; -}).directive('datepickerPopup', function (){ - // fixes https://github.com/angular-ui/bootstrap/issues/2659 - return { - restrict: 'EAC', - require: 'ngModel', - link: function(scope, element, attr, controller) { - //remove the default formatter from the input directive to prevent conflict - controller.$formatters.shift(); - } - } -}); + // Prevent the usage of the application for members with incomplete profiles: they will be redirected to + // the 'profile completion' page. This is especially useful for user's accounts imported through SSO. + $rootScope.$on('$stateChangeStart', function (event, toState) { + Auth.currentUser().then(function(currentUser) { + if (currentUser.need_completion && toState.name != 'app.logged.profileCompletion') { + $state.go('app.logged.profileCompletion'); + } + }); + }); + + }]).constant('angularMomentConfig', { + timezone: Fablab.timezone + }); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 2a275ace3..55ab68983 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -20,7 +20,6 @@ //= require jquery-ui/ui/jquery.ui.droppable //= require jquery-ui/ui/jquery.ui.resizable //= require angular -//= require angular-i18n/angular-locale_fr-fr.js //= require angular-cookies //= require angular-resource //= require angular-sanitize @@ -29,18 +28,18 @@ //= require angular-touch //= require angular-ui-router/release/angular-ui-router //= require angular-bootstrap/ui-bootstrap-tpls -//= require select2/select2 -//= require select2/select2_locale_fr -//= require angular-ui-select2/src/select2 +//= require angular-ui-select/dist/select //= require moment/moment -//= require moment/locale/fr +//= require moment-timezone/builds/moment-timezone-with-data-2010-2020 +//= require angular-ui-calendar/src/calendar +//= require fullcalendar/dist/fullcalendar //= require angular-moment/angular-moment //= require ngUpload/ng-upload //= require jasny-bootstrap/js/fileinput //= require holderjs/holder //= require angular-devise/lib/devise //= require devise-modal -//= require angular-growl/build/angular-growl +//= require angular-growl-v2/build/angular-growl //= require angular-xeditable/dist/js/xeditable //= require checklist-model/checklist-model //= require angular-unsavedChanges/src/unsavedChanges @@ -50,13 +49,25 @@ //= require dirDisqus //= require humanize //= require underscore/underscore +//= require elasticsearch/elasticsearch.angular +//= require d3/d3 +//= require nvd3/build/nv.d3.js //= require app //= require router +//= require medium-editor/dist/js/medium-editor +//= require angular-medium-editor/dist/angular-medium-editor +//= require bootstrap-switch/dist/js/bootstrap-switch.min +//= require angular-bootstrap-switch/dist/angular-bootstrap-switch.min +//= require angular-base64-upload/dist/angular-base64-upload.min +//= require summernote/dist/summernote +//= require angular-summernote/dist/angular-summernote +//= require jquery-minicolors/jquery.minicolors.js +//= require angular-minicolors/angular-minicolors.js +//= require angular-translate/angular-translate +//= require angular-translate-loader-partial/angular-translate-loader-partial +//= require messageformat/messageformat +//= require angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat //= require_tree ./controllers //= require_tree ./services //= require_tree ./directives //= require_tree ./filters -//= require summernote/dist/summernote -//= require summernote/lang/summernote-fr-FR -//= require summernote/plugin/summernote-ext-video -//= require angular-summernote/dist/angular-summernote diff --git a/app/assets/javascripts/controllers/about.coffee b/app/assets/javascripts/controllers/about.coffee new file mode 100644 index 000000000..f91ab061f --- /dev/null +++ b/app/assets/javascripts/controllers/about.coffee @@ -0,0 +1,19 @@ +'use strict' + +Application.Controllers.controller "AboutController", ['$scope', 'Setting', 'CustomAsset', ($scope, Setting, CustomAsset)-> + + ### PUBLIC SCOPE ### + + Setting.get { name: 'about_title'}, (data)-> + $scope.aboutTitle = data.setting + + Setting.get { name: 'about_body'}, (data)-> + $scope.aboutBody = data.setting + + Setting.get { name: 'about_contacts'}, (data)-> + $scope.aboutContacts = data.setting + + # retrieve the CGU + CustomAsset.get {name: 'cgu-file'}, (cgu) -> + $scope.cgu = cgu.custom_asset +] diff --git a/public/favicon.ico b/app/assets/javascripts/controllers/admin/admins.coffee similarity index 100% rename from public/favicon.ico rename to app/assets/javascripts/controllers/admin/admins.coffee diff --git a/app/assets/javascripts/controllers/admin/authentications.coffee b/app/assets/javascripts/controllers/admin/authentications.coffee new file mode 100644 index 000000000..708704211 --- /dev/null +++ b/app/assets/javascripts/controllers/admin/authentications.coffee @@ -0,0 +1,221 @@ +'use strict' + +### COMMON CODE ### + +## list of supported authentication methods +METHODS = { + 'DatabaseProvider' : 'Base de données locale', + #'OAuthProvider' : 'OAuth 1.0', + 'OAuth2Provider' : 'OAuth 2.0', + #'LdapProvider' : 'LDAP' +} + +## +# Iterate through the provided array and return the index of the requested element +# @param elements {Array} array of objects with property 'id' +# @param id {Number} id of the element to retrieve in the list +# @returns {Number} index of the requested element, in the provided array +## +findIdxById = (elements, id)-> + (elements.map (elem)-> + elem.id + ).indexOf(id) + + + +## +# For OAuth2 ententications, mapping the user's ID is mendatory. This function will check that this mapping +# is effective and will return false otherwise +# @param mappings {Array} expected: $scope.provider.providable_attributes.o_auth2_mappings_attributes +# @returns {Boolean} true if the mapping is declared +## +check_oauth2_id_is_mapped = (mappings) -> + for mapping in mappings + if mapping.local_model == 'user' and mapping.local_field == 'uid' and not mapping._destroy + return true + return false + + + +## +# Page listing all authentication providers +## +Application.Controllers.controller "AuthentificationController", ["$scope", "$state", "$rootScope", "dialogs", "growl", "authProvidersPromise", 'AuthProvider', '_t' +, ($scope, $state, $rootScope, dialogs, growl, authProvidersPromise, AuthProvider, _t) -> + + ### PUBLIC SCOPE ### + + ## full list of authentication providers + $scope.providers = authProvidersPromise + + + + ## + # Translate the classname into an explicit textual message + # @param type {string} Ruby polymorphic model classname + # @returns {string} + ## + $scope.getType = (type) -> + text = METHODS[type] + if typeof text != 'undefined' + return text + else + return _t('unknown')+type + + + + ## + # Translate the status string into an explicit textual message + # @param status {string} active | pending | previous + # @returns {string} + ## + $scope.getState = (status) -> + switch status + when 'active' then _t('active') + when 'pending' then _t('pending') + when 'previous' then _t('previous_provider') + else _t('unknown')+status + + + + ## + # Ask for confirmation then delete the specified provider + # @param providers {Array} full list of authentication providers + # @param provider {Object} provider to delete + ## + $scope.destroyProvider = (providers, provider) -> + dialogs.confirm + resolve: + object: -> + title: _t('confirmation_required') + msg: _t('do_you_really_want_to_delete_the_TYPE_authentication_provider_NAME', {TYPE:$scope.getType(provider.providable_type), NAME:provider.name}) + , -> + # the admin has confirmed, delete + AuthProvider.delete id: provider.id + , -> + providers.splice(findIdxById(providers, provider.id), 1) + growl.success(_t('authentication_provider_successfully_deleted')) + , -> + growl.error(_t('an_error_occurred_unable_to_delete_the_specified_provider')) + +] + + + +## +# Page to add a new authentication provider +## +Application.Controllers.controller "NewAuthenticationController", ["$scope", "$state", "$rootScope", "dialogs", "growl", "mappingFieldsPromise", "authProvidersPromise", "AuthProvider", '_t' +, ($scope, $state, $rootScope, dialogs, growl, mappingFieldsPromise, authProvidersPromise, AuthProvider, _t) -> + + $scope.authMethods = METHODS + + $scope.mappingFields = mappingFieldsPromise + + $scope.mode = 'creation' + + $scope.provider = { + name: '', + providable_type: '', + providable_attributes: {} + } + + + ## + # Initialize some provider's specific properties when selecting the provider type + ## + $scope.updateProvidable = -> + # === OAuth2Provider === + if $scope.provider.providable_type == 'OAuth2Provider' + if typeof $scope.provider.providable_attributes.o_auth2_mappings_attributes == 'undefined' + $scope.provider.providable_attributes['o_auth2_mappings_attributes'] = [] + # Add others providers initializers here if needed ... + + + + ## + # Validate and save the provider parameters in database + ## + $scope.registerProvider = -> + # === DatabaseProvider === + if $scope.provider.providable_type == 'DatabaseProvider' + # prevent from adding mode than 1 + for provider in authProvidersPromise + if provider.providable_type == 'DatabaseProvider' + growl.error _t('a_local_database_provider_already_exists_unable_to_create_another') + return false + AuthProvider.save auth_provider: $scope.provider, (provider) -> + growl.success _t('local_provider_successfully_saved') + $state.go('app.admin.members') + # === OAuth2Provider === + else if $scope.provider.providable_type == 'OAuth2Provider' + # check the ID mapping + unless check_oauth2_id_is_mapped($scope.provider.providable_attributes.o_auth2_mappings_attributes) + growl.error(_t('it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider')) + return false + # discourage the use of unsecure SSO + unless $scope.provider.providable_attributes.base_url.indexOf('https://') > -1 + dialogs.confirm + size: 'l' + resolve: + object: -> + title: _t('security_issue_detected') + msg: _t('beware_the_oauth2_authenticatoin_provider_you_are_about_to_add_isnt_using_HTTPS') + + _t('this_is_a_serious_security_issue_on_internet_and_should_never_be_used_except_for_testing_purposes') + + _t('do_you_really_want_to_continue') + , -> # unsecured http confirmed + AuthProvider.save auth_provider: $scope.provider, (provider) -> + growl.success _t('unsecured_oauth2_provider_successfully_added') + $state.go('app.admin.members') + else + AuthProvider.save auth_provider: $scope.provider, (provider) -> + growl.success _t('oauth2_provider_successfully_added') + $state.go('app.admin.members') + + + + ## + # Changes the admin's view to the members list page + ## + $scope.cancel = -> + $state.go('app.admin.members') + +] + + + +## +# Page to edit an already added authentication provider +## +Application.Controllers.controller "EditAuthenticationController", ["$scope", "$state", "$stateParams", "$rootScope", "dialogs", "growl", 'providerPromise', 'mappingFieldsPromise', 'AuthProvider', '_t' +, ($scope, $state, $stateParams, $rootScope, dialogs, growl, providerPromise, mappingFieldsPromise, AuthProvider, _t) -> + + $scope.provider = providerPromise + + $scope.authMethods = METHODS + + $scope.mode = 'edition' + + $scope.mappingFields = mappingFieldsPromise + + ## + # Update the current provider with the new inputs + ## + $scope.updateProvider = -> + # check the ID mapping + unless check_oauth2_id_is_mapped($scope.provider.providable_attributes.o_auth2_mappings_attributes) + growl.error(_t('it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider')) + return false + AuthProvider.update {id: $scope.provider.id}, {auth_provider: $scope.provider}, (provider) -> + growl.success(_t('provider_successfully_updated')) + $state.go('app.admin.members') + , -> + growl.error(_t('an_error_occurred_unable_to_update_the_provider')) + + ## + # Changes the admin's view to the members list page + ## + $scope.cancel = -> + $state.go('app.admin.members') + +] \ No newline at end of file diff --git a/app/assets/javascripts/controllers/admin/calendar.coffee.erb b/app/assets/javascripts/controllers/admin/calendar.coffee.erb new file mode 100644 index 000000000..81e38b54f --- /dev/null +++ b/app/assets/javascripts/controllers/admin/calendar.coffee.erb @@ -0,0 +1,403 @@ +'use strict' + +## +# Controller used in the calendar management page +## + +Application.Controllers.controller "AdminCalendarController", ["$scope", "$state", "$uibModal", "moment", "Availability", 'Slot', 'Setting', 'growl', 'dialogs', 'availabilitiesPromise', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t' +($scope, $state, $uibModal, moment, Availability, Slot, Setting, growl, dialogs, availabilitiesPromise, bookingWindowStart, bookingWindowEnd, machinesPromise, _t) -> + + + + ### PRIVATE STATIC CONSTANTS ### + + # The calendar is divided in slots of 30 minutes + BASE_SLOT = '00:30:00' + + # The bookings can be positioned every half hours + BOOKING_SNAP = '00:30:00' + + # The calendar will be initialized positioned under 9:00 AM + DEFAULT_CALENDAR_POSITION = '09:00:00' + + # We do not allow the creation of slots that are not a multiple of 60 minutes + SLOT_MULTIPLE = 60 + + + + ### PUBLIC SCOPE ### + + ## list of the FabLab machines + $scope.machines = machinesPromise + + ## currently selected availability + $scope.availability = null + + ## bind the availabilities slots with full-Calendar events + $scope.eventSources = [] + $scope.eventSources.push + events: availabilitiesPromise + textColor: 'black' + + ## after fullCalendar loads, provides access to its methods through $scope.calendar.fullCalendar() + $scope.calendar = null + + ## fullCalendar (v2) configuration + $scope.calendarConfig = + timezone: Fablab.timezone + lang: Fablab.fullcalendar_locale + header: + left: 'month agendaWeek' + center: 'title' + right: 'today prev,next' + firstDay: 1 # Week start on monday (France) + scrollTime: DEFAULT_CALENDAR_POSITION + slotDuration: BASE_SLOT + snapDuration: BOOKING_SNAP + allDayDefault: false + minTime: "00:00:00" + maxTime: "24:00:00" + height: 'auto' + buttonIcons: + prev: 'left-single-arrow' + next: 'right-single-arrow' + timeFormat: + agenda:'H:mm' + month: 'H(:mm)' + axisFormat: 'H:mm' + + allDaySlot: false + defaultView: 'agendaWeek' + selectable: true + selecHelper: true + select: (start, end, jsEvent, view) -> + calendarSelectCb(start, end, jsEvent, view) + eventClick: (event, jsEvent, view)-> + calendarEventClickCb(event, jsEvent, view) + eventRender: (event, element, view) -> + eventRenderCb(event, element) + + ## fullCalendar time bounds (up & down) + $scope.calendarConfig.minTime = moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss')) + $scope.calendarConfig.maxTime = moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss')) + + + + ## + # Open a confirmation modal to cancel the booking of a user for the currently selected event. + # @param slot {Object} reservation slot of a user, inherited from $resource + ## + $scope.cancelBooking = (slot) -> + # open a confirmation dialog + dialogs.confirm + resolve: + object: -> + title: _t('confirmation_required') + msg: _t("do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION" + , { GENDER:getGender($scope.currentUser), USER:slot.user.name, DATE:moment(slot.start_at).format('L'), TIME:moment(slot.start_at).format('LT'), RESERVATION:slot.reservable.name } + , 'messageformat') + , -> + # the admin has confirmed, cancel the subscription + Slot.cancel {id: slot.slot_id} + , (data, status) -> # success + # update the canceled_at attribute + for resa in $scope.reservations + if resa.slot_id == data.id + resa.canceled_at = data.canceled_at + break + # notify the admin + growl.success(_t('reservation_was_successfully_cancelled')) + , (data, status) -> # failed + growl.error(_t('reservation_cancellation_failed')) + + + + ## + # Open a confirmation modal to remove a machine for the currently selected availability, + # except if it is the last machine of the reservation. + # @param machine {Object} must contain the machine ID and name + ## + $scope.removeMachine = (machine) -> + if $scope.availability.machine_ids.length == 1 + growl.error(_t('unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather')) + else + # open a confirmation dialog + dialogs.confirm + resolve: + object: -> + title: _t('confirmation_required') + msg: _t('do_you_really_want_to_remove_MACHINE_from_this_slot', {GENDER:getGender($scope.currentUser), MACHINE:machine.name}, "messageformat") + ' ' + + _t('this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + ' ' + + _t('beware_this_cannot_be_reverted') + , -> + # the admin has confirmed, remove the machine + machines = $scope.availability.machine_ids + for key, m_id in machines + if m_id == machine.id + machines.splice(key, 1) + + Availability.update {id: $scope.availability.id}, {availability: {machines_attributes: [{id: machine.id, _destroy: true}]}} + , (data, status) -> # success + # update the machine_ids attribute + $scope.availability.machine_ids = data.machine_ids + $scope.availability.title = data.title + $scope.calendar.fullCalendar 'rerenderEvents' + # notify the admin + growl.success(_t('the_machine_was_successfully_removed_from_the_slot')) + , (data, status) -> # failed + growl.error(_t('deletion_failed')) + + + + ### PRIVATE SCOPE ### + + ## + # Return an enumerable meaninful string for the gender of the provider user + # @param user {Object} Database user record + # @return {string} 'male' or 'female' + ## + getGender = (user) -> + if user.profile + if user.profile.gender == "true" then 'male' else 'female' + else 'other' + + # Triggered when the admin drag on the agenda to create a new reservable slot. + # @see http://fullcalendar.io/docs/selection/select_callback/ + ## + calendarSelectCb = (start, end, jsEvent, view) -> + start = moment.tz(start.toISOString(), Fablab.timezone) + end = moment.tz(end.toISOString(), Fablab.timezone) + # first we check that the selected slot is an N-hours multiple (ie. not decimal) + if Number.isInteger(parseInt((end.valueOf() - start.valueOf()) / (SLOT_MULTIPLE * 1000), 10)/SLOT_MULTIPLE) + today = new Date() + if (parseInt((start.valueOf() - today) / (60 * 1000), 10) >= 0) + # then we open a modal window to let the admin specify the slot type + modalInstance = $uibModal.open + templateUrl: '<%= asset_path "admin/calendar/eventModal.html" %>' + controller: 'CreateEventModalController' + resolve: + start: -> start + end: -> end + # when the modal is closed, we send the slot to the server for saving + modalInstance.result.then (availability) -> + $scope.calendar.fullCalendar 'renderEvent', + id: availability.id + title: availability.title, + start: availability.start_at + end: availability.end_at + textColor: 'black' + backgroundColor: availability.backgroundColor + borderColor: availability.borderColor + tag_ids: availability.tag_ids + machine_ids: availability.machine_ids + , true + , -> + $scope.calendar.fullCalendar('unselect') + + $scope.calendar.fullCalendar('unselect') + + + + ## + # Triggered when the admin clicks on a availability slot in the agenda. + # @see http://fullcalendar.io/docs/mouse/eventClick/ + ## + calendarEventClickCb = (event, jsEvent, view) -> + + $scope.availability = event + + # if the user has clicked on the delete event button, delete the event + if ($(jsEvent.target).hasClass('remove-event')) + Availability.delete id: event.id, -> + $scope.calendar.fullCalendar 'removeEvents', event.id + for _event, i in $scope.eventSources[0].events + if _event.id == event.id + $scope.eventSources[0].events.splice(i,1) + + growl.success(_t('the_slot_START-END_has_been_successfully_deleted', {START:+moment(event.start).format('LL LT'), END:moment(event.end).format('LT')})) + ,-> + growl.error(_t('unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member', {START:+moment(event.start).format('LL LT'), END:moment(event.end).format('LT')})) + # if the user has only clicked on the event, display its reservations + else + Availability.reservations {id: event.id}, (reservations) -> + $scope.reservations = reservations + + + + + ## + # Triggered when fullCalendar tries to graphicaly render an event block. + # Append the event tag into the block, just after the event title. + # @see http://fullcalendar.io/docs/event_rendering/eventRender/ + ## + eventRenderCb = (event, element) -> + if event.tag_ids.length > 0 + Availability.get {id: event.id}, (avail) -> + html = '' + for tag in avail.tags + html += "#{tag.name} " + element.find('.fc-title').append("
"+html) + +] + + + +## +# Controller used in the slot creation modal window +## +Application.Controllers.controller 'CreateEventModalController', ["$scope", "$uibModalInstance", "moment", "start", "end", "Machine", "Availability", "Training", 'Tag', 'growl', '_t', ($scope, $uibModalInstance, moment, start, end, Machine, Availability, Training, Tag, growl, _t) -> + + ## $uibModal parameter + $scope.start = start + + ## $uibModal parameter + $scope.end = end + + ## machines list + $scope.machines = [] + + ## trainings list + $scope.trainings = [] + + ## machines associated with the created slot + $scope.selectedMachines = [] + + ## the user is not able to edit the ending time of the availability, unless he set the type to 'training' + $scope.endDateReadOnly = true + + ## timepickers configuration + $scope.timepickers = + start: + hstep: 1 + mstep: 5 + end: + hstep: 1 + mstep: 5 + + ## slot details + $scope.availability = + start_at: start + end_at: end + available_type: 'machines' # default + + + + ## + # Adds or removes the provided machine from the current slot + # @param machine {Object} + ## + $scope.toggleSelection = (machine)-> + index = $scope.selectedMachines.indexOf(machine) + if index > -1 + $scope.selectedMachines.splice(index, 1) + else + $scope.selectedMachines.push(machine) + + + + ## + # Callback for the modal window validation: save the slot and closes the modal + ## + $scope.ok = -> + if $scope.availability.available_type == "machines" + if $scope.selectedMachines.length > 0 + $scope.availability.machine_ids = $scope.selectedMachines.map (m) -> m.id + else + growl.error(_t('you_should_link_a_training_or_a_machine_to_this_slot')) + return + else + $scope.availability.training_ids = [$scope.selectedTraining.id] + Availability.save + availability: $scope.availability + , (availability) -> + $uibModalInstance.close(availability) + + + + ## + # Callback to cancel the slot creation + ## + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + + + + ## + # Switches the slot type : machine availability or training availability + ## + $scope.changeAvailableType = -> + if $scope.availability.available_type == "machines" + $scope.availability.available_type = "training" + else + $scope.availability.available_type = "machines" + + + + ## + # For training avaiabilities, set the maximum number of people allowed to register on this slot + ## + $scope.setNbTotalPlaces = -> + $scope.availability.nb_total_places = $scope.selectedTraining.nb_total_places + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + Machine.query().$promise.then (data)-> + $scope.machines = data.map (d) -> + id: d.id + name: d.name + Training.query().$promise.then (data)-> + $scope.trainings = data.map (d) -> + id: d.id + name: d.name + nb_total_places: d.nb_total_places + if $scope.trainings.length > 0 + $scope.selectedTraining = $scope.trainings[0] + $scope.setNbTotalPlaces() + Tag.query().$promise.then (data) -> + $scope.tags = data + + ## When we configure a machine availability, do not let the user change the end time, as the total + ## time must be dividable by 60 minutes (base slot duration). For training availabilities, the user + ## can configure any duration as it does not matters. + $scope.$watch 'availability.available_type', (newValue, oldValue, scope) -> + if newValue == 'machines' + $scope.endDateReadOnly = true + diff = moment($scope.end).diff($scope.start, 'hours') # the result is rounded down by moment.js + $scope.end = moment($scope.start).add(diff, 'hours').toDate() + $scope.availability.end_at = $scope.end + else + $scope.endDateReadOnly = false + + ## When the start date is changed, if we are configuring a machine availability, + ## maintain the relative length of the slot (ie. change the end time accordingly) + $scope.$watch 'start', (newValue, oldValue, scope) -> + # for machine availabilities, adjust the end time + if $scope.availability.available_type == 'machines' + end = moment($scope.end) + end.add(moment(newValue).diff(oldValue), 'milliseconds') + $scope.end = end.toDate() + else # for training availabilities + # prevent the admin from setting the begining after the and + if moment(newValue).add(1, 'hour').isAfter($scope.end) + $scope.start = oldValue + # update availability object + $scope.availability.start_at = $scope.start + + ## Maintain consistency between the end time and the date object in the availability object + $scope.$watch 'end', (newValue, oldValue, scope) -> + ## we prevent the admin from setting the end of the availability before its begining + if moment($scope.start).add(1, 'hour').isAfter(newValue) + $scope.end = oldValue + # update availability object + $scope.availability.end_at = $scope.end + + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() +] diff --git a/app/assets/javascripts/controllers/admin/events.coffee b/app/assets/javascripts/controllers/admin/events.coffee index 5d782709d..50503beb0 100644 --- a/app/assets/javascripts/controllers/admin/events.coffee +++ b/app/assets/javascripts/controllers/admin/events.coffee @@ -14,8 +14,8 @@ # - $scope.addFile() # - $scope.deleteFile(file) # - $scope.fileinputClass(v) -# - $scope.openStartDatePicker($event) -# - $scope.openEndDatePicker($event) +# - $scope.toggleStartDatePicker($event) +# - $scope.toggleEndDatePicker($event) # - $scope.toggleRecurrenceEnd(e) # # Requires : @@ -23,7 +23,7 @@ # - $state (Ui-Router) [ 'app.public.events_list' ] ## class EventsController - constructor: ($scope, $state, Event, Category) -> + constructor: ($scope, $state, $locale, Event, Category) -> ## Retrieve the list of categories from the server (stage, atelier, ...) Category.query().$promise.then (data)-> @@ -33,12 +33,12 @@ class EventsController ## default parameters for AngularUI-Bootstrap datepicker $scope.datePicker = - format: 'dd/MM/yyyy' + format: $locale.DATETIME_FORMATS.shortDate startOpened: false # default: datePicker is not shown endOpened: false recurrenceEndOpened: false options: - startingDay: 1 # France: the week starts on monday + startingDay: Fablab.weekStartingDay @@ -136,21 +136,20 @@ class EventsController ## # Controller used in the events listing page (admin view) ## -Application.Controllers.controller "adminEventsController", ["$scope", "$state", 'Event', ($scope, $state, Event) -> +Application.Controllers.controller "AdminEventsController", ["$scope", "$state", 'Event', 'eventsPromise', ($scope, $state, Event, eventsPromise) -> ### PUBLIC SCOPE ### - ## The events displayed on the page - $scope.events = [] - ## By default, the pagination mode is activated to limit the page size $scope.paginateActive = true - ## The currently displayed page number - $scope.page = 1 + ## The events displayed on the page + $scope.events = eventsPromise + ## Current virtual page + $scope.page = 2 ## # Adds a bucket of events to the bottom of the page, grouped by month @@ -158,10 +157,7 @@ Application.Controllers.controller "adminEventsController", ["$scope", "$state", $scope.loadMoreEvents = -> Event.query {page: $scope.page}, (data)-> $scope.events = $scope.events.concat data - if data.length - $scope.paginateActive = false if $scope.events.length >= data[0].nb_total_events - else - $scope.paginateActive = false + paginationCheck(data, $scope.events) $scope.page += 1 @@ -172,10 +168,40 @@ Application.Controllers.controller "adminEventsController", ["$scope", "$state", # Kind of constructor: these actions will be realized first when the controller is loaded ## initialize = -> - $scope.loadMoreEvents() + paginationCheck(eventsPromise, $scope.events) - ## !!! MUST BE CALLED AT THE END of the controller + + ## + # Check if all events are already displayed OR if the button 'load more events' + # is required + # @param lastEvents {Array} last events loaded onto the diplay (ie. last "page") + # @param events {Array} full list of events displayed on the page (not only the last retrieved) + ## + paginationCheck = (lastEvents, events)-> + if lastEvents.length > 0 + $scope.paginateActive = false if events.length >= lastEvents[0].nb_total_events + else + $scope.paginateActive = false + + + + # init the controller (call at the end !) initialize() + +] + + + +## +# Controller used in the reservations listing page for a specific event +## +Application.Controllers.controller "ShowEventReservationsController", ["$scope", 'eventPromise', 'reservationsPromise', ($scope, eventPromise, reservationsPromise) -> + + ## retrieve the event from the ID provided in the current URL + $scope.event = eventPromise + + ## list of reservations for the current event + $scope.reservations = reservationsPromise ] @@ -183,7 +209,8 @@ Application.Controllers.controller "adminEventsController", ["$scope", "$state", ## # Controller used in the event creation page ## -Application.Controllers.controller "newEventController", ["$scope", "$state", 'Event', 'Category', 'CSRF', ($scope, $state, Event, Category, CSRF) -> +Application.Controllers.controller "NewEventController", ["$scope", "$state", "$locale", 'Event', 'Category', 'CSRF', '_t' +, ($scope, $state, $locale, Event, Category, CSRF, _t) -> CSRF.setMetaTags() ## API URL where the form will be posted @@ -204,15 +231,18 @@ Application.Controllers.controller "newEventController", ["$scope", "$state", 'E ## Possible types of recurrences for an event $scope.recurrenceTypes = [ - {label: 'Aucune', value: 'none'}, - {label: 'Tous les jours', value: 'day'}, - {label: 'Chaque semaine', value: 'week'}, - {label: 'Chaque mois', value: 'month'}, - {label: 'Chaque année', value: 'year'} + {label: _t('none'), value: 'none'}, + {label: _t('every_days'), value: 'day'}, + {label: _t('every_week'), value: 'week'}, + {label: _t('every_month'), value: 'month'}, + {label: _t('every_year'), value: 'year'} ] + ## currency symbol for the current locale (cf. angular-i18n) + $scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM; + ## Using the EventsController - new EventsController($scope, $state, Event, Category) + new EventsController($scope, $state, $locale, Event, Category) ] @@ -220,8 +250,12 @@ Application.Controllers.controller "newEventController", ["$scope", "$state", 'E ## # Controller used in the events edition page ## -Application.Controllers.controller "editEventController", ["$scope", "$state", "$stateParams", 'Event', 'Category', 'CSRF', ($scope, $state, $stateParams, Event, Category, CSRF) -> - CSRF.setMetaTags() +Application.Controllers.controller "EditEventController", ["$scope", "$state", "$stateParams", "$locale", 'Event', 'Category', 'CSRF', 'eventPromise' +, ($scope, $state, $stateParams, $locale, Event, Category, CSRF, eventPromise) -> + + ### PUBLIC SCOPE ### + + ## API URL where the form will be posted $scope.actionUrl = "/api/events/" + $stateParams.id @@ -230,13 +264,32 @@ Application.Controllers.controller "editEventController", ["$scope", "$state", " $scope.method = 'put' ## Retrieve the event details, in case of error the user is redirected to the events listing - Event.get {id: $stateParams.id} - , (event)-> - $scope.event = event - return - , -> - $state.go('app.public.events_list') + $scope.event = eventPromise - ## Using the EventsController - new EventsController($scope, $state, Event, Category) + ## currency symbol for the current locale (cf. angular-i18n) + $scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM; + + + + ### PRIVATE SCOPE ### + + + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + CSRF.setMetaTags() + + # init the dates to JS objects + $scope.event.start_date = moment($scope.event.start_date).toDate() + $scope.event.end_date = moment($scope.event.end_date).toDate() + + ## Using the EventsController + new EventsController($scope, $state, $locale, Event, Category) + + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() ] diff --git a/app/assets/javascripts/controllers/admin/graphs.coffee b/app/assets/javascripts/controllers/admin/graphs.coffee new file mode 100644 index 000000000..03a7b542b --- /dev/null +++ b/app/assets/javascripts/controllers/admin/graphs.coffee @@ -0,0 +1,648 @@ +'use strict' + +Application.Controllers.controller "GraphsController", ["$scope", "$state", '$locale', "$rootScope", 'es', 'Statistics', '_t' +, ($scope, $state, $locale, $rootScope, es, Statistics, _t) -> + + + + ### PRIVATE STATIC CONSTANTS ### + + ## height of the HTML/SVG charts elements in pixels + CHART_HEIGHT = 500 + + ## Label of the charts' horizontal axes + X_AXIS_LABEL = _t('date') + + ## Label of the charts' vertical axes + Y_AXIS_LABEL = _t('number') + + ## Colors for the line charts. Each new line uses the next color in this array + CHART_COLORS = ['#b35a94', '#1c5794', '#00b49e', '#6fac48', '#ebcf4a', '#fd7e33', '#ca3436', '#a26e3a'] + + + + ### PUBLIC SCOPE ### + + ## ui-view transitions optimization: if true, the charts will never be refreshed + $scope.preventRefresh = false + + ## statistics structure in elasticSearch + $scope.statistics = [] + + ## statistics data recovered from elasticSearch + $scope.data = null + + ## default interval: one day + $scope.display = + interval: 'week' + + ## active tab will be set here + $scope.selectedIndex = null + + ## for palmares graphs, filters values are stored here + $scope.ranking = + sortCriterion: 'ca' + groupCriterion: 'subType' + + ## default: we do not open the datepicker menu + $scope.datePicker = + show: false + + ## datePicker parameters for interval beginning + $scope.datePickerStart = + format: $locale.DATETIME_FORMATS.shortDate + opened: false # default: datePicker is not shown + minDate: null + maxDate: moment().subtract(1, 'day').toDate() + selected: moment().utc().subtract(1, 'months').subtract(1, 'day').startOf('day').toDate() + options: + startingDay: Fablab.weekStartingDay + + ## datePicker parameters for interval ending + $scope.datePickerEnd = + format: $locale.DATETIME_FORMATS.shortDate + opened: false # default: datePicker is not shown + minDate: null + maxDate: moment().subtract(1, 'day').toDate() + selected: moment().subtract(1, 'day').endOf('day').toDate() + options: + startingDay: Fablab.weekStartingDay + + + + ## + # Callback to open the datepicker (interval start) + # @param {Object} jQuery event object + ## + $scope.toggleStartDatePicker = ($event) -> + toggleDatePicker($event, $scope.datePickerStart) + + + + ## + # Callback to open the datepicker (interval end) + # @param {Object} jQuery event object + ## + $scope.toggleEndDatePicker = ($event) -> + toggleDatePicker($event, $scope.datePickerEnd) + + + + ## + # Callback called when the active tab is changed. + # Recover the current tab and store its value in $scope.selectedIndex + # @param tab {Object} elasticsearch statistic structure + ## + $scope.setActiveTab = (tab) -> + $scope.selectedIndex = tab + $scope.ranking.groupCriterion = 'subType' + if tab.ca + $scope.ranking.sortCriterion = 'ca' + else + $scope.ranking.sortCriterion = tab.types[0].key + refreshChart() + + + + ## + # Callback to close the date-picking popup and refresh the results + ## + $scope.validateDateChange = -> + $scope.datePicker.show = false + refreshChart() + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + Statistics.query (stats) -> + $scope.statistics = stats + # watch the interval changes to refresh the graph + $scope.$watch (scope) -> + return scope.display.interval + , (newValue, oldValue) -> + refreshChart() + $scope.$watch (scope) -> + return scope.ranking.sortCriterion + , (newValue, oldValue) -> + refreshChart() + $scope.$watch (scope) -> + return scope.ranking.groupCriterion + , (newValue, oldValue) -> + refreshChart() + refreshChart() + + # workaround for angular-bootstrap::tabs behavior: on tab deletion, another tab will be selected + # which will cause every tabs to reload, one by one, when the view is closed + $rootScope.$on '$stateChangeStart', (event, toState, toParams, fromState, fromParams) -> + if fromState.name == 'app.admin.stats_graphs' and Object.keys(fromParams).length == 0 + $scope.preventRefresh = true + + + + ## + # Generic function to toggle a bootstrap datePicker + # @param $event {Object} jQuery event object + # @param datePicker {Object} settings object of the concerned datepicker. Must have an 'opened' property + ## + toggleDatePicker = ($event, datePicker) -> + $event.preventDefault() + $event.stopPropagation() + datePicker.opened = !datePicker.opened + + + + ## + # Query elasticSearch according to the current parameters and update the chart + ## + refreshChart = -> + if $scope.selectedIndex and !$scope.preventRefresh + query $scope.selectedIndex, (aggregations, error)-> + if error + console.error(error) + else + if $scope.selectedIndex.graph.chart_type != 'discreteBarChart' + $scope.data = formatAggregations(aggregations) + angular.forEach $scope.data, (datum, key) -> + updateChart($scope.selectedIndex.graph.chart_type, datum, key) + else + $scope.data = formatRankingAggregations(aggregations, $scope.selectedIndex.graph.limit, $scope.ranking.groupCriterion) + updateChart($scope.selectedIndex.graph.chart_type, $scope.data.ranking, $scope.selectedIndex.es_type_key) + + + + ## + # Callback used in NVD3 to print timestamps as literal dates on the X axis + ## + xAxisTickFormatFunction = (d, x, y) -> + ### WARNING !! These tests (typeof/instanceof) may become broken on nvd3 update ### + if $scope.display.interval == 'day' + if typeof d == 'number' or d instanceof Date + d3.time.format(Fablab.d3DateFormat) moment(d).toDate() + else # typeof d == 'string' + d + else if $scope.display.interval == 'week' + if typeof x == 'number' or d instanceof Date + d3.time.format(_t('week_short')+' %U') moment(d).toDate() + else if typeof d == 'number' + _t('week_of_START_to_END', {START:moment(d).format('L'), END:moment(d).add(6, 'days').format('L')}) + else # typeof d == 'string' + d + else if $scope.display.interval == 'month' + if typeof d == 'number' + label = moment(d).format('MMMM YYYY') + label.substr(0,1).toUpperCase()+label.substr(1).toLowerCase() + else # typeof d == 'string' + d + + + + ## + # Format aggregations as retuned by elasticSearch to an understandable format for NVD3 + # @param aggs {Object} as returned by elasticsearch + ## + formatAggregations = (aggs) -> + format = {} + + angular.forEach aggs, (type, type_key) -> # go through aggs[$TYPE] where $TYPE = month|year|hour|booking|... + format[type_key] = [] + if type.subgroups + angular.forEach type.subgroups.buckets, (subgroup) -> # go through aggs.$TYPE.subgroups.buckets where each bucket represent a $SUBTYPE + angular.forEach $scope.selectedIndex.types, (cur_type) -> # in the mean time, go through the types of the current index (active tab) ... + if cur_type.key == type_key # ... looking for the type matching $TYPE + for it_st in [0.. cur_type.subtypes.length-1] by 1 # when we've found it, iterate over its subtypes ... + cur_subtype = cur_type.subtypes[it_st] + if subgroup.key == cur_subtype.key # ... which match $SUBTYPE + # then we construct NVD3 dataSource according to these informations + dataSource = + values: [] + key: cur_subtype.label + total : 0 + color: CHART_COLORS[it_st] + area: true + # finally, we iterate over 'intervals' buckets witch contains + # per date aggregations for our current dataSource + angular.forEach subgroup.intervals.buckets, (interval) -> + dataSource.values.push + x: interval.key + y: interval.total.value + dataSource.total += parseInt(interval.total.value) + dataSource.key += ' (' + dataSource.total + ')' + format[type_key].push dataSource + format + + + + ## + # Format aggregations for ranking charts to an understandable format for NVD3 + # @param aggs {Object} as returned by elasticsearch + # @param limit {number} limit the number of stats in the bar chart + # @param typeKey {String} field name witch results are grouped by + ## + formatRankingAggregations = (aggs, limit, typeKey) -> + format = + ranking: [] + + it = 0 + while (it < aggs.subgroups.buckets.length) + bucket = aggs.subgroups.buckets[it] + dataSource = + values: [] + key: getRankingLabel(bucket.key, typeKey) + color: CHART_COLORS[it] + area: true + dataSource.values.push + x: getRankingLabel(bucket.key, typeKey) + y: bucket.total.value + format.ranking.push(dataSource) + it++ + getY = (object)-> + object.values[0].y + format.ranking = stableSort(format.ranking, 'DESC', getY).slice(0, limit) + for i in [0..format.ranking.length] by 1 + if typeof format.ranking[i] == 'undefined' then format.ranking.splice(i,1) + format + + + + ## + # For BarCharts, return the label for a given bar + # @param key {string} raw value of the label + # @param typeKey {string} name of the field the results are grouped by + ## + getRankingLabel = (key, typeKey) -> + if $scope.selectedIndex + if (typeKey == 'subType') + for type in $scope.selectedIndex.types + for subtype in type.subtypes + if (subtype.key == key) + return subtype.label + else + for field in $scope.selectedIndex.additional_fields + if (field.key == typeKey) + switch field.data_type + when 'date' then return moment(key).format('LL') + when 'list' then return key.name + else return key + + + + ## + # Prepare the elasticSearch query for the stats matching the current controller's parameters + # @param index {{id:{number}, es_type_key:{string}, label:{string}, table:{boolean}, additional_fields:{Array}, + # types:{Array}, graph:{Object}}} elasticSearch type in stats index to query + # @param callback {function} function be to run after results were retrieved, + # it will receive two parameters : results {Array}, error {String} (if any) + ## + query = (index, callback) -> + # invalid callback handeling + if typeof(callback) != "function" + console.error('[graphsController::query] Error: invalid callback provided') + return + if !index + callback([], '[graphsController::query] Error: invalid index provided') + return + + if index.graph.chart_type != 'discreteBarChart' + # list statistics types + stat_types = [] + for t in index.types + if t.graph + stat_types.push(t.key) + + # exception handeling + if stat_types.length == 0 + callback([], "Error: Unable to retrieve any graphical statistic types in the provided index") + + type_it = 0 + results = {} + error = '' + recursiveCb = -> + if type_it < stat_types.length + queryElasticStats index.es_type_key, stat_types[type_it], (prevResults, prevError)-> + if (prevError) + console.error('[graphsController::query] '+prevError) + error += '\n'+prevError + results[stat_types[type_it]] = prevResults + type_it++ + recursiveCb() + else + callback(results) + recursiveCb() + else # palmares (ranking) + queryElasticRanking index.es_type_key, $scope.ranking.groupCriterion, $scope.ranking.sortCriterion, index.graph.limit, (results, error) -> + if (error) + callback([], error) + else + callback(results) + + + + ## + # Run the elasticSearch query to retreive the /stats/type aggregations + # @param esType {String} elasticSearch document type (subscription|machine|training|...) + # @param statType {String} statistics type (year|month|hour|booking|...) + # @param callback {function} function be to run after results were retrieved, + # it will receive two parameters : results {Array}, error {String} (if any) + ## + queryElasticStats = (esType, statType, callback) -> + # handle invalid callback + if typeof(callback) != "function" + console.error('[graphsController::queryElasticStats] Error: invalid callback provided') + return + if !esType or !statType + callback([], '[graphsController::queryElasticStats] Error: invalid parameters provided') + + # run query + es.search + "index": "stats" + "type": esType + "searchType": "count" + "body": buildElasticAggregationsQuery(statType, $scope.display.interval, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected)) + , (error, response) -> + if (error) + callback([], "Error: something unexpected occurred during elasticSearch query: "+error) + else + callback(response.aggregations) + + + + ## + # For ranking displays, run the elasticSearch query to retreive the /stats/type aggregations + # @param esType {String} elasticSearch document type (subscription|machine|training|...) + # @param statType {String} statistics type (year|month|hour|booking|...) + # @param callback {function} function be to run after results were retrieved, + # it will receive two parameters : results {Array}, error {String} (if any) + ## + queryElasticRanking = (esType, groupKey, sortKey, limit, callback) -> + # handle invalid callback + if typeof(callback) != "function" + console.error('[graphsController::queryElasticRanking] Error: invalid callback provided') + return + if !esType or !groupKey or !sortKey or typeof limit != 'number' + callback([], '[graphsController::queryElasticRanking] Error: invalid parameters provided') + + # run query + es.search + "index": "stats" + "type": esType + "searchType": "count" + "body": buildElasticAggregationsRankingQuery(groupKey, sortKey, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected)) + , (error, response) -> + if (error) + callback([], "Error: something unexpected occurred during elasticSearch query: "+error) + else + callback(response.aggregations) + + + + ## + # Parse a final elastic results bucket and return a D3 compatible object + # @param bucket {{key_as_string:{String}, key:{Number}, doc_count:{Number}, total:{{value:{Number}}}}} interval bucket + ## + parseElasticBucket = (bucket) -> + [ bucket.key, bucket.total.value ] + + + + ## + # Build an object representing the content of the REST-JSON query to elasticSearch, based on the parameters + # currently defined for data aggegations. + # @param type {String} statistics type (visit|rdv|rating|ca|plan|account|search|...) + # @param interval {String} statistics interval (year|quarter|month|week|day|hour|minute|second) + # @param intervalBegin {moment} statitics interval beginning (moment.js type) + # @param intervalEnd {moment} statitics interval ending (moment.js type) + ## + buildElasticAggregationsQuery = (type, interval, intervalBegin, intervalEnd) -> + q = + "query": + "bool": + "must": [ + { + "match": + "type": type + } + { + "range": + "date": + "gte": intervalBegin.format() + "lte": intervalEnd.format() + } + ] + "aggregations": + "subgroups": + "terms": + "field": "subType" #TODO allow aggregate by custom field + "aggregations": + "intervals": + "date_histogram": + "field": "date" + "interval": interval + "min_doc_count": 0 + "extended_bounds": + "min": intervalBegin.valueOf() + "max": intervalEnd.valueOf() + "aggregations": + "total": + "sum": + "field": "stat" + + # scale weeks on sunday as nvd3 supports only these weeks + if interval == 'week' + q.aggregations.subgroups.aggregations.intervals.date_histogram['post_offset'] = '-1d' + q.aggregations.subgroups.aggregations.intervals.date_histogram['pre_offset'] = '-1d' + # scale days to UTC time + else if interval == 'day' + offset = moment().utcOffset() + q.aggregations.subgroups.aggregations.intervals.date_histogram['post_offset'] = (-offset)+'m' + q + + + + ## + # Build an object representing the content of the REST-JSON query to elasticSearch, based on the parameters + # currently defined for data aggegations. + # @param groupKey {String} statistics subtype or custom field + # @param sortKey {String} statistics type or 'ca' + # @param intervalBegin {moment} statitics interval beginning (moment.js type) + # @param intervalEnd {moment} statitics interval ending (moment.js type) + ## + buildElasticAggregationsRankingQuery = (groupKey, sortKey, intervalBegin, intervalEnd) -> + q = + "query": + "bool": + "must": [ + { + "range": + "date": + "gte": intervalBegin.format() + "lte": intervalEnd.format() + } + { + "term": + "type": "booking" + } + ] + "aggregations": + "subgroups": + "terms": + "field": "subType" + "aggregations": + "total": + "sum": + "field": "stat" + + # we group the results by the custom given key (eg. by event date) + q.aggregations.subgroups.terms = + field: groupKey + size: 0 + + # results must be sorted and limited later by angular + if sortKey != 'ca' + angular.forEach q.query.bool.must, (must) -> + if must.term + must.term.type = sortKey + else + q.aggregations.subgroups.aggregations.total.sum.field = sortKey + + q + + + + ## + # Redraw the NDV3 chart using the provided data + # @param chart_type {String} stackedAreaChart|discreteBarChart|lineChart + # @param data {Array} array of NVD3 dataSources + # @param type {String} which chart to update (statistic type key) + ## + updateChart = (chart_type, data, type) -> + + id = "#chart-"+type+" svg" + + # clean old charts + d3.selectAll(id+" > *").remove() + + nv.addGraph -> + # no data or many dates, display line charts + if data.length == 0 or (data[0].values.length > 1 and (chart_type != 'discreteBarChart')) + if chart_type == 'stackedAreaChart' + chart = nv.models.stackedAreaChart().useInteractiveGuideline(true) + else + chart = nv.models.lineChart().useInteractiveGuideline(true) + + if data.length > 0 + if $scope.display.interval == 'day' + setTimeScale(chart.xAxis, chart.xScale, [d3.time.day, data[0].values.length]) + else if $scope.display.interval == 'week' + setTimeScale(chart.xAxis, chart.xScale, [d3.time.week, data[0].values.length]) + else if $scope.display.interval == 'month' + setTimeScale(chart.xAxis, chart.xScale, [d3.time.month, data[0].values.length]) + + chart.xAxis.tickFormat(xAxisTickFormatFunction) + chart.yAxis.tickFormat(d3.format('d')) + + chart.xAxis.axisLabel(X_AXIS_LABEL) + chart.yAxis.axisLabel(Y_AXIS_LABEL) + + # only one date, display histograms + else + chart = nv.models.discreteBarChart() + chart.tooltip.enabled(false) + chart.showValues(true) + chart.x (d) -> d.label + chart.y (d) -> d.value + data = prepareDataForBarChart(data, type) + + # common for each charts + chart.margin({left: 100, right: 100}) + chart.noData(_t('no_data_for_this_period')) + chart.height( CHART_HEIGHT ) + + # add new chart to the page + d3.select(id).datum(data).transition().duration(350).call(chart) + + # resize the graph when the page is resized + nv.utils.windowResize(chart.update) + # return the chart + chart + + + + ## + # Given an NVD3 line chart axis, scale it to display ordinated dates, according to the given arguments + ## + setTimeScale = (nvd3Axis, nvd3Scale, argsArray) -> + scale = d3.time.scale() + + nvd3Axis.scale(scale) + nvd3Scale(scale) + + if (not argsArray and not argsArray.length) + oldTicks = nvd3Axis.axis.ticks + nvd3Axis.axis.ticks = -> + oldTicks.apply(nvd3Axis.axis, argsArray) + + + + ## + # Translate line chart data in dates row to bar chart data, one bar per type. + ## + prepareDataForBarChart = (data, type) -> + newData = [ + key: type + values: [] + ] + for info in data + if info + newData[0].values.push + "label": info.key + "value": info.values[0].y + "color": info.color + + newData + + + + ## + # Sort the provided array, in the specified order, on the value returned by the callback. + # This is a stable-sorting algorithm implementation, ie. two call with the same array will return the same results + # orders, especially with equal values. + # @param array {Array} the array to sort + # @param order {string} 'ASC' or 'DESC' + # @param getValue {function} the callback which will return the value on which the sort will occurs + # @returns {Array} + ## + stableSort = (array, order, getValue) -> + # prepare sorting + keys_order = [] + result = [] + for i in [0..array.length] by 1 + keys_order[array[i]] = i; + result.push(array[i]); + + # callback for javascript native Array.sort() + sort_fc = (a, b) -> + val_a = getValue(a) + val_b = getValue(b) + if val_a == val_b + return keys_order[a] - keys_order[b] + if val_a < val_b + if order == 'ASC' then return -1 + else return 1 + else + if order == 'ASC' then return 1 + else return -1 + + # finish the sort + result.sort(sort_fc) + return result + + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() +] diff --git a/app/assets/javascripts/controllers/admin/groups.coffee.erb b/app/assets/javascripts/controllers/admin/groups.coffee.erb new file mode 100644 index 000000000..87fd816cc --- /dev/null +++ b/app/assets/javascripts/controllers/admin/groups.coffee.erb @@ -0,0 +1,64 @@ +Application.Controllers.controller "GroupsController", ["$scope", 'groupsPromise', 'Group', 'growl', '_t', ($scope, groupsPromise, Group, growl, _t) -> + + ## List of users groups + $scope.groups = groupsPromise + + + + ## + # Removes the newly inserted but not saved group / Cancel the current group modification + # @param rowform {Object} see http://vitalets.github.io/angular-xeditable/ + # @param index {number} group index in the $scope.groups array + ## + $scope.cancelGroup = (rowform, index) -> + if $scope.groups[index].id? + rowform.$cancel() + else + $scope.groups.splice(index, 1) + + + + ## + # Creates a new empty entry in the $scope.groups array + ## + $scope.addGroup = -> + $scope.inserted = + name: '' + $scope.groups.push($scope.inserted) + + + + ## + # Saves a new group / Update an existing group to the server (form validation callback) + # @param data {Object} group name + # @param [data] {number} group id, in case of update + ## + $scope.saveGroup = (data, id) -> + if id? + Group.update {id: id}, { group: data }, (response) -> + growl.success(_t('changes_successfully_saved')) + , (error) -> + growl.error(_t('an_error_occurred_while_saving_changes')) + else + Group.save { group: data }, (resp)-> + growl.success(_t('new_group_successfully_saved')) + $scope.groups[$scope.groups.length-1].id = resp.id + , (error) -> + growl.error(_t('an_error_occurred_when_saving_the_new_group')) + $scope.groups.splice($scope.groups.length-1, 1) + + + + ## + # Deletes the group at the specified index + # @param index {number} group index in the $scope.groups array + ## + $scope.removeGroup = (index) -> + Group.delete { id: $scope.groups[index].id }, (resp) -> + growl.success(_t('group_successfully_deleted')) + $scope.groups.splice(index, 1) + , (error) -> + growl.error(_t('unable_to_delete_group_because_some_users_and_or_groups_are_still_linked_to_it')) + + +] diff --git a/app/assets/javascripts/controllers/admin/invoices.coffee.erb b/app/assets/javascripts/controllers/admin/invoices.coffee.erb new file mode 100644 index 000000000..21727dfe7 --- /dev/null +++ b/app/assets/javascripts/controllers/admin/invoices.coffee.erb @@ -0,0 +1,492 @@ +'use strict' + +## +# Controller used in the admin invoices listing page +## +Application.Controllers.controller "InvoicesController", ["$scope", "$state", 'Invoice', '$uibModal', "growl", "$filter", 'Setting', 'settings', '_t' +, ($scope, $state, Invoice, $uibModal, growl, $filter, Setting, settings, _t) -> + + + + ### PUBLIC SCOPE ### + + ## List of all users invoices + $scope.invoices = Invoice.query() + + ## Default invoices ordering/sorting + $scope.orderInvoice = '-reference' + + ## Invoices parameters + $scope.invoice = + logo: null + reference: + model: '' + help: null + templateUrl: 'editReference.html' + code: + model: '' + active: true + templateUrl: 'editCode.html' + number: + model: '' + help: null + templateUrl: 'editNumber.html' + VAT: + rate: 19.6 + active: false + templateUrl: 'editVAT.html' + text: + content: '' + legals: + content: '' + + ## Placeholding date for the invoice creation + $scope.today = moment() + + ## Placeholding date for the reservation begin + $scope.inOneWeek = moment().add(1, 'week').startOf('hour') + + ## Placeholding date for the reservation end + $scope.inOneWeekAndOneHour = moment().add(1, 'week').add(1, 'hour').startOf('hour') + + + + ## + # Change the invoices ordering criterion to the one provided + # @param orderBy {string} ordering criterion + ## + $scope.setOrderInvoice = (orderBy)-> + if $scope.orderInvoice == orderBy + $scope.orderInvoice = '-'+orderBy + else + $scope.orderInvoice = orderBy + + + + ## + # Open a modal window asking the admin the details to refund the user about the provided invoice + # @param invoice {Object} invoice inherited from angular's $resource + ## + $scope.generateAvoirForInvoice = (invoice)-> + # open modal + modalInstance = $uibModal.open + templateUrl: '<%= asset_path "admin/invoices/avoirModal.html" %>' + controller: 'AvoirModalController' + resolve: + invoice: -> invoice + + # once done, update the invoice model and inform the admin + modalInstance.result.then (res) -> + $scope.invoices.unshift res.avoir + Invoice.get {id: invoice.id}, (data) -> + invoice.has_avoir = data.has_avoir + growl.success(_t('refund_invoice_successfully_created')) + + + + ## + # Generate an invoice reference sample from the parametrized model + # @returns {string} invoice reference sample + ## + $scope.mkReference = -> + sample = $scope.invoice.reference.model + if sample + # invoice number per day (dd..dd) + sample = sample.replace(/d+(?![^\[]*])/g, (match, offset, string) -> + padWithZeros(2, match.length) + ) + # invoice number per month (mm..mm) + sample = sample.replace(/m+(?![^\[]*])/g, (match, offset, string) -> + padWithZeros(12, match.length) + ) + # invoice number per year (yy..yy) + sample = sample.replace(/y+(?![^\[]*])/g, (match, offset, string) -> + padWithZeros(8, match.length) + ) + # date informations + sample = sample.replace(/[YMD]+(?![^\[]*])/g, (match, offset, string) -> + $scope.today.format(match) + ) + # information about online selling (X[text]) + sample = sample.replace(/X\[([^\]]+)\]/g, (match, p1, offset, string) -> + p1 + ) + # information about refunds (R[text]) - does not apply here + sample = sample.replace(/R\[([^\]]+)\]/g, "") + sample + + + ## + # Generate an order nmuber sample from the parametrized model + # @returns {string} invoice reference sample + ## + $scope.mkNumber = -> + sample = $scope.invoice.number.model + if sample + # global order number (nn..nn) + sample = sample.replace(/n+(?![^\[]*])/g, (match, offset, string) -> + padWithZeros(327, match.length) + ) + # order number per year (yy..yy) + sample = sample.replace(/y+(?![^\[]*])/g, (match, offset, string) -> + padWithZeros(8, match.length) + ) + # order number per month (mm..mm) + sample = sample.replace(/m+(?![^\[]*])/g, (match, offset, string) -> + padWithZeros(12, match.length) + ) + # order number per day (dd..dd) + sample = sample.replace(/d+(?![^\[]*])/g, (match, offset, string) -> + padWithZeros(2, match.length) + ) + # date informations + sample = sample.replace(/[YMD]+(?![^\[]*])/g, (match, offset, string) -> + $scope.today.format(match) + ) + sample + + + + ## + # Open a modal dialog allowing the user to edit the invoice reference generation template + ## + $scope.openEditReference = -> + modalInstance = $uibModal.open + animation: true, + templateUrl: $scope.invoice.reference.templateUrl, + size: 'lg', + resolve: + model: -> + $scope.invoice.reference.model + controller: ($scope, $uibModalInstance, model) -> + $scope.model = model + $scope.ok = -> + $uibModalInstance.close($scope.model) + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + + modalInstance.result.then (model) -> + Setting.update { name: 'invoice_reference' }, { value: model }, (data)-> + $scope.invoice.reference.model = model + growl.success(_t('invoice_reference_successfully_saved')) + , (error)-> + growl.error(_t('an_error_occurred_while_saving_invoice_reference')) + console.error(error) + + + + + ## + # Open a modal dialog allowing the user to edit the invoice code + ## + $scope.openEditCode = -> + modalInstance = $uibModal.open + animation: true, + templateUrl: $scope.invoice.code.templateUrl, + size: 'lg', + resolve: + model: -> + $scope.invoice.code.model + active: -> + $scope.invoice.code.active + controller: ($scope, $uibModalInstance, model, active) -> + $scope.codeModel = model + $scope.isSelected = active + + + $scope.ok = -> + $uibModalInstance.close({model: $scope.codeModel, active: $scope.isSelected}) + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + + modalInstance.result.then (result) -> + Setting.update { name: 'invoice_code-value' }, { value: result.model }, (data)-> + $scope.invoice.code.model = result.model + if result.active + growl.success(_t('invoicing_code_succesfully_saved')) + , (error)-> + growl.error(_t('an_error_occurred_while_saving_the_invoicing_code')) + console.error(error) + + Setting.update { name: 'invoice_code-active' }, { value: if result.active then "true" else "false" }, (data)-> + $scope.invoice.code.active = result.active + if result.active + growl.success(_t('code_successfully_activated')) + else + growl.success(_t('code_successfully_disabled')) + , (error)-> + growl.error(_t('an_error_occurred_while_activating_the_invoicing_code')) + console.error(error) + + + + + ## + # Open a modal dialog allowing the user to edit the invoice number + ## + $scope.openEditInvoiceNb = -> + modalInstance = $uibModal.open + animation: true, + templateUrl: $scope.invoice.number.templateUrl, + size: 'lg', + resolve: + model: -> + $scope.invoice.number.model + controller: ($scope, $uibModalInstance, model) -> + $scope.model = model + $scope.ok = -> + $uibModalInstance.close($scope.model) + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + + modalInstance.result.then (model) -> + Setting.update { name: 'invoice_order-nb' }, { value: model }, (data)-> + $scope.invoice.number.model = model + growl.success(_t('order_number_successfully_saved')) + , (error)-> + growl.error(_t('an_error_occurred_while_saving_the_order_number')) + console.error(error) + + + + + ## + # Open a modal dialog allowing the user to edit the VAT parameters for the invoices + # The VAT can be disabled and its rate can be configured + ## + $scope.openEditVAT = -> + modalInstance = $uibModal.open + animation: true, + templateUrl: $scope.invoice.VAT.templateUrl, + size: 'lg', + resolve: + rate: -> + $scope.invoice.VAT.rate + active: -> + $scope.invoice.VAT.active + controller: ($scope, $uibModalInstance, rate, active) -> + $scope.rate = rate + $scope.isSelected = active + + + $scope.ok = -> + $uibModalInstance.close({rate: $scope.rate, active: $scope.isSelected}) + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + + modalInstance.result.then (result) -> + Setting.update { name: 'invoice_VAT-rate' }, { value: result.rate+"" }, (data)-> + $scope.invoice.VAT.rate = result.rate + if result.active + growl.success(_t('VAT_rate_successfully_saved')) + , (error)-> + growl.error(_t('an_error_occurred_while_saving_the_VAT_rate')) + console.error(error) + + Setting.update { name: 'invoice_VAT-active' }, { value: if result.active then "true" else "false" }, (data)-> + $scope.invoice.VAT.active = result.active + if result.active + growl.success(_t('VAT_successfully_activated')) + else + growl.success(_t('VAT_successfully_disabled')) + , (error)-> + growl.error(_t('an_error_occurred_while_activating_the_VAT')) + console.error(error) + + + + ## + # Callback to save the value of the text zone when editing is done + ## + $scope.textEditEnd = (event) -> + parsed = parseHtml($scope.invoice.text.content) + Setting.update { name: 'invoice_text' }, { value: parsed }, (data)-> + $scope.invoice.text.content = parsed + growl.success(_t('text_successfully_saved')) + , (error)-> + growl.error(_t('an_error_occurred_while_saving_the_text')) + console.error(error) + + + + ## + # Callback to save the value of the legal informations zone when editing is done + ## + $scope.legalsEditEnd = (event) -> + parsed = parseHtml($scope.invoice.legals.content) + Setting.update { name: 'invoice_legals' }, { value: parsed }, (data)-> + $scope.invoice.legals.content = parsed + growl.success(_t('address_and_legal_information_successfully_saved')) + , (error)-> + growl.error(_t('an_error_occurred_while_saving_the_address_and_the_legal_information')) + console.error(error) + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + # retrieve settings from the DB through the API + $scope.invoice.legals.content = settings['invoice_legals'] + $scope.invoice.text.content = settings['invoice_text'] + $scope.invoice.VAT.rate = parseFloat(settings['invoice_VAT-rate']) + $scope.invoice.VAT.active = (settings['invoice_VAT-active'] == "true") + $scope.invoice.number.model = settings['invoice_order-nb'] + $scope.invoice.code.model = settings['invoice_code-value'] + $scope.invoice.code.active = (settings['invoice_code-active'] == "true") + $scope.invoice.reference.model = settings['invoice_reference'] + $scope.invoice.logo = + filetype: 'image/png' + filename: 'logo.png' + base64: settings['invoice_logo'] + + # Watch the logo, when a change occurs, save it + $scope.$watch 'invoice.logo', -> + if $scope.invoice.logo and $scope.invoice.logo.filesize + Setting.update { name: 'invoice_logo' }, { value: $scope.invoice.logo.base64 }, (data)-> + growl.success(_t('logo_successfully_saved')) + , (error)-> + growl.error(_t('an_error_occurred_while_saving_the_logo')) + console.error(error) + + + + ## + # Output the given integer with leading zeros. If the given value is longer than the given + # length, it will be truncated. + # @param value {number} the integer to pad + # @param length {number} the length of the resulting string. + ## + padWithZeros = (value, length) -> + (1e15+value+"").slice(-length) + + + + ## + # Remove every unsupported html tag from the given html text (like

, , ...). + # The supported tags are , , and
. + # @param html {string} single line html text + # @return {string} multi line simplified html text + ## + parseHtml = (html) -> + html = html.replace(/<\/?(\w+)((\s+\w+(\s*=\s*(?:".*?"|'.*?'|[^'">\s]+))?)+\s*|\s*)\/?>/g, (match, p1, offset, string) -> + if p1 in ['b', 'u', 'i', 'br'] + match + else + '' + ) + + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() +] + + + +## +# Controller used in the invoice refunding modal window +## +Application.Controllers.controller 'AvoirModalController', ["$scope", "$uibModalInstance", '$locale', "invoice", "Invoice", "growl", '_t' +, ($scope, $uibModalInstance, $locale, invoice, Invoice, growl, _t) -> + + + + ### PUBLIC SCOPE ### + + ## invoice linked to the current refund + $scope.invoice = invoice + + ## Associative array containing invoice_item ids associated with boolean values + $scope.partial = {} + + ## Default refund parameters + $scope.avoir = + invoice_id: invoice.id + subscription_to_expire: false + invoice_items_ids: [] + + ## Possible refunding methods + $scope.avoirModes = [ + {name: _t('none'), value: 'none'} + {name: _t('by_cash'), value: 'cash'} + {name: _t('by_cheque'), value: 'cheque'} + {name: _t('by_transfer'), value: 'transfer'} + ] + + ## If a subscription was took with the current invoice, should it be canceled or not + $scope.subscriptionExpireOptions = {} + $scope.subscriptionExpireOptions[_t('yes')] = true + $scope.subscriptionExpireOptions[_t('no')] = false + + ## AngularUI-Bootstrap datepicker parameters to define when to refund + $scope.datePicker = + format: $locale.DATETIME_FORMATS.shortDate + opened: false # default: datePicker is not shown + options: + startingDay: Fablab.weekStartingDay + + + + ## + # Callback to open the datepicker + ## + $scope.openDatePicker = ($event) -> + $event.preventDefault() + $event.stopPropagation() + $scope.datePicker.opened = true + + + + ## + # Validate the refunding and generate a refund invoice + ## + $scope.ok = -> + # check that at least 1 element of the invoice is refunded + $scope.avoir.invoice_items_ids = [] + for itemId, refundItem of $scope.partial + $scope.avoir.invoice_items_ids.push(parseInt(itemId)) if refundItem + + if $scope.avoir.invoice_items_ids.length is 0 + growl.error(_t('you_must_select_at_least_one_element_to_create_a_refund')) + else + Invoice.save {avoir: $scope.avoir}, (avoir) -> + # success + $uibModalInstance.close({avoir:avoir, invoice:$scope.invoice}) + , (err) -> + # failed + growl.error(_t('unable_to_create_the_refund')) + + + + ## + # Cancel the refund, dismiss the modal window + ## + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + ## if the invoice was payed with stripe, allow to refund through stripe + Invoice.get {id: invoice.id}, (data) -> + $scope.invoice = data + # default : all elements of the invoice are refund + for item in data.items + $scope.partial[item.id] = (typeof item.avoir_item_id isnt 'number') + + if invoice.stripe + $scope.avoirModes.push {name: _t('online_payment'), value: 'stripe'} + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() +] diff --git a/app/assets/javascripts/controllers/admin/members.coffee b/app/assets/javascripts/controllers/admin/members.coffee deleted file mode 100644 index a9117e411..000000000 --- a/app/assets/javascripts/controllers/admin/members.coffee +++ /dev/null @@ -1,153 +0,0 @@ -'use strict' - -### COMMON CODE ### - -## -# Provides a set of common properties and methods to the $scope parameter. They are used -# in the various members' admin controllers. -# -# Provides : -# - $scope.groups = [{Group}] -# - $scope.datePicker = {} -# - $scope.submited(content) -# - $scope.cancel() -# - $scope.fileinputClass(v) -# - $scope.openDatePicker($event) -# -# Requires : -# - $state (Ui-Router) [ 'app.admin.members' ] -## -class MembersController - constructor: ($scope, $state, Group) -> - - ## Retrieve the profiles groups (eg. students ...) - Group.query (groups) -> - $scope.groups = groups - $scope.user.group_id = $scope.groups[0].id - - ## Default parameters for AngularUI-Bootstrap datepicker - $scope.datePicker = - format: 'dd/MM/yyyy' - opened: false # default: datePicker is not shown - options: - startingDay: 1 # France: the week starts on monday - - - ## - # Shows the birth day datepicker - # @param $event {Object} jQuery event object - ## - $scope.openDatePicker = ($event) -> - $event.preventDefault() - $event.stopPropagation() - $scope.datePicker.opened = true - - - - ## - # For use with ngUpload (https://github.com/twilson63/ngUpload). - # Intended to be the callback when an upload is done: any raised error will be stacked in the - # $scope.alerts array. If everything goes fine, the user is redirected to the members listing page. - # @param content {Object} JSON - The upload's result - ## - $scope.submited = (content) -> - if !content.id? - $scope.alerts = [] - angular.forEach content, (v, k)-> - angular.forEach v, (err)-> - $scope.alerts.push - msg: k+': '+err, - type: 'danger' - else - $state.go('app.admin.members') - - - - ## - # Changes the admin's view to the members list page - ## - $scope.cancel = -> - $state.go('app.admin.members') - - - - ## - # For use with 'ng-class', returns the CSS class name for the uploads previews. - # The preview may show a placeholder or the content of the file depending on the upload state. - # @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules) - ## - $scope.fileinputClass = (v)-> - if v - 'fileinput-exists' - else - 'fileinput-new' - - - -## -# Controller used in the member edition page -## -Application.Controllers.controller "editMemberController", ["$scope", "$state", "$stateParams", "Member", 'dialogs', 'growl', 'Group', 'CSRF', ($scope, $state, $stateParams, Member, dialogs, growl, Group, CSRF) -> - CSRF.setMetaTags() - - - ### PUBLIC SCOPE ### - - ## API URL where the form will be posted - $scope.actionUrl = "/api/members/" + $stateParams.id - - ## Form action on the above URL - $scope.method = 'patch' - - ## The user to edit - $scope.user = {} - - ## Profiles types (student/standard/...) - $scope.groups = [] - - - - ### PRIVATE SCOPE ### - - ## - # Kind of constructor: these actions will be realized first when the controller is loaded - ## - initialize = -> - ## Retrieve the member's profile details - Member.get {id: $stateParams.id}, (resp)-> - $scope.user = resp - - ## Using the MembersController - new MembersController($scope, $state, Group) - - - - ## !!! MUST BE CALLED AT THE END of the controller - initialize() -] - - - -## -# Controller used in the member's creation page (admin view) -## -Application.Controllers.controller "newMemberController", ["$scope", "$state", "$stateParams", "Member", 'Group', 'CSRF', ($scope, $state, $stateParams, Member, Group, CSRF) -> - CSRF.setMetaTags() - - ### PUBLIC SCOPE ### - - ## API URL where the form will be posted - $scope.actionUrl = "/api/members" - - ## Form action on the above URL - $scope.method = 'post' - - ## Default member's profile parameters - $scope.user = - plan_interval: '' - - - - ## Using the MembersController - new MembersController($scope, $state, Group) -] diff --git a/app/assets/javascripts/controllers/admin/members.coffee.erb b/app/assets/javascripts/controllers/admin/members.coffee.erb new file mode 100644 index 000000000..7e135ce54 --- /dev/null +++ b/app/assets/javascripts/controllers/admin/members.coffee.erb @@ -0,0 +1,439 @@ +'use strict' + +### COMMON CODE ### + +## +# Provides a set of common properties and methods to the $scope parameter. They are used +# in the various members' admin controllers. +# +# Provides : +# - $scope.groups = [{Group}] +# - $scope.trainings = [{Training}] +# - $scope.plans = [] +# - $scope.datePicker = {} +# - $scope.submited(content) +# - $scope.cancel() +# - $scope.fileinputClass(v) +# - $scope.openDatePicker($event) +# - $scope.openSubscriptionDatePicker($event) +# +# Requires : +# - $state (Ui-Router) [ 'app.admin.members' ] +## +class MembersController + constructor: ($scope, $state, $locale, Group, Training) -> + + ## Retrieve the profiles groups (eg. students ...) + Group.query (groups) -> + $scope.groups = groups + + ## Retrieve the list the available trainings + Training.query().$promise.then (data)-> + $scope.trainings = data.map (d) -> + id: d.id + name: d.name + + ## Default parameters for AngularUI-Bootstrap datepicker + $scope.datePicker = + format: $locale.DATETIME_FORMATS.shortDate + opened: false # default: datePicker is not shown + subscription_date_opened: false + options: + startingDay: Fablab.weekStartingDay + + ## + # Shows the birth day datepicker + # @param $event {Object} jQuery event object + ## + $scope.openDatePicker = ($event) -> + $event.preventDefault() + $event.stopPropagation() + $scope.datePicker.opened = true + + + + ## + # Shows the end of subscription datepicker + # @param $event {Object} jQuery event object + ## + $scope.openSubscriptionDatePicker = ($event) -> + $event.preventDefault() + $event.stopPropagation() + $scope.datePicker.subscription_date_opened = true + + + + ## + # For use with ngUpload (https://github.com/twilson63/ngUpload). + # Intended to be the callback when an upload is done: any raised error will be stacked in the + # $scope.alerts array. If everything goes fine, the user is redirected to the members listing page. + # @param content {Object} JSON - The upload's result + ## + $scope.submited = (content) -> + if !content.id? + $scope.alerts = [] + angular.forEach content, (v, k)-> + angular.forEach v, (err)-> + $scope.alerts.push + msg: k+': '+err, + type: 'danger' + else + $state.go('app.admin.members') + + + + ## + # Changes the admin's view to the members list page + ## + $scope.cancel = -> + $state.go('app.admin.members') + + + + ## + # For use with 'ng-class', returns the CSS class name for the uploads previews. + # The preview may show a placeholder or the content of the file depending on the upload state. + # @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules) + ## + $scope.fileinputClass = (v)-> + if v + 'fileinput-exists' + else + 'fileinput-new' + + +## +# Controller used in the members/groups management page +## +Application.Controllers.controller "AdminMembersController", ["$scope", 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t' +, ($scope, membersPromise, adminsPromise, growl, Admin, dialogs, _t) -> + + + + ### PUBLIC SCOPE ### + + ## members list + $scope.members = membersPromise + + ## admins list + $scope.admins = adminsPromise.admins + + ## Members ordering/sorting. Default: not sorted + $scope.orderMember = null + + ## Admins ordering/sorting. Default: not sorted + $scope.orderAdmin = null + + + + ## + # Change the members ordering criterion to the one provided + # @param orderBy {string} ordering criterion + ## + $scope.setOrderMember = (orderBy)-> + if $scope.orderMember == orderBy + $scope.orderMember = '-'+orderBy + else + $scope.orderMember = orderBy + + + + ## + # Change the admins ordering criterion to the one provided + # @param orderBy {string} ordering criterion + ## + $scope.setOrderAdmin = (orderAdmin)-> + if $scope.orderAdmin == orderAdmin + $scope.orderAdmin = '-'+orderAdmin + else + $scope.orderAdmin = orderAdmin + + + + ## + # Ask for confirmation then delete the specified administrator + # @param admins {Array} full list of administrators + # @param admin {Object} administrator to delete + ## + $scope.destroyAdmin = (admins, admin)-> + dialogs.confirm + resolve: + object: -> + title: _t('confirmation_required') + msg: _t('do_you_really_want_to_delete_this_administrator_this_cannot_be_undone') + , -> # cancel confirmed + Admin.delete id: admin.id, -> + admins.splice(findAdminIdxById(admins, admin.id), 1) + growl.success(_t('administrator_successfully_deleted')) + , (error)-> + growl.error(_t('unable_to_delete_the_administrator')) + + + + ### PRIVATE SCOPE ### + + ## + # Iterate through the provided array and return the index of the requested admin + # @param admins {Array} full list of users with role 'admin' + # @param id {Number} user id of the admin to retrieve in the list + # @returns {Number} index of the requested admin, in the provided array + ## + findAdminIdxById = (admins, id)-> + (admins.map (admin)-> + admin.id + ).indexOf(id) +] + + +## +# Controller used in the member edition page +## +Application.Controllers.controller "EditMemberController", ["$scope", "$state", "$stateParams", '$locale', "Member", 'Training', 'dialogs', 'growl', 'Group', 'Subscription', 'CSRF', 'memberPromise', 'tagsPromise', '$uibModal', 'Plan', '$filter', '_t' +, ($scope, $state, $stateParams, $locale, Member, Training, dialogs, growl, Group, Subscription, CSRF, memberPromise, tagsPromise, $uibModal, Plan, $filter, _t) -> + + + + ### PUBLIC SCOPE ### + + ## API URL where the form will be posted + $scope.actionUrl = "/api/members/" + $stateParams.id + + ## Form action on the above URL + $scope.method = 'patch' + + ## List of tags associables with user + $scope.tags = tagsPromise + + ## The user to edit + $scope.user = memberPromise + + ## the user subscription + if $scope.user.subscribed_plan? and $scope.user.subscription? + $scope.subscription = $scope.user.subscription + $scope.subscription.expired_at = $scope.subscription.expired_at + else + Plan.query group_id: $scope.user.group_id, (plans)-> + $scope.plans = plans + for plan in $scope.plans + plan.nameToDisplay = $filter('humanReadablePlanName')(plan) + + + ## Available trainings list + $scope.trainings = [] + + ## Profiles types (student/standard/...) + $scope.groups = [] + + + + ## + # Open a modal dialog, allowing the admin to extend the current user's subscription (freely or not) + # @param subscription {Object} User's subscription object + # @param free {boolean} True if the extent is offered, false otherwise + ## + $scope.updateSubscriptionModal = (subscription, free)-> + modalInstance = $uibModal.open + animation: true, + templateUrl: '<%= asset_path "admin/subscriptions/expired_at_modal.html" %>' + size: 'lg', + controller: ['$scope', '$uibModalInstance', 'Subscription', ($scope, $uibModalInstance, Subscription) -> + $scope.new_expired_at = angular.copy(subscription.expired_at) + $scope.free = free + $scope.datePicker = + opened: false + format: $locale.DATETIME_FORMATS.shortDate + options: + startingDay: Fablab.weekStartingDay + minDate: new Date + + $scope.openDatePicker = (ev)-> + ev.preventDefault(); + ev.stopPropagation(); + $scope.datePicker.opened = true + + + $scope.ok = -> + Subscription.update { id: subscription.id }, { subscription: { expired_at: $scope.new_expired_at, free: free } }, (_subscription)-> + growl.success(_t('you_successfully_changed_the_expiration_date_of_the_user_s_subscription')) + $uibModalInstance.close(_subscription) + , (error)-> + growl.error(_t('a_problem_occurred_while_saving_the_date')) + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + ] + # once the form was validated succesfully ... + modalInstance.result.then (subscription) -> + $scope.subscription.expired_at = subscription.expired_at + + + + ## + # Open a modal dialog allowing the admin to set a subscription for the given user. + # @param user {Object} User object, user currently reviewed, as recovered from GET /api/members/:id + # @param plans {Array} List of plans, availables for the currently reviewed user, as recovered from GET /api/plans + ## + $scope.createSubscriptionModal = (user, plans)-> + modalInstance = $uibModal.open + animation: true, + templateUrl: '<%= asset_path "admin/subscriptions/create_modal.html" %>' + size: 'lg', + controller: ['$scope', '$uibModalInstance', 'Subscription', 'Group', ($scope, $uibModalInstance, Subscription, Group) -> + + ## selected user + $scope.user = user + + ## available plans for the selected user + $scope.plans = plans + + ## + # Generate a string identifying the given plan by literal humain-readable name + # @param plan {Object} Plan object, as recovered from GET /api/plan/:id + # @param groups {Array} List of Groups objects, as recovered from GET /api/groups + # @param short {boolean} If true, the generated name will contains the group slug, otherwise the group full name + # will be included. + # @returns {String} + ## + $scope.humanReadablePlanName = (plan, groups, short)-> + "#{$filter('humanReadablePlanName')(plan, groups, short)}" + + ## + # Modal dialog validation callback + ## + $scope.ok = -> + $scope.subscription.user_id = user.id + Subscription.save { }, { subscription: $scope.subscription }, (_subscription)-> + + growl.success(_t('subscription_successfully_purchased')) + $uibModalInstance.close(_subscription) + $state.reload() + , (error)-> + growl.error(_t('a_problem_occurred_while_taking_the_subscription')) + + ## + # Modal dialog cancellation callback + ## + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + ] + # once the form was validated succesfully ... + modalInstance.result.then (subscription) -> + $scope.subscription = subscription + + + + ### PRIVATE SCOPE ### + + + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + CSRF.setMetaTags() + + # init the birth date to JS object + $scope.user.profile.birthday = moment($scope.user.profile.birthday).toDate() + + ## the user subscription + if $scope.user.subscribed_plan? and $scope.user.subscription? + $scope.subscription = $scope.user.subscription + $scope.subscription.expired_at = $scope.subscription.expired_at + else + Plan.query group_id: $scope.user.group_id, (plans)-> + $scope.plans = plans + for plan in $scope.plans + plan.nameToDisplay = "#{plan.base_name} - #{plan.interval}" + + # Using the MembersController + new MembersController($scope, $state, $locale, Group, Training) + + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() +] + + + +## +# Controller used in the member's creation page (admin view) +## +Application.Controllers.controller "NewMemberController", ["$scope", "$state", "$locale", "$stateParams", "Member", 'Training', 'Group', 'CSRF' +, ($scope, $state, $locale, $stateParams, Member, Training, Group, CSRF) -> + + CSRF.setMetaTags() + + ### PUBLIC SCOPE ### + + ## API URL where the form will be posted + $scope.actionUrl = "/api/members" + + ## Form action on the above URL + $scope.method = 'post' + + ## Default member's profile parameters + $scope.user = + plan_interval: '' + + + + ## Using the MembersController + new MembersController($scope, $state, $locale, Group, Training) +] + + + +## +# Controller used in the admin's creation page (admin view) +## +Application.Controllers.controller 'NewAdminController', ['$state', '$scope', '$locale', 'Admin', 'growl', ($state, $scope, $locale, Admin, growl)-> + + ## default admin profile + $scope.admin = + profile_attributes: + gender: true + + ## Default parameters for AngularUI-Bootstrap datepicker + $scope.datePicker = + format: $locale.DATETIME_FORMATS.shortDate + opened: false + options: + startingDay: Fablab.weekStartingDay + + + + ## + # Shows the birth day datepicker + # @param $event {Object} jQuery event object + ## + $scope.openDatePicker = ($event)-> + $scope.datePicker.opened = true + + + + ## + # Send the new admin, currently stored in $scope.admin, to the server for database saving + ## + $scope.saveAdmin = -> + Admin.save {}, { admin: $scope.admin }, -> + growl.success(_t('administrator_successfully_created_he_will_receive_his_connection_directives_by_email', {GENDER:getGender($scope.admin)}, "messageformat")) + $state.go('app.admin.members') + , (error)-> + console.log(error) + + + + ### PRIVATE SCOPE ### + + ## + # Return an enumerable meaninful string for the gender of the provider user + # @param user {Object} Database user record + # @return {string} 'male' or 'female' + ## + getGender = (user) -> + if user.profile_attributes + if user.profile_attributes.gender then 'male' else 'female' + else 'other' + + +] diff --git a/app/assets/javascripts/controllers/admin/plans.coffee.erb b/app/assets/javascripts/controllers/admin/plans.coffee.erb new file mode 100644 index 000000000..c5bf1151b --- /dev/null +++ b/app/assets/javascripts/controllers/admin/plans.coffee.erb @@ -0,0 +1,260 @@ +'use strict' + +### COMMON CODE ### + + + +class PlanController + + constructor: ($scope, groups, plans, machines, prices, partners, CSRF) -> + # protection against request forgery + CSRF.setMetaTags() + + + + ## groups list + $scope.groups = groups + + ## plans list + $scope.plans = plans + + ## machines list + $scope.machines = machines + + ## users with role 'partner', notifiables for a partner plan + $scope.partners = partners.users + + ## Subscriptions prices, machines prices and training prices, per groups + $scope.group_pricing = prices + + ## + # For use with 'ng-class', returns the CSS class name for the uploads previews. + # The preview may show a placeholder or the content of the file depending on the upload state. + # @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules) + ## + $scope.fileinputClass = (v)-> + if v + 'fileinput-exists' + else + 'fileinput-new' + + ## + # Mark the provided file for deletion + # @param file {Object} + ## + $scope.deleteFile = (file) -> + if file? and file.id? + file._destroy = true + + + + ## + # Retrieve a plan from its numeric identifier + # @param id {number} plan ID + # @returns {Object} Plan, inherits from $resource + ## + $scope.getPlanFromId = (id) -> + for plan in $scope.plans + if plan.id == id + return plan + + + + + + +## +# Controller used in the plan creation form +## +Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal', 'groups', 'plans', 'machines', 'prices', 'partners', 'CSRF', '$state', 'growl', '_t', '$locale' +, ($scope, $uibModal, groups, plans, machines, prices, partners, CSRF, $state, growl, _t, $locale) -> + + + + ### PRIVATE STATIC CONSTANTS ### + + ## when creating a new contact for a partner plan, this ID will be sent to the server + NEW_PARTNER_ID: null + + + + ### PUBLIC SCOPE ### + + ## current form is used to create a new plan + $scope.mode = 'creation' + + ## prices bindings + $scope.prices = + training: {} + machine: {} + + ## form inputs bindings + $scope.plan = + type: null + group_id: null + interval: null + intervalCount: 0 + amount: null + isRolling: false + partnerId: null + partnerContact: null + ui_weight: 0 + + ## API URL where the form will be posted + $scope.actionUrl = "/api/plans/" + + ## HTTP method for the rest API + $scope.method = 'POST' + + + ## currency symbol for the current locale (cf. angular-i18n) + $scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM; + + + + ## + # Checks if the partner contact is a valid data. Used in the form validation process + # @returns {boolean} + ## + $scope.partnerIsValid = -> + ($scope.plan.type == "Plan") or ($scope.plan.partnerId or ($scope.plan.partnerContact and $scope.plan.partnerContact.email)) + + + + ## + # Open a modal dialog allowing the admin to create a new partner user + ## + $scope.openPartnerNewModal = (subscription)-> + modalInstance = $uibModal.open + animation: true, + templateUrl: '<%= asset_path "shared/_partner_new_modal.html" %>' + size: 'lg', + controller: ['$scope', '$uibModalInstance', 'User', ($scope, $uibModalInstance, User) -> + $scope.partner = {} + + $scope.ok = -> + User.save {}, { user: $scope.partner }, (user)-> + $scope.partner.id = user.id + $scope.partner.name = "#{user.first_name} #{user.last_name}" + $uibModalInstance.close($scope.partner) + , (error)-> + growl.error(_t('unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name')) + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + ] + # once the form was validated succesfully ... + modalInstance.result.then (partner) -> + $scope.partners.push(partner) + $scope.plan.partnerId = partner.id + + + + ## + # Display some messages and redirect the user, once the form was submitted, depending on the result status + # (failed/succeeded). + # @param content {Object} + ## + $scope.afterSubmit = (content) -> + if !content.id? and !content.plan_ids? + growl.error(_t('unable_to_create_the_subscription_please_try_again')) + else + growl.success(_t('successfully_created_subscription(s)_dont_forget_to_redefine_prices')) + if content.plan_ids? + $state.go('app.admin.pricing') + else + if content.id? + $state.go('app.admin.plans.edit', {id: content.id}) + + + + new PlanController($scope, groups, plans, machines, prices, partners, CSRF) +] + + + +## +# Controller used in the plan edition form +## +Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'plans', 'planPromise', 'machines', 'prices', 'partners', 'CSRF', '$state', '$stateParams', 'growl', '$filter', '_t', '$locale' +, ($scope, groups, plans, planPromise, machines, prices, partners, CSRF, $state, $stateParams, growl, $filter, _t, $locale) -> + + + + ### PUBLIC SCOPE ### + $scope.groups = groups + ## current form is used for edition mode + $scope.mode = 'edition' + + ## edited plan data + $scope.plan = planPromise + $scope.plan.type = "Plan" if $scope.plan.type == null + + ## API URL where the form will be posted + $scope.actionUrl = "/api/plans/" + $stateParams.id + + ## HTTP method for the rest API + $scope.method = 'PATCH' + + + ## currency symbol for the current locale (cf. angular-i18n) + $scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM; + + + + ## + # If a parent plan was set ($scope.plan.parent), the prices will be copied from this parent plan into + # the current plan prices list. Otherwise, the current plan prices will be erased. + ## + $scope.copyPricesFromPlan = -> + if $scope.plan.parent + parentPlan = $scope.getPlanFromId($scope.plan.parent) + for parentPrice in parentPlan.prices + for childKey, childPrice of $scope.plan.prices + if childPrice.priceable_type == parentPrice.priceable_type and childPrice.priceable_id == parentPrice.priceable_id + $scope.plan.prices[childKey].amount = parentPrice.amount + break + # if no plan were selected, unset every prices + else + for key, price of $scope.plan.prices + $scope.plan.prices[key].amount = 0 + + + + ## + # Display some messages once the form was submitted, depending on the result status (failed/succeeded) + # @param content {Object} + ## + $scope.afterSubmit = (content) -> + if !content.id? and !content.plan_ids? + growl.error(_t('unable_to_save_subscription_changes_please_try_again')) + else + growl.success(_t('subscription_successfully_changed')) + $state.go('app.admin.pricing') + + + + ## + # Generate a string identifying the given plan by literal humain-readable name + # @param plan {Object} Plan object, as recovered from GET /api/plan/:id + # @param groups {Array} List of Groups objects, as recovered from GET /api/groups + # @param short {boolean} If true, the generated name will contains the group slug, otherwise the group full name + # will be included. + # @returns {String} + ## + $scope.humanReadablePlanName = (plan, groups, short)-> + "#{$filter('humanReadablePlanName')(plan, groups, short)}" + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + # Using the PlansController + new PlanController($scope, groups, plans, machines, prices, partners, CSRF) + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() +] \ No newline at end of file diff --git a/app/assets/javascripts/controllers/admin/pricing.coffee.erb b/app/assets/javascripts/controllers/admin/pricing.coffee.erb new file mode 100644 index 000000000..7a60717df --- /dev/null +++ b/app/assets/javascripts/controllers/admin/pricing.coffee.erb @@ -0,0 +1,383 @@ +'use strict' + +## +# Controller used in the prices edition page +## +Application.Controllers.controller "EditPricingController", ["$scope", "$state", '$uibModal', 'Training', 'TrainingsPricing', 'Machine', '$filter', 'Credit', 'Pricing', 'Plan', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', '_t' +, ($scope, $state, $uibModal, Training, TrainingsPricing, Machine, $filter, Credit, Pricing, Plan, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, _t) -> + + ### PUBLIC SCOPE ### + ## List of machines prices (not considering any plan) + $scope.machinesPrices = machinesPricesPromise.prices + + ## List of trainings pricing + $scope.trainingsPricings = trainingsPricingsPromise + + ## List of available subscriptions plans (eg. student/month, PME/year ...) + $scope.plans = plans + + ## List of groups (eg. normal, student ...) + $scope.groups = groups + + ## Associate free machine hours with subscriptions + $scope.machineCredits = [] + + ## Array of associations (plan <-> training) + $scope.trainingCredits = [] + + ## Associate a plan with all its trainings ids + $scope.trainingCreditsGroups = {} + + ## List of trainings + $scope.trainings = [] + + ## List of machines + $scope.machines = [] + + ## The plans list ordering. Default: by group + $scope.orderPlans = 'group_id' + + ## Status of the drop-down menu in Credits tab + $scope.status = + isopen: false + + + + $scope.findTrainingsPricing = (trainingsPricings, trainingId, groupId)-> + for trainingsPricing in trainingsPricings + if trainingsPricing.training_id == trainingId and trainingsPricing.group_id == groupId + return trainingsPricing + + + $scope.updateTrainingsPricing = (data, trainingsPricing)-> + if data? + TrainingsPricing.update({ id: trainingsPricing.id }, { trainings_pricing: { amount: data } }).$promise + else + _t('please_specify_a_number') + + ## + # Retrieve a plan from its given identifier and returns it + # @param id {number} plan ID + # @returns {Object} Plan, inherits from $resource + ## + $scope.getPlanFromId = (id) -> + for plan in $scope.plans + if plan.id == parseInt(id) + return plan + + + + ## + # Retrieve a group from its given identifier and returns it + # @param id {number} group ID + # @returns {Object} Group, inherits from $resource + ## + $scope.getGroupFromId = (groups, id) -> + for group in groups + if group.id == parseInt(id) + return group + + + ## + # Returns a human readable string of named trainings, according to the provided array. + # $scope.trainings may contains the full list of training. The returned string will only contains the trainings + # whom ID are given in the provided parameter + # @param trainings {Array} trainings IDs array + ## + $scope.showTrainings = (trainings) -> + unless angular.isArray(trainings) and trainings.length > 0 + return _t('none') + + selected = [] + angular.forEach $scope.trainings, (t) -> + if trainings.indexOf(t.id) >= 0 + selected.push t.name + return if selected.length then selected.join(' | ') else _t('none') + + + + ## + # Validation callback when editing training's credits. Save the changes. + # @param newdata {Object} training and associated plans + # @param planId {number|string} plan id + ## + $scope.saveTrainingCredits = (newdata, planId) -> + # save the number of credits + Plan.update {id: planId}, + training_credit_nb: newdata.training_credits + , angular.noop() # do nothing in case of success + , (error) -> + growl.error(_t('an_error_occurred_while_saving_the_number_of_credits')) + + # save the associated trainings + angular.forEach $scope.trainingCreditsGroups, (original, key) -> + if parseInt(key) == parseInt(planId) # we've got the original data + if original.join('_') != newdata.training_ids.join('_') # if any changes + # iterate through the previous credits to remove + angular.forEach original, (oldTrainingId) -> + if newdata.training_ids.indexOf(oldTrainingId) == -1 + tc = findTrainingCredit(oldTrainingId, planId) + if tc + tc.$delete {} + , -> + $scope.trainingCredits.splice($scope.trainingCredits.indexOf(tc), 1) + $scope.trainingCreditsGroups[planId].splice($scope.trainingCreditsGroups[planId].indexOf(tc.id), 1) + , (error) -> + growl.error(_t('an_error_occurred_while_deleting_credit_with_the_TRAINING', {TRAINING:tc.creditable.name})) + else + growl.error(_t('an_error_occurred_unable_to_find_the_credit_to_revoke')) + + # iterate through the new credits to add + angular.forEach newdata.training_ids, (newTrainingId) -> + if original.indexOf(newTrainingId) == -1 + Credit.save + credit: + creditable_id: newTrainingId + creditable_type: 'Training' + plan_id: planId + , (newTc) -> # success + $scope.trainingCredits.push(newTc) + $scope.trainingCreditsGroups[newTc.plan_id].push(newTc.creditable_id) + , (error) -> # failed + training = getTrainingFromId(newTrainingId) + growl.error(_t('an_error_occurred_while_creating_credit_with_the_TRAINING', {TRAINING: training.name})) + console.error(error) + + + + + ## + # Cancel the current training credit modification + # @param rowform {Object} see http://vitalets.github.io/angular-xeditable/ + ## + $scope.cancelTrainingCredit = (rowform) -> + rowform.$cancel() + + + ## + # Create a new empty entry in the $scope.machineCredits array + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.addMachineCredit = (e)-> + e.preventDefault() + e.stopPropagation() + $scope.inserted = + creditable_type: 'Machine' + $scope.machineCredits.push($scope.inserted) + $scope.status.isopen = !$scope.status.isopen + + + + ## + # In the Credits tab, while editing a machine credit row, select the current machine from the + # drop-down list of machines as the current item. + # @param credit {Object} credit object, inherited from $resource + ## + $scope.showCreditableName = (credit) -> + selected = _t('not_set') + if credit and credit.creditable_id + angular.forEach $scope.machines, (m)-> + if m.id == credit.creditable_id + selected = m.name+' ( id. '+m.id+' )' + return selected + + + + ## + # Validation callback when editing machine's credits. Save the changes. + # This will prevent the creation of two credits associating the same machine and plan. + # @param data {Object} machine, associated plan and number of credit hours. + # @param [id] {number} credit id for edition, create a new credit object if not provided + ## + $scope.saveMachineCredit = (data, id) -> + for mc in $scope.machineCredits + if mc.plan_id == data.plan_id and mc.creditable_id == data.creditable_id and (id == null or mc.id != id) + growl.error(_t('error_a_credit_linking_this_machine_with_that_subscription_already_exists')) + unless id + $scope.machineCredits.pop() + return false + + if id? + Credit.update {id: id}, credit: data, -> + growl.success(_t('changes_have_been_successfully_saved')) + else + data.creditable_type = 'Machine' + Credit.save + credit: data + , (resp) -> + $scope.machineCredits[$scope.machineCredits.length-1].id = resp.id + growl.success(_t('credit_was_successfully_saved')) + + + ## + # Removes the newly inserted but not saved machine credit / Cancel the current machine credit modification + # @param rowform {Object} see http://vitalets.github.io/angular-xeditable/ + # @param index {number} theme index in the $scope.machineCredits array + ## + $scope.cancelMachineCredit = (rowform, index) -> + if $scope.machineCredits[index].id? + rowform.$cancel() + else + $scope.machineCredits.splice(index, 1) + + + + ## + # Deletes the machine credit at the specified index + # @param index {number} machine credit index in the $scope.machineCredits array + ## + $scope.removeMachineCredit = (index) -> + Credit.delete $scope.machineCredits[index] + $scope.machineCredits.splice(index, 1) + + + + ## + # If the plan does not have a type, return a default value for display purposes + # @param type {string|undefined|null} plan's type (eg. 'partner') + # @returns {string} + ## + $scope.getPlanType = (type) -> + if type == 'PartnerPlan' + return _t('partner') + else return _t('standard') + + ## + # Change the plans ordering criterion to the one provided + # @param orderBy {string} ordering criterion + ## + $scope.setOrderPlans = (orderBy) -> + if $scope.orderPlans == orderBy + $scope.orderPlans = '-'+orderBy + else + $scope.orderPlans = orderBy + + ## + # Retrieve a price from prices array by a machineId and a groupId + ## + $scope.findPriceBy = (prices, machineId, groupId)-> + for price in prices + if price.priceable_id == machineId and price.group_id == groupId + return price + + ## + # update a price for a machine and a group, not considering any plan + ## + $scope.updatePrice = (data, price)-> + if data? + Price.update({ id: price.id }, { price: { amount: data } }).$promise + else + _t('please_specify_a_number') + + ## + # Delete the specified subcription plan + # @param id {number} plan id + ## + $scope.deletePlan = (plans, id) -> + if typeof id != 'number' + console.error('[editPricingController::deletePlan] Error: invalid id parameter') + else + # open a confirmation dialog + dialogs.confirm + resolve: + object: -> + title: _t('confirmation_required') + msg: _t('do_you_really_want_to_delete_this_subscription_plan') + , -> + # the admin has confirmed, delete the plan + Plan.delete {id: id}, (res) -> + growl.success(_t('subscription_plan_was_successfully_deleted')) + $scope.plans.splice(findPlanIdxById(plans, id), 1) + + , (error) -> + console.error('[editPricingController::deletePlan] Error: '+error.statusText) if error.statusText + growl.error(_t('unable_to_delete_the_specified_subscription_an_error_occurred')) + + + + ## + # Generate a string identifying the given plan by literal humain-readable name + # @param plan {Object} Plan object, as recovered from GET /api/plan/:id + # @param groups {Array} List of Groups objects, as recovered from GET /api/groups + # @param short {boolean} If true, the generated name will contains the group slug, otherwise the group full name + # will be included. + # @returns {String} + ## + $scope.humanReadablePlanName = (plan, groups, short)-> + "#{$filter('humanReadablePlanName')(plan, groups, short)}" + + + + ### PRIVATE SCOPE ### + + findPlanIdxById = (plans, id)-> + (plans.map (plan)-> + plan.id + ).indexOf(id) + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + + Credit.query({creditable_type: 'Training'}).$promise.then (data)-> + $scope.trainingCredits = data + $scope.trainingCreditsGroups = groupCreditsByPlan(data) + + ## adds empty array for plan which hasn't any credits yet + for plan in $scope.plans + unless $scope.trainingCreditsGroups[plan.id]? + $scope.trainingCreditsGroups[plan.id] = [] + + Credit.query({creditable_type: 'Machine'}).$promise.then (data)-> + $scope.machineCredits = data + + Training.query().$promise.then (data)-> + $scope.trainings = data + + Machine.query().$promise.then (data)-> + $scope.machines = data + + + ## + # Group the given credits array into a map associating the plan ID with its associated trainings/machines + # @return {Object} the association map + ## + groupCreditsByPlan = (credits) -> + creditsMap = {} + angular.forEach credits, (c) -> + unless creditsMap[c.plan_id] + creditsMap[c.plan_id] = [] + creditsMap[c.plan_id].push(c.creditable_id) + creditsMap + + + + ## + # Iterate through $scope.traininfCredits to find the credit matching the given criterion + # @param trainingId {number|string} training ID + # @param planId {number|string} plan ID + ## + findTrainingCredit = (trainingId, planId) -> + trainingId = parseInt(trainingId) + planId = parseInt(planId) + + for credit in $scope.trainingCredits + if credit.plan_id == planId and credit.creditable_id == trainingId + return credit + + + ## + # Retrieve a training from its given identifier and returns it + # @param id {number} training ID + # @returns {Object} Training inherited from $resource + ## + getTrainingFromId = (id) -> + for training in $scope.trainings + if training.id == parseInt(id) + return training + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() +] diff --git a/app/assets/javascripts/controllers/admin/project_elements.coffee b/app/assets/javascripts/controllers/admin/project_elements.coffee index e5f9e0460..67b7c25c8 100644 --- a/app/assets/javascripts/controllers/admin/project_elements.coffee +++ b/app/assets/javascripts/controllers/admin/project_elements.coffee @@ -1,17 +1,16 @@ 'use strict' -Application.Controllers.controller "projectElementsController", ["$scope", "$state", 'Component', 'Licence', 'Theme', ($scope, $state, Component, Licence, Theme) -> +Application.Controllers.controller "ProjectElementsController", ["$scope", "$state", 'Component', 'Licence', 'Theme', 'componentsPromise', 'licencesPromise', 'themesPromise' +, ($scope, $state, Component, Licence, Theme, componentsPromise, licencesPromise, themesPromise) -> ## Materials list (plastic, wood ...) - $scope.components = Component.query() + $scope.components = componentsPromise ## Licences list (Creative Common ...) - $scope.licences = Licence.query() + $scope.licences = licencesPromise ## Themes list (cooking, sport ...) - $scope.themes = Theme.query() - - + $scope.themes = themesPromise ## # Saves a new component / Update an existing material to the server (form validation callback) @@ -153,5 +152,3 @@ Application.Controllers.controller "projectElementsController", ["$scope", "$sta else $scope.licences.splice(index, 1) ] - - diff --git a/app/assets/javascripts/controllers/admin/settings.coffee b/app/assets/javascripts/controllers/admin/settings.coffee new file mode 100644 index 000000000..ff84c7170 --- /dev/null +++ b/app/assets/javascripts/controllers/admin/settings.coffee @@ -0,0 +1,196 @@ +'use strict' + +Application.Controllers.controller "SettingsController", ["$scope", 'Setting', 'growl', 'settingsPromise', 'cgvFile', 'cguFile', 'logoFile', 'logoBlackFile', 'faviconFile', 'CSRF', '_t' + ($scope, Setting, growl, settingsPromise, cgvFile, cguFile, logoFile, logoBlackFile, faviconFile, CSRF, _t) -> + + + + ### PUBLIC SCOPE ### + + ## timepickers steps configuration + $scope.timepicker = + hstep: 1 + mstep: 15 + + ## API URL where the upload forms will be posted + $scope.actionUrl = + cgu: "/api/custom_assets" + cgv: "/api/custom_assets" + logo: "/api/custom_assets" + logoBlack: "/api/custom_assets" + favicon: "/api/custom_assets" + + ## Form actions on the above URL + $scope.methods = + cgu: "post" + cgv: "post" + logo: "post" + logoBlack: "post" + favicon: "post" + + ## Are we uploading the files currently (if so, display the loader) + $scope.loader = + cgu: false + cgv: false + + ## various parametrable settings + $scope.twitterSetting = { name: 'twitter_name', value: settingsPromise.twitter_name } + $scope.aboutTitleSetting = { name: 'about_title', value: settingsPromise.about_title } + $scope.aboutBodySetting = { name: 'about_body', value: settingsPromise.about_body } + $scope.aboutContactsSetting = { name: 'about_contacts', value: settingsPromise.about_contacts } + $scope.homeBlogpostSetting = { name: 'home_blogpost', value: settingsPromise.home_blogpost } + $scope.machineExplicationsAlert = { name: 'machine_explications_alert', value: settingsPromise.machine_explications_alert } + $scope.trainingExplicationsAlert = { name: 'training_explications_alert', value: settingsPromise.training_explications_alert } + $scope.trainingInformationMessage = { name: 'training_information_message', value: settingsPromise.training_information_message} + $scope.subscriptionExplicationsAlert = { name: 'subscription_explications_alert', value: settingsPromise.subscription_explications_alert } + $scope.eventReducedAmountAlert = { name: 'event_reduced_amount_alert', value: settingsPromise.event_reduced_amount_alert } + $scope.windowStart = { name: 'booking_window_start', value: settingsPromise.booking_window_start } + $scope.windowEnd = { name: 'booking_window_end', value: settingsPromise.booking_window_end } + $scope.mainColorSetting = { name: 'main_color', value: settingsPromise.main_color } + $scope.secondColorSetting = { name: 'secondary_color', value: settingsPromise.secondary_color } + $scope.fablabName = { name: 'fablab_name', value: settingsPromise.fablab_name } + $scope.nameGenre = { name: 'name_genre', value: settingsPromise.name_genre } + $scope.cguFile = cguFile.custom_asset + $scope.cgvFile = cgvFile.custom_asset + $scope.customLogo = logoFile.custom_asset + $scope.customLogoBlack = logoBlackFile.custom_asset + $scope.customFavicon = faviconFile.custom_asset + + $scope.enableMove = + name: 'booking_move_enable' + value: (settingsPromise.booking_move_enable == "true") + + $scope.moveDelay = + name: 'booking_move_delay' + value: parseInt(settingsPromise.booking_move_delay) + + $scope.enableCancel = + name: 'booking_cancel_enable' + value: (settingsPromise.booking_cancel_enable == "true") + + $scope.cancelDelay = + name: 'booking_cancel_delay' + value: parseInt(settingsPromise.booking_cancel_delay) + + + + ## + # For use with 'ng-class', returns the CSS class name for the uploads previews. + # The preview may show a placeholder or the content of the file depending on the upload state. + # @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules) + ## + $scope.fileinputClass = (v)-> + if v + 'fileinput-exists' + else + 'fileinput-new' + + + + ## + # Callback to save the setting value to the database + # @param setting {{value:*, name:string}} note that the value will be stringified + ## + $scope.save = (setting)-> + # trim empty html + if setting.value == "
" or setting.value == "


" + setting.value = "" + # convert dates to ISO format + if setting.value instanceof Date + setting.value = setting.value.toISOString() + + if setting.value isnt null + value = setting.value.toString() + else + value = setting.value + + Setting.update { name: setting.name }, { value: value }, (data)-> + growl.success(_t('customization_of_SETTING_successfully_saved', {SETTING:setting.name})) + , (error)-> + console.log(error) + + + + ## + # For use with ngUpload (https://github.com/twilson63/ngUpload). + # Intended to be the callback when the upload is done: Any raised error will be displayed in a growl + # message. If everything goes fine, a growl success message is shown. + # @param content {Object} JSON - The upload's result + ## + $scope.submited = (content) -> + if !content.custom_asset? + $scope.alerts = [] + angular.forEach content, (v, k)-> + angular.forEach v, (err)-> + growl.error(err) + else + growl.success(_t('file_successfully_updated')) + if content.custom_asset.name is 'cgu-file' + $scope.cguFile = content.custom_asset + $scope.methods.cgu = 'put' + $scope.actionUrl.cgu += '/cgu-file' unless $scope.actionUrl.cgu.indexOf('/cgu-file') > 0 + $scope.loader.cgu = false + else if content.custom_asset.name is 'cgv-file' + $scope.cgvFile = content.custom_asset + $scope.methods.cgv = 'put' + $scope.actionUrl.cgv += '/cgv-file' unless $scope.actionUrl.cgv.indexOf('/cgv-file') > 0 + $scope.loader.cgv = false + else if content.custom_asset.name is 'logo-file' + $scope.logoFile = content.custom_asset + $scope.methods.logo = 'put' + $scope.actionUrl.logo += '/logo-file' unless $scope.actionUrl.logo.indexOf('/logo-file') > 0 + else if content.custom_asset.name is 'logo-black-file' + $scope.logoBlackFile = content.custom_asset + $scope.methods.logoBlack = 'put' + $scope.actionUrl.logoBlack += '/logo-black-file' unless $scope.actionUrl.logoBlack.indexOf('/logo-black-file') > 0 + else if content.custom_asset.name is 'favicon-file' + $scope.faviconFile = content.custom_asset + $scope.methods.favicon = 'put' + $scope.actionUrl.favicon += '/favicon-file' unless $scope.actionUrl.favicon.indexOf('/favicon-file') > 0 + + + + ## + # @param target {String} 'cgu' | 'cgv' + ## + $scope.addLoader = (target) -> + $scope.loader[target] = true + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + # set the authenticity tokens in the forms + CSRF.setMetaTags() + + # we prevent the admin from setting the closing time before the opening time + $scope.$watch 'windowEnd.value', (newValue, oldValue, scope) -> + if $scope.windowStart and moment($scope.windowStart.value).isAfter(newValue) + $scope.windowEnd.value = oldValue + + # change form methods to PUT if items already exists + if cguFile.custom_asset + $scope.methods.cgu = 'put' + $scope.actionUrl.cgu += '/cgu-file' + if cgvFile.custom_asset + $scope.methods.cgv = 'put' + $scope.actionUrl.cgv += '/cgv-file' + if logoFile.custom_asset + $scope.methods.logo = 'put' + $scope.actionUrl.logo += '/logo-file' + if logoBlackFile.custom_asset + $scope.methods.logoBlack = 'put' + $scope.actionUrl.logoBlack += '/logo-black-file' + if faviconFile.custom_asset + $scope.methods.favicon = 'put' + $scope.actionUrl.favicon += '/favicon-file' + + + # init the controller (call at the end !) + initialize() + +] diff --git a/app/assets/javascripts/controllers/admin/statistics.coffee b/app/assets/javascripts/controllers/admin/statistics.coffee new file mode 100644 index 000000000..db22fc55e --- /dev/null +++ b/app/assets/javascripts/controllers/admin/statistics.coffee @@ -0,0 +1,453 @@ +'use strict' + +Application.Controllers.controller "StatisticsController", ["$scope", "$state", "$rootScope", "$locale", "Statistics", "es", "Member", '_t' +, ($scope, $state, $rootScope, $locale, Statistics, es, Member, _t) -> + + + + ### PUBLIC SCOPE ### + + ## ui-view transitions optimization: if true, the stats will never be refreshed + $scope.preventRefresh = false + + ## statistics structure in elasticSearch + $scope.statistics = [] + + ## fablab users list + $scope.members = [] + + ## statistics data recovered from elasticSearch + $scope.data = null + + ## configuration of the widget allowing to pick the ages range + $scope.agePicker = + show: false + start: null + end: null + + ## total CA for the current view + $scope.sumCA = 0 + + ## average users' age for the current view + $scope.averageAge = 0 + + ## total of the stat field for non simple types + $scope.sumStat = 0 + + ## default: results are not sorted + $scope.sorting = + ca: 'none' + + ## active tab will be set here + $scope.selectedIndex = null + + ## type filter binding + $scope.type = + selected: null + active: null + + ## selected custom filter + $scope.customFilter = + show: false + criterion: {} + value : null + exclude: false + datePicker: + format: $locale.DATETIME_FORMATS.shortDate + opened: false # default: datePicker is not shown + minDate: null + maxDate: moment().toDate() + options: + startingDay: 1 # France: the week starts on monday + + ## available custom filters + $scope.filters = [] + + ## default: we do not open the datepicker menu + $scope.datePicker = + show: false + + ## datePicker parameters for interval beginning + $scope.datePickerStart = + format: $locale.DATETIME_FORMATS.shortDate + opened: false # default: datePicker is not shown + minDate: null + maxDate: moment().subtract(1, 'day').toDate() + selected: moment().utc().subtract(1, 'months').subtract(1, 'day').startOf('day').toDate() + options: + startingDay: Fablab.weekStartingDay + + ## datePicker parameters for interval ending + $scope.datePickerEnd = + format: $locale.DATETIME_FORMATS.shortDate + opened: false # default: datePicker is not shown + minDate: null + maxDate: moment().subtract(1, 'day').toDate() + selected: moment().subtract(1, 'day').endOf('day').toDate() + options: + startingDay: Fablab.weekStartingDay + + + + ## + # Callback to open the datepicker (interval start) + # @param $event {Object} jQuery event object + ## + $scope.toggleStartDatePicker = ($event) -> + toggleDatePicker($event, $scope.datePickerStart) + + + + ## + # Callback to open the datepicker (interval end) + # @param $event {Object} jQuery event object + ## + $scope.toggleEndDatePicker = ($event) -> + toggleDatePicker($event, $scope.datePickerEnd) + + + + ## + # Callback to open the datepicker (custom filter) + # @param $event {Object} jQuery event object + ## + $scope.toggleCustomDatePicker = ($event) -> + toggleDatePicker($event, $scope.customFilter.datePicker) + + + + ## + # Callback called when the active tab is changed. + # recover the current tab and store its value in $scope.selectedIndex + # @param tab {Object} elasticsearch statistic structure + ## + $scope.setActiveTab = (tab) -> + $scope.selectedIndex = tab + $scope.type.selected = tab.types[0] + $scope.type.active = $scope.type.selected + $scope.customFilter.criterion = {} + $scope.customFilter.value = null + $scope.customFilter.exclude = false + $scope.sorting.ca = 'none' + buildCustomFiltersList() + refreshStats() + + + + ## + # Callback to validate the filters and send a new request to elastic + ## + $scope.validateFilterChange = -> + $scope.agePicker.show = false + $scope.customFilter.show = false + $scope.type.active = $scope.type.selected + buildCustomFiltersList() + refreshStats() + + + + ## + # Callback to validate the dates range and refresh the data from elastic + ## + $scope.validateDateChange = -> + $scope.datePicker.show = false + refreshStats() + + + + ## + # Parse the given date and return a user-friendly string + # @param date {Date} JS date or ant moment.js compatible date string + ## + $scope.formatDate = (date) -> + moment(date).format("LL") + + + + ## + # Parse the sex and return a user-friendly string + # @param sex {string} 'male' | 'female' + ## + $scope.formatSex = (sex) -> + if sex == 'male' + return _t('man') + if sex == 'female' + return t('woman') + + + + ## + # Retrieve the label for the given subtype in the current type + # @param key {string} statistic subtype key + ## + $scope.formatSubtype = (key) -> + label = "" + angular.forEach $scope.type.active.subtypes, (subtype) -> + if subtype.key == key + label = subtype.label + label + + + + ## + # Helper usable in ng-switch to determine the input type to display for custom filter value + # @param filter {Object} custom filter criterion + ## + $scope.getCustomValueInputType = (filter) -> + if filter and filter.values + if typeof(filter.values[0]) == 'string' + return filter.values[0] + else if typeof(filter.values[0] == 'object') + return 'input_select' + else + 'input_text' + + + + ## + # Change the sorting order and refresh the results to match the new order + # @param filter {Object} any filter + ## + $scope.toggleSorting = (filter) -> + switch $scope.sorting[filter] + when 'none' then $scope.sorting[filter] = 'asc' + when 'asc' then $scope.sorting[filter] = 'desc' + when 'desc' then $scope.sorting[filter] = 'none' + refreshStats() + + + + ## + # Return the user's name from his given ID + # @param id {number} user ID + ## + $scope.getUserNameFromId = (id) -> + if $scope.members.length == 0 + return "ID "+id + else + for member in $scope.members + if member.id == id + return member.name + return "ID "+id + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + Statistics.query (stats) -> + $scope.statistics = stats + + Member.query (members) -> + $scope.members = members + + # workaround for angular-bootstrap::tabs behavior: on tab deletion, another tab will be selected + # which will cause every tabs to reload, one by one, when the view is closed + $rootScope.$on '$stateChangeStart', (event, toState, toParams, fromState, fromParams) -> + if fromState.name == 'app.admin.statistics' and Object.keys(fromParams).length == 0 + $scope.preventRefresh = true + + + + ## + # Generic function to toggle a bootstrap datePicker + # @param $event {Object} jQuery event object + # @param datePicker {Object} settings object of the concerned datepicker. Must have an 'opened' property + ## + toggleDatePicker = ($event, datePicker) -> + $event.preventDefault() + $event.stopPropagation() + datePicker.opened = !datePicker.opened + + + + ## + # Force update the statistics table, querying elasticSearch according to the current config values + ## + refreshStats = -> + if $scope.selectedIndex and !$scope.preventRefresh + $scope.data = [] + $scope.sumCA = 0 + $scope.averageAge = 0 + $scope.sumStat = 0 + custom = null + if $scope.customFilter.criterion and $scope.customFilter.criterion.key and $scope.customFilter.value + custom = {} + custom.key = $scope.customFilter.criterion.key + custom.value = $scope.customFilter.value + custom.exclude = $scope.customFilter.exclude + queryElasticStats $scope.selectedIndex.es_type_key, $scope.type.active.key, custom, (res, err)-> + if (err) + console.error("[statisticsController::refreshStats] Unable to refresh due to "+err) + else + $scope.data = res.hits + sumCA = 0 + sumAge = 0 + sumStat = 0 + if $scope.data.length > 0 + angular.forEach $scope.data, (datum) -> + if datum._source.ca + sumCA += parseInt(datum._source.ca) + if datum._source.age + sumAge += parseInt(datum._source.age) + if datum._source.stat + sumStat += parseInt(datum._source.stat) + sumAge /= $scope.data.length + $scope.sumCA = sumCA + $scope.averageAge = Math.round(sumAge*100)/100 + $scope.sumStat = sumStat + + + + ## + # Run the elasticSearch query to retreive the /stats/type aggregations + # @param index {String} elasticSearch document type (account|event|machine|project|subscription|training) + # @param type {String} statistics type (month|year|booking|hour|user|project) + # @param custom {{key:{string}, value:{string}}|null} custom filter property or null to disable this filter + # @param callback {function} function be to run after results were retrieved, it will receive + # two parameters : results {Array}, error {String} (if any) + ## + queryElasticStats = (index, type, custom, callback) -> + # handle invalid callback + if typeof(callback) != "function" + console.error('[statisticsController::queryElasticStats] Error: invalid callback provided') + return + + # run query + es.search + "index": "stats" + "type": index + "size": 1000000000 + "body": buildElasticDataQuery(type, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting) + , (error, response) -> + if (error) + callback([], "Error: something unexpected occurred during elasticSearch query: "+error) + else + callback(response.hits) + + + + ## + # Build an object representing the content of the REST-JSON query to elasticSearch, + # based on the provided parameters for row data recovering. + # @param type {String} statistics type (month|year|booking|hour|user|project) + # @param custom {{key:{string}, value:{string}}|null} custom filter property or null to disable this filter + # @param ageMin {Number|null} filter by age: range lower value OR null to do not filter + # @param ageMax {Number|null} filter by age: range higher value OR null to do not filter + # @param intervalBegin {moment} statitics interval beginning (moment.js type) + # @param intervalEnd {moment} statitics interval ending (moment.js type) + # @param sortings {Array|null} elasticSearch criteria for sorting the results + ## + buildElasticDataQuery = (type, custom, ageMin, ageMax, intervalBegin, intervalEnd, sortings) -> + q = + "query": + "bool": + "must": [ + { + "term": + "type": type + } + { + "range": + "date": + "gte": intervalBegin.format() + "lte": intervalEnd.format() + } + ] + # optional date range + if ageMin && ageMax + q.query.bool.must.push + "range": + "age": + "gte": ageMin + "lte": ageMax + # optional criterion + if custom + criterion = { + "match" : {} + } + switch $scope.getCustomValueInputType($scope.customFilter.criterion) + when 'input_date' then criterion.match[custom.key] = moment(custom.value).format('YYYY-MM-DD') + when 'input_select' then criterion.match[custom.key] = custom.value.key + when 'input_list' then criterion.match[custom.key+".name"] = custom.value + else criterion.match[custom.key] = custom.value + + if (custom.exclude) + q = "query": { + "filtered": { + "query": q.query, + "filter": { + "not": { + "term": criterion.match + } + } + } + } + else + q.query.bool.must.push(criterion) + + + if sortings + q["sort"] = buildElasticSortCriteria(sortings) + q + + + + ## + # Parse the provided criteria array and return the corresponding elasticSearch syntax + # @param criteria {Array} array of {key_to_sort:order} + ## + buildElasticSortCriteria = (criteria) -> + crits = [] + angular.forEach criteria, (value, key) -> + if typeof value != 'undefined' and value != null and value != 'none' + c = {} + c[key] = {'order': value} + crits.push(c) + crits + + + + ## + # Fullfil the list of available options in the custom filter panel. The list will be based on common + # properties and on index-specific properties (additional_fields) + ## + buildCustomFiltersList = -> + $scope.filters = [] + + filters = [ + {key: 'date', label: _t('date'), values: ['input_date']}, + {key: 'userId', label: _t('user_id'), values: ['input_number']}, + {key: 'gender', label: _t('gender'), values: [{key:'male', label:_t('man')}, {key:'female', label:_t('woman')}]}, + {key: 'age', label: _t('age'), values: ['input_number']}, + {key: 'subType', label: _t('type'), values: $scope.type.active.subtypes}, + {key: 'ca', label: _t('revenue'), values: ['input_number']} + ] + + $scope.filters = filters + + if !$scope.type.active.simple + f = {key: 'stat', label: $scope.type.active.label, values: ['input_number']} + $scope.filters.push(f) + + angular.forEach $scope.selectedIndex.additional_fields, (field) -> + filter = {key: field.key, label: field.label, values:[]} + switch field.data_type + when 'index' then filter.values.push('input_number') + when 'number' then filter.values.push('input_number') + when 'date' then filter.values.push('input_date') + when 'list' then filter.values.push('input_list') + else filter.values.push('input_text') + + $scope.filters.push(filter) + + + + + # init the controller (call at the end !) + initialize() + +] diff --git a/app/assets/javascripts/controllers/admin/tags.coffee.erb b/app/assets/javascripts/controllers/admin/tags.coffee.erb new file mode 100644 index 000000000..5402a7871 --- /dev/null +++ b/app/assets/javascripts/controllers/admin/tags.coffee.erb @@ -0,0 +1,65 @@ +Application.Controllers.controller "TagsController", ["$scope", 'tagsPromise', 'Tag', 'growl', '_t', ($scope, tagsPromise, Tag, growl, _t) -> + + ## List of users's tags + $scope.tags = tagsPromise + + + + ## + # Removes the newly inserted but not saved tag / Cancel the current tag modification + # @param rowform {Object} see http://vitalets.github.io/angular-xeditable/ + # @param index {number} tag index in the $scope.tags array + ## + $scope.cancelTag = (rowform, index) -> + if $scope.tags[index].id? + rowform.$cancel() + else + $scope.tags.splice(index, 1) + + + + ## + # Creates a new empty entry in the $scope.tags array + ## + $scope.addTag = -> + $scope.inserted = + name: '' + $scope.tags.push($scope.inserted) + + + + ## + # Saves a new tag / Update an existing tag to the server (form validation callback) + # @param data {Object} tag name + # @param [data] {number} tag id, in case of update + ## + $scope.saveTag = (data, id) -> + if id? + Tag.update {id: id}, { tag: data }, (response) -> + growl.success(_t('changes_successfully_saved')) + , (error) -> + growl.error(_t('an_error_occurred_while_saving_changes')) + else + Tag.save { tag: data }, (resp)-> + growl.success(_t('new_tag_successfully_saved')) + $scope.tags[$scope.tags.length-1].id = resp.id + , (error) -> + growl.error(_t('an_error_occurred_while_saving_the_new_tag')) + $scope.tags.splice($scope.tags.length-1, 1) + + + + ## + # Deletes the tag at the specified index + # @param index {number} tag index in the $scope.tags array + ## + $scope.removeTag = (index) -> + # TODO add confirmation : les utilisateurs seront déasociés + Tag.delete { id: $scope.tags[index].id }, (resp) -> + growl.success(_t('tag_successfully_deleted')) + $scope.tags.splice(index, 1) + , (error) -> + growl.error(_t('an_error_occurred_and_the_tag_deletion_failed')) + + +] diff --git a/app/assets/javascripts/controllers/admin/trainings.coffee.erb b/app/assets/javascripts/controllers/admin/trainings.coffee.erb new file mode 100644 index 000000000..44051492f --- /dev/null +++ b/app/assets/javascripts/controllers/admin/trainings.coffee.erb @@ -0,0 +1,230 @@ +'use strict' + +Application.Controllers.controller "TrainingsController", ["$scope", "$state", "$uibModal", 'Training', 'trainingsPromise', 'machinesPromise', '_t', 'growl' +, ($scope, $state, $uibModal, Training, trainingsPromise, machinesPromise, _t, growl) -> + + + + ### PUBLIC SCOPE ### + + ## list of trainings + $scope.trainings = trainingsPromise + + ## simplified list of machines + $scope.machines = machinesPromise + + ## list of training availabilies, grouped by date + $scope.groupedAvailabilities = {} + + ## default: accordions are not open + $scope.accordions = {} + + ## Binding for the parseInt function + $scope.parseInt = parseInt + + ## + # In the trainings listing tab, return the stringified list of machines associated with the provided training + # @param training {Object} Training object, inherited from $resource + # @returns {string} + ## + $scope.showMachines = (training) -> + selected = [] + angular.forEach $scope.machines, (m) -> + if (training.machine_ids.indexOf(m.id) >= 0) + selected.push(m.name) + return if selected.length then selected.join(', ') else _t('none') + + + + ## + # Create a new empty training object and append it to the $scope.trainings list + ## + $scope.addTraining = -> + $scope.inserted = + name: '' + machine_ids: [] + $scope.trainings.push($scope.inserted) + + + + ## + # Saves a new training / Update an existing training to the server (form validation callback) + # @param data {Object} training name, associated machine(s) and default places number + # @param id {number} training id, in case of update + ## + $scope.saveTraining = (data, id) -> + if id? + Training.update {id: id}, + training: data + else + Training.save + training: data + , (resp) -> + $scope.trainings[$scope.trainings.length-1] = resp + console.log(resp) + + + + ## + # Removes the newly inserted but not saved training / Cancel the current training modification + # @param rowform {Object} see http://vitalets.github.io/angular-xeditable/ + # @param index {number} training index in the $scope.trainings array + ## + $scope.cancelTraining = (rowform, index) -> + if $scope.trainings[index].id? + rowform.$cancel() + else + $scope.trainings.splice(index, 1) + + + + ## + # In the trainings monitoring tab, callback to open a modal window displaying the current bookings for the + # provided training slot. The admin will be then able to validate the training for the users that followed + # the training. + # @param training {Object} Training object, inherited from $resource + # @param availability {Object} time slot when the training occurs + ## + $scope.showReservations = (training, availability) -> + $uibModal.open + templateUrl: '<%= asset_path "admin/trainings/validTrainingModal.html" %>' + controller: ['$scope', '$uibModalInstance', ($scope, $uibModalInstance) -> + $scope.availability = availability + + $scope.usersToValid = [] + + ## + # Mark/unmark the provided user for training validation + # @param user {Object} from the availability.reservation_users list + ## + $scope.toggleSelection = (user) -> + index = $scope.usersToValid.indexOf(user) + if index > -1 + $scope.usersToValid.splice(index, 1) + else + $scope.usersToValid.push user + + ## + # Validates the modifications (training validations) and save them to the server + ## + $scope.ok = -> + users = $scope.usersToValid.map (u) -> + u.id + Training.update {id: training.id}, + training: + users: users + , -> # success + angular.forEach $scope.usersToValid, (u) -> + u.is_valid = true + $scope.usersToValid = [] + $uibModalInstance.close(training) + + ## + # Cancel the modifications and close the modal window + ## + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + ] + + + + ## + # Delete the provided training and, in case of sucess, remove it from the trainings list afterwards + # @param index {number} index of the provided training in $scope.trainings + # @param training {Object} training to delete + ## + $scope.removeTraining = (index, training)-> + training.$delete -> + $scope.trainings.splice(index, 1) + growl.info(_t('training_successfully_deleted')) + , (error)-> + growl.warning(_t('unable_to_delete_the_training_because_some_users_alredy_booked_it')) + + + + ## + # Open the modal to edit description of the training + # @param training {Object} Training to edit description + ## + $scope.openModalToSetDescription = (training)-> + $uibModal.open( + templateUrl: "<%= asset_path 'admin/trainings/modal_edit.html' %>" + controller: ['$scope', '$uibModalInstance', 'Training', 'growl', ($scope, $uibModalInstance, Training, growl)-> + $scope.training = training + $scope.save = -> + Training.update id: training.id, { training: { description: $scope.training.description } }, (training)-> + $uibModalInstance.close() + growl.success(_t('description_was_successfully_saved')) + return + ] + ) + + + + ## + # Takes a month number and return its localized literal name + # @param {Number} from 0 to 11 + # @returns {String} eg. 'janvier' + ## + $scope.formatMonth = (number) -> + number = parseInt(number) + moment().month(number).format('MMMM') + + + + ## + # Given a day, month and year, return a localized literal name for the day + # @param day {Number} from 1 to 31 + # @param month {Number} from 0 to 11 + # @param year {Number} Gregorian's year number + # @returns {String} eg. 'mercredi 12' + ## + $scope.formatDay = (day, month, year) -> + day = parseInt(day) + month = parseInt(month) + year = parseInt(year) + + moment({year: year, month:month, day:day}).format('dddd D') + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + $scope.groupedAvailabilities = groupAvailabilities($scope.trainings) + + + + ## + # Group the trainings availabilites by trainings and by dates and return the resulting tree + # @param trainings {Array} $scope.trainings is expected here + # @returns {Object} Tree constructed as /training_name/year/month/day/[availabilities] + ## + groupAvailabilities = (trainings) -> + tree = {} + for training in trainings + tree[training.name] = {} + tree[training.name].training = training + for availability in training.availabilities + start = moment(availability.start_at) + + # init the tree structure + if typeof tree[training.name][start.year()] == 'undefined' + tree[training.name][start.year()] = {} + if typeof tree[training.name][start.year()][start.month()] == 'undefined' + tree[training.name][start.year()][start.month()] = {} + if typeof tree[training.name][start.year()][start.month()][start.date()] == 'undefined' + tree[training.name][start.year()][start.month()][start.date()] = [] + + # add the availability at its right place + tree[training.name][start.year()][start.month()][start.date()].push( availability ) + tree + + + + # init the controller (call at the end !) + initialize() +] diff --git a/app/assets/javascripts/controllers/application.coffee.erb b/app/assets/javascripts/controllers/application.coffee.erb index 49cefa6db..604310e0c 100644 --- a/app/assets/javascripts/controllers/application.coffee.erb +++ b/app/assets/javascripts/controllers/application.coffee.erb @@ -1,6 +1,7 @@ 'use strict' -Application.Controllers.controller 'ApplicationController', ["$rootScope", "$scope", "Session", "AuthService", "Auth", "$modal", "$state", 'growl', 'Notification', '$interval', ($rootScope, $scope, Session, AuthService, Auth, $modal, $state, growl, Notification, $interval) -> +Application.Controllers.controller 'ApplicationController', ["$rootScope", "$scope", "$window", "Session", "AuthService", "Auth", "$uibModal", "$state", 'growl', 'Notification', '$interval', "Setting", '$locale', '_t' +, ($rootScope, $scope, $window, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, $locale, _t) -> @@ -18,14 +19,14 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco # @param user {Object} Rails/Devise user ## $scope.setCurrentUser = (user) -> - $scope.currentUser = user + $rootScope.currentUser = user Session.create(user); getNotifications() ## # Login callback - # @param e {Object} jQuery event + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- # @param callback {function} ## $scope.login = (e, callback) -> @@ -36,14 +37,14 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco ## # Logout callback - # @param e {Object} jQuery event + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- ## $scope.logout = (e) -> e.preventDefault() Auth.logout().then (oldUser) -> # console.log(oldUser.name + " you're signed out now."); Session.destroy() - $scope.currentUser = null + $rootScope.currentUser = null $rootScope.toCheckNotifications = false $scope.notifications = [] $state.go('app.public.home') @@ -54,21 +55,21 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco ## # Open the modal window allowing the user to create an account. - # @param e {Object} jQuery event + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- ## $scope.signup = (e) -> e.preventDefault() if e - $modal.open + $uibModal.open templateUrl: '<%= asset_path "shared/signupModal.html" %>' size: 'md' - controller: ['$scope', '$modalInstance', 'Group', ($scope, $modalInstance, Group) -> + controller: ['$scope', '$uibModalInstance', 'Group', 'CustomAsset', ($scope, $uibModalInstance, Group, CustomAsset) -> # default parameters for the date picker in the account creation modal $scope.datePicker = - format: 'dd/MM/yyyy' + format: $locale.DATETIME_FORMATS.shortDate opened: false options: - startingDay: 1 + startingDay: Fablab.weekStartingDay # callback to open the date picker (account creation modal) $scope.openDatePicker = ($event) -> @@ -80,6 +81,10 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco Group.query (groups) -> $scope.groups = groups + # retrieve the CGU + CustomAsset.get {name: 'cgu-file'}, (cgu) -> + $scope.cgu = cgu.custom_asset + # default user's parameters $scope.user = is_allow_contact: true @@ -95,7 +100,7 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco $scope.alerts = [] Auth.register($scope.user).then (user) -> # creation successful - $modalInstance.close(user) + $uibModalInstance.close(user) , (error) -> # creation failed... angular.forEach error.data.errors, (v, k) -> @@ -114,10 +119,10 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco # @param token {string} security token for password changing. The user should have recieved it by mail ## $scope.editPassword = (token) -> - $modal.open + $uibModal.open templateUrl: '<%= asset_path "shared/passwordEditModal.html" %>' size: 'md' - controller: ['$scope', '$modalInstance', '$http', ($scope, $modalInstance, $http) -> + controller: ['$scope', '$uibModalInstance', '$http', '_t', ($scope, $uibModalInstance, $http, _t) -> $scope.user = reset_password_token: token $scope.alerts = [] @@ -127,7 +132,7 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco $scope.changePassword = -> $scope.alerts = [] $http.put('/users/password.json', {user: $scope.user}).success (data) -> - $modalInstance.close() + $uibModalInstance.close() .error (data) -> angular.forEach data.errors, (v, k) -> angular.forEach v, (err) -> @@ -136,20 +141,20 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco type: 'danger' ] .result['finally'](null).then (user) -> - growl.addInfoMessage('Votre mot de passe a bien été modifié.') + growl.success(_t('your_password_was_successfully_changed')) Auth.login().then (user) -> $scope.setCurrentUser(user) , (error) -> # Authentication failed... - + ## # Compact/Expend the width of the left navigation bar - # @param e {Object} jQuery event object + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- ## $scope.toggleNavSize = (event) -> if typeof event == 'undefined' - console.error '[applicationController::toggleNavSize] Missing event parameter' + console.error '[ApplicationController::toggleNavSize] Missing event parameter' return toggler = $(event.target) @@ -184,14 +189,16 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco ### PRIVATE SCOPE ### - ## # Kind of constructor: these actions will be realized first when the controller is loaded ## initialize = -> + # try to retrieve any currently logged user Auth.login().then (user) -> $scope.setCurrentUser(user) + if user.need_completion + $state.transitionTo('app.logged.profileCompletion') , (error) -> # Authentication failed... $rootScope.toCheckNotifications = false @@ -205,11 +212,17 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco event.preventDefault() if AuthService.isAuthenticated() # user is not allowed - console.log('user is not allowed') + console.error('[ApplicationController::initialize] user is not allowed') else # user is not logged in openLoginModal(toState, toParams) + Setting.get { name: 'fablab_name' }, (data)-> + $scope.fablabName = data.setting.value + Setting.get { name: 'name_genre' }, (data)-> + $scope.nameGenre = data.setting.value + + # shorthands $scope.isAuthenticated = Auth.isAuthenticated; @@ -223,7 +236,7 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco ## getNotifications = -> $rootScope.toCheckNotifications = true - unless $rootScope.checkNotificationsIsInit or !$scope.currentUser + unless $rootScope.checkNotificationsIsInit or !$rootScope.currentUser $scope.notifications = Notification.query {is_read: false} $scope.$watch 'notifications', (newValue, oldValue) -> diff = [] @@ -239,7 +252,7 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco angular.forEach diff, (notification, key) -> - growl.addInfoMessage(notification.message.description) + growl.info(notification.message.description) , true @@ -257,35 +270,39 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco # Open the modal window allowing the user to log in. ## openLoginModal = (toState, toParams, callback) -> - $modal.open + <% active_provider = AuthProvider.active %> + <% if active_provider.providable_type != DatabaseProvider.name %> + $window.location.href = '<%=user_omniauth_authorize_path(AuthProvider.active.strategy_name.to_sym)%>' + <% else %> + $uibModal.open templateUrl: '<%= asset_path "shared/deviseModal.html" %>' size: 'sm' - controller: ['$scope', '$modalInstance', ($scope, $modalInstance) -> + controller: ['$scope', '$uibModalInstance', '_t', ($scope, $uibModalInstance, _t) -> user = $scope.user = {} $scope.login = () -> Auth.login(user).then (user) -> # Authentification succeeded ... - $modalInstance.close(user) + $uibModalInstance.close(user) if callback and typeof callback is "function" callback(user) , (error) -> # Authentication failed... $scope.alerts = [] $scope.alerts.push - msg: 'E-mail ou mot de passe incorrect.' + msg: _t('wrong_email_or_password') type: 'danger' # handle modal behaviors. The provided reason will be used to define the following actions $scope.dismiss = -> - $modalInstance.dismiss('cancel') + $uibModalInstance.dismiss('cancel') $scope.openSignup = (e) -> e.preventDefault() - $modalInstance.dismiss('signup') + $uibModalInstance.dismiss('signup') $scope.openResetPassword = (e) -> e.preventDefault() - $modalInstance.dismiss('resetPassword') + $uibModalInstance.dismiss('resetPassword') ] # what to do when the modal is closed @@ -303,25 +320,26 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco $scope.signup() else if reason is 'resetPassword' # open the 'reset password' modal - $modal.open + $uibModal.open templateUrl: '<%= asset_path "shared/passwordNewModal.html" %>' size: 'sm' - controller: ['$scope', '$modalInstance', '$http', ($scope, $modalInstance, $http) -> + controller: ['$scope', '$uibModalInstance', '$http', ($scope, $uibModalInstance, $http) -> $scope.user = {email: ''} $scope.sendReset = () -> $scope.alerts = [] $http.post('/users/password.json', {user: $scope.user}).success -> - $modalInstance.close() + $uibModalInstance.close() .error -> $scope.alerts.push - msg: "Votre adresse email n'existe pas." + msg: _t('your_email_address_is_unknown') type: 'danger' ] .result['finally'](null).then -> - growl.addInfoMessage('Vous allez recevoir sous quelques minutes un e-mail vous indiquant comment réinitialiser votre mot de passe.') + growl.info(_t('you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password')) # otherwise the user just closed the modal + <% end %> diff --git a/app/assets/javascripts/controllers/dashboard.coffee b/app/assets/javascripts/controllers/dashboard.coffee index e01bde51e..2e878fa46 100644 --- a/app/assets/javascripts/controllers/dashboard.coffee +++ b/app/assets/javascripts/controllers/dashboard.coffee @@ -1,43 +1,7 @@ 'use strict' -## -# Controller used on the private projects listing page (my dashboard/projects) -## -Application.Controllers.controller "dashboardProjectsController", ["$scope", 'Member', ($scope, Member) -> +Application.Controllers.controller "DashboardController", ["$scope", 'memberPromise', ($scope, memberPromise) -> -## Current user's profile - $scope.user = Member.get {id: $scope.currentUser.id} -] - - - -## -# Controller used on the personal trainings page (my dashboard/trainings) -## -Application.Controllers.controller "dashboardTrainingsController", ["$scope", 'Member', ($scope, Member) -> - -## Current user's profile - $scope.user = Member.get {id: $scope.currentUser.id} -] - - - -## -# Controller used on the private events page (my dashboard/events) -## -Application.Controllers.controller "dashboardEventsController", ["$scope", 'Member', ($scope, Member) -> - -## Current user's profile - $scope.user = Member.get {id: $scope.currentUser.id} -] - - - -## -# Controller used on the personal invoices listing page (my dashboard/invoices) -## -Application.Controllers.controller "dashboardInvoicesController", ["$scope", 'Member', ($scope, Member) -> - -## Current user's profile - $scope.user = Member.get {id: $scope.currentUser.id} + ## Current user's profile + $scope.user = memberPromise ] diff --git a/app/assets/javascripts/controllers/events.coffee.erb b/app/assets/javascripts/controllers/events.coffee.erb index 21eab3d96..0ef4e2939 100644 --- a/app/assets/javascripts/controllers/events.coffee.erb +++ b/app/assets/javascripts/controllers/events.coffee.erb @@ -1,71 +1,72 @@ 'use strict' -Application.Controllers.controller "eventsController", ["$scope", "$state", 'Event', ($scope, $state, Event) -> +Application.Controllers.controller "EventsController", ["$scope", "$state", 'Event', ($scope, $state, Event) -> - ### PRIVATE STATIC CONSTANTS ### + ### PRIVATE STATIC CONSTANTS ### - # Number of events added to the page when the user clicks on 'load next events' - EVENTS_PER_PAGE = 12 + # Number of events added to the page when the user clicks on 'load next events' + EVENTS_PER_PAGE = 12 - ### PUBLIC SCOPE ### + ### PUBLIC SCOPE ### - ## The events displayed on the page - $scope.events = [] + ## The events displayed on the page + $scope.events = [] - ## By default, the pagination mode is activated to limit the page size - $scope.paginateActive = true + ## By default, the pagination mode is activated to limit the page size + $scope.paginateActive = true - ## The currently displayed page number - $scope.page = 1 + ## The currently displayed page number + $scope.page = 1 - ## - # Adds EVENTS_PER_PAGE events to the bottom of the page, grouped by month - ## - $scope.loadMoreEvents = -> - Event.query {page: $scope.page}, (data) -> - $scope.events = $scope.events.concat data - if data.length > 0 - $scope.paginateActive = false if ($scope.page-2)*EVENTS_PER_PAGE+data.length >= data[0].nb_total_events + ## + # Adds EVENTS_PER_PAGE events to the bottom of the page, grouped by month + ## + $scope.loadMoreEvents = -> + Event.query {page: $scope.page}, (data) -> + $scope.events = $scope.events.concat data + if data.length > 0 + $scope.paginateActive = false if ($scope.page-2)*EVENTS_PER_PAGE+data.length >= data[0].nb_total_events - $scope.eventsGroupByMonth = _.groupBy($scope.events, (obj) -> - _.map ['month', 'year'], (key, value) -> obj[key] - ) - $scope.monthOrder = _.sortBy _.keys($scope.eventsGroupByMonth), (k)-> - monthYearArray = k.split(',') - date = new Date() - date.setMonth(monthYearArray[0]) - date.setYear(monthYearArray[1]) - return -date.getTime() - else - $scope.paginateActive = false - $scope.page += 1 + $scope.eventsGroupByMonth = _.groupBy($scope.events, (obj) -> + _.map ['month', 'year'], (key, value) -> obj[key] + ) + $scope.monthOrder = _.sortBy _.keys($scope.eventsGroupByMonth), (k)-> + monthYearArray = k.split(',') + date = new Date() + date.setMonth(monthYearArray[0]) + date.setYear(monthYearArray[1]) + return -date.getTime() + else + $scope.paginateActive = false + $scope.page += 1 - ## - # Callback to redirect the user to the specified event page - # @param event {{id:number}} - ## - $scope.showEvent = (event) -> - $state.go('app.public.events_show', {id: event.id}) + ## + # Callback to redirect the user to the specified event page + # @param event {{id:number}} + ## + $scope.showEvent = (event) -> + $state.go('app.public.events_show', {id: event.id}) - ### PRIVATE SCOPE ### + ### PRIVATE SCOPE ### - ## - # Kind of constructor: these actions will be realized first when the controller is loaded - ## - initialize = -> - $scope.loadMoreEvents() + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + $scope.loadMoreEvents() - ## !!! MUST BE CALLED AT THE END of the controller - initialize() + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() ] @@ -74,47 +75,440 @@ Application.Controllers.controller "eventsController", ["$scope", "$state", 'Eve -Application.Controllers.controller "showEventController", ["$scope", "$state", "$stateParams", "Event", '$modal', 'Member', ($scope, $state, $stateParams, Event, $modal, Member) -> +Application.Controllers.controller "ShowEventController", ["$scope", "$state", "$stateParams", "Event", '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'eventPromise', 'reducedAmountAlert', 'growl', '_t' +($scope, $state, $stateParams, Event, $uibModal, Member, Reservation, Price, CustomAsset, eventPromise, reducedAmountAlert, growl, _t) -> - ### PUBLIC SCOPE ### + ### PUBLIC SCOPE ### + $scope.reducedAmountAlert = reducedAmountAlert.setting.value - ## current event details - $scope.event = {} + ## reservations for the currently shown event + $scope.reservations = [] + + ## user to deal with + $scope.ctrl = + member: {} + + ## parameters for a new reservation + $scope.reserve = + nbPlaces: [] + nbReducedPlaces: [] + nbReservePlaces: 0 + nbReserveReducedPlaces: 0 + toReserve: false + amountTotal : 0 - ## - # Callback to delete the provided event (admins only) - # @param event {$resource} angular's Event $resource - ## - $scope.deleteEvent = (event) -> - event.$delete -> - $state.go('app.public.events_list') + # get the details for the current event (event's id is recovered from the current URL) + $scope.event = eventPromise - ### PRIVATE SCOPE ### - - ## - # Kind of constructor: these actions will be realized first when the controller is loaded - ## - initialize = -> - - # get the details for the current event (event's id is recovered from the current URL) - Event.get {id: $stateParams.id} - , (data) -> - $scope.event = data - if !$scope.event.reduced_amount - $scope.event.reduced_amount = 0 - return - , -> - $state.go('app.public.events_list') + ## + # Callback to delete the provided event (admins only) + # @param event {$resource} angular's Event $resource + ## + $scope.deleteEvent = (event) -> + event.$delete -> + $state.go('app.public.events_list') - ## !!! MUST BE CALLED AT THE END of the controller - initialize() + ## + # Callback to call when the number of places change in the current booking + ## + $scope.changeNbPlaces = -> + reste = $scope.event.nb_free_places - $scope.reserve.nbReservePlaces + $scope.reserve.nbReducedPlaces = [0..reste] + $scope.computeEventAmount() + + + + ## + # Callback to call when the number of discounted places change in the current booking + ## + $scope.changeNbReducedPlaces = -> + reste = $scope.event.nb_free_places - $scope.reserve.nbReserveReducedPlaces + $scope.reserve.nbPlaces = [0..reste] + $scope.computeEventAmount() + + + + ## + # Callback to reset the current reservation parameters + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.cancelReserve = (e)-> + e.preventDefault() + resetEventReserve() + + + + ## + # Callback to allow the user to set the details for his reservation + ## + $scope.reserveEvent = -> + if $scope.event.nb_total_places > 0 + $scope.reserveSuccess = false + if !$scope.isAuthenticated() + $scope.login null, (user)-> + $scope.reserve.toReserve = !$scope.reserve.toReserve + if user.role isnt 'admin' + $scope.ctrl.member = user + else + Member.query (members) -> + $scope.members = members + else + $scope.reserve.toReserve = !$scope.reserve.toReserve + + + + ## + # Callback to deal with the reservations of the user selected in the dropdown list instead of the current user's + # reservations. (admins only) + ## + $scope.updateMember = -> + resetEventReserve() + $scope.reserveSuccess = false + if $scope.ctrl.member + getReservations($scope.event.id, 'Event', $scope.ctrl.member.id) + + + + ## + # Callback to trigger the payment process of the current reservation + ## + $scope.payEvent = -> + + # first, we check that a user was selected + if Object.keys($scope.ctrl.member).length > 0 + reservation = mkReservation($scope.ctrl.member, $scope.reserve, $scope.event) + + if $scope.currentUser.role isnt 'admin' and $scope.reserve.amountTotal > 0 + payByStripe(reservation) + else + if $scope.currentUser.role is 'admin' or $scope.reserve.amountTotal is 0 + payOnSite(reservation) + else + # otherwise we alert, this error musn't occur when the current user is not admin + growl.error(_t('please_select_a_member_first')) + + + + ## + # Callback to validate the booking of a free event + ## + $scope.validReserveEvent = -> + reservation = + user_id: $scope.ctrl.member.id + reservable_id: $scope.event.id + reservable_type: 'Event' + slots_attributes: [] + nb_reserve_places: $scope.reserve.nbReservePlaces + nb_reserve_reduced_places: $scope.reserve.nbReserveReducedPlaces + reservation.slots_attributes.push + start_at: $scope.event.start_date + end_at: $scope.event.end_date + availability_id: $scope.event.availability.id + $scope.attempting = true + Reservation.save reservation: reservation, (reservation) -> + afterPayment(reservation) + $scope.attempting = false + , (response)-> + $scope.alerts = [] + $scope.alerts.push + msg: response.data.card[0] + type: 'danger' + $scope.attempting = false + + + + ## + # Callback to alter an already booked reservation date. A modal window will be opened to allow the user to choose + # a new date for his reservation (if any available) + # @param reservation {{id:number, reservable_id:number, nb_reserve_places:number, nb_reserve_reduced_places:number}} + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.modifyReservation = (reservation, e)-> + e.preventDefault() + e.stopPropagation() + + index = $scope.reservations.indexOf(reservation) + $uibModal.open + templateUrl: '<%= asset_path "events/modify_event_reservation_modal.html" %>' + resolve: + event: -> $scope.event + reservation: -> reservation + controller: ['$scope', '$uibModalInstance', 'event', 'reservation', 'Reservation', ($scope, $uibModalInstance, event, reservation, Reservation) -> + # we copy the controller's resolved parameters into the scope + $scope.event = event + $scope.reservation = angular.copy reservation + + # set the reservable_id to the first available event + for e in event.recurrence_events + if e.nb_free_places > (reservation.nb_reserve_places + reservation.nb_reserve_reduced_places) + $scope.reservation.reservable_id = e.id + break + + # Callback to validate the new reservation's date + $scope.ok = -> + eventToPlace = null + angular.forEach event.recurrence_events, (e)-> + if e.id is parseInt($scope.reservation.reservable_id, 10) + eventToPlace = e + $scope.reservation.slots[0].start_at = eventToPlace.start_date + $scope.reservation.slots[0].end_at = eventToPlace.end_date + $scope.reservation.slots[0].availability_id = eventToPlace.availability_id + $scope.reservation.slots_attributes = $scope.reservation.slots + $scope.attempting = true + Reservation.update {id: reservation.id}, {reservation: $scope.reservation}, (reservation) -> + $uibModalInstance.close(reservation) + $scope.attempting = true + , (response)-> + $scope.alerts = [] + angular.forEach response, (v, k)-> + angular.forEach v, (err)-> + $scope.alerts.push({msg: k+': '+err, type: 'danger'}) + $scope.attempting = false + + # Callback to cancel the modification + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + ] + .result['finally'](null).then (reservation)-> + $scope.reservations.splice(index, 1) + $scope.event.nb_free_places = $scope.event.nb_free_places + reservation.nb_reserve_places + reservation.nb_reserve_reduced_places + angular.forEach $scope.event.recurrence_events, (e)-> + if e.id is parseInt(reservation.reservable_id, 10) + e.nb_free_places = e.nb_free_places - reservation.nb_reserve_places - reservation.nb_reserve_reduced_places + + + + ## + # Checks if the provided reservation is able to be modified + # @param reservation {{nb_reserve_places:number, nb_reserve_reduced_places:number}} + ## + $scope.reservationCanModify = (reservation)-> + isAble = false + angular.forEach $scope.event.recurrence_events, (e)-> + isAble = true if e.nb_free_places > (reservation.nb_reserve_places + reservation.nb_reserve_reduced_places) + isAble + + + + ## + # Compute the total amount for the current reservation according to the previously set parameters + # and assign the result in $scope.reserve.amountTotal + ## + $scope.computeEventAmount = -> + # first we check that a user was selected + if Object.keys($scope.ctrl.member).length > 0 + r = mkReservation($scope.ctrl.member, $scope.reserve, $scope.event) + Price.compute {reservation: r}, (res) -> + $scope.reserve.amountTotal = res.price + else + $scope.reserve.amountTotal = null + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + # gather the current user or the list of users if the current user is an admin + if $scope.currentUser + if $scope.currentUser.role isnt 'admin' + $scope.ctrl.member = $scope.currentUser + else + Member.query (members) -> + $scope.members = members + + # check that the event's reduced rate is initialized + if !$scope.event.reduced_amount + $scope.event.reduced_amount = 0 + + # initialize the "reserve" object with the event's data + $scope.reserve.nbPlaces = [0..$scope.event.nb_free_places] + $scope.reserve.nbReducedPlaces = [0..$scope.event.nb_free_places] + + # if non-admin, get the current user's reservations into $scope.reservations + if $scope.currentUser + getReservations($scope.event.id, 'Event', $scope.currentUser.id) + + + + ## + # Retrieve the reservations for the couple event / user + # @param reservable_id {number} the current event id + # @param reservable_type {string} 'Event' + # @param user_id {number} the user's id (current or managed) + ## + getReservations = (reservable_id, reservable_type, user_id)-> + Reservation.query(reservable_id: reservable_id, reservable_type: reservable_type, user_id: user_id).$promise.then (reservations)-> + $scope.reservations = reservations + + + + ## + # Create an hash map implementing the Reservation specs + # @param member {Object} User as retreived from the API: current user / selected user if current is admin + # @param reserve {Object} Reservation parameters (places...) + # @param event {Object} Current event (Atelier/Stage) + # @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array, nb_reserve_places:Number, nb_reserve_reduced_places:Number}} + ## + mkReservation = (member, reserve, event) -> + reservation = + user_id: member.id + reservable_id: event.id + reservable_type: 'Event' + slots_attributes: [] + nb_reserve_places: reserve.nbReservePlaces + nb_reserve_reduced_places: reserve.nbReserveReducedPlaces + + reservation.slots_attributes.push + start_at: event.start_date + end_at: event.end_date + availability_id: event.availability.id + offered: event.offered || false + + reservation + + + + ## + # Set the current reservation to the default values. This implies to reservation form to be hidden. + ## + resetEventReserve = -> + if $scope.event + $scope.reserve = + nbPlaces: [0..$scope.event.nb_free_places] + nbReducedPlaces: [0..$scope.event.nb_free_places] + nbReservePlaces: 0 + nbReserveReducedPlaces: 0 + toReserve: false + amountTotal : 0 + $scope.event.offered = false + + + + ## + # Open a modal window which trigger the stripe payment process + # @param reservation {Object} to book + ## + payByStripe = (reservation) -> + $uibModal.open + templateUrl: '<%= asset_path "stripe/payment_modal.html" %>' + size: 'md' + resolve: + reservation: -> + reservation + price: -> + Price.compute({reservation: reservation}).$promise + cgv: -> + CustomAsset.get({name: 'cgv-file'}).$promise + objectToPay: -> + eventToReserve: $scope.event + reserve: $scope.reserve + member: $scope.ctrl.member + controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'growl', ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, growl) -> + # Price + $scope.amount = price.price + + # CGV + $scope.cgv = cgv.custom_asset + + # Reservation + $scope.reservation = reservation + + # Callback for the stripe payment authorization + $scope.payment = (status, response) -> + if response.error + growl.error(response.error.message) + else + $scope.attempting = true + $scope.reservation.card_token = response.id + Reservation.save reservation: $scope.reservation, (reservation) -> + $uibModalInstance.close(reservation) + , (response)-> + $scope.alerts = [] + $scope.alerts.push + msg: response.data.card[0] + type: 'danger' + $scope.attempting = false + ] + .result['finally'](null).then (reservation)-> + afterPayment(reservation) + + + + ## + # Open a modal window which trigger the local payment process + # @param reservation {Object} to book + ## + payOnSite = (reservation) -> + $uibModal.open + templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>' + size: 'sm' + resolve: + reservation: -> + reservation + price: -> + Price.compute({reservation: reservation}).$promise + controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation) -> + # Price + $scope.amount = price.price + + # Reservation + $scope.reservation = reservation + + # Button label + if $scope.amount > 0 + $scope.validButtonName = _t('confirm_(payment_on_site)') + else + $scope.validButtonName = _t('confirm') + + # Callback to validate the payment + $scope.ok = -> + $scope.attempting = true + Reservation.save reservation: $scope.reservation, (reservation) -> + $uibModalInstance.close(reservation) + $scope.attempting = true + , (response)-> + $scope.alerts = [] + angular.forEach response, (v, k)-> + angular.forEach v, (err)-> + $scope.alerts.push + msg: k+': '+err + type: 'danger' + $scope.attempting = false + + # Callback to cancel the payment + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + ] + .result['finally'](null).then (reservation)-> + afterPayment(reservation) + + + + ## + # What to do after the payment was successful + # @param resveration {Object} booked reservation + ## + afterPayment = (reservation)-> + $scope.event.nb_free_places = $scope.event.nb_free_places - reservation.nb_reserve_places - reservation.nb_reserve_reduced_places + resetEventReserve() + $scope.reserveSuccess = true + $scope.reservations.push reservation + if $scope.currentUser.role == 'admin' + $scope.ctrl.member = null + + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() ] - diff --git a/app/assets/javascripts/controllers/home.coffee b/app/assets/javascripts/controllers/home.coffee index 5215748e4..a3a6e2085 100644 --- a/app/assets/javascripts/controllers/home.coffee +++ b/app/assets/javascripts/controllers/home.coffee @@ -1,35 +1,34 @@ 'use strict' -Application.Controllers.controller "homeController", ['$scope', '$stateParams', 'Member', 'Twitter', 'Project', 'Event', ($scope, $stateParams, Member, Twitter, Project, Event) -> - - - - ### PRIVATE STATIC CONSTANTS ### - - # The 4 last users will be displayed on the home page - LAST_MEMBERS_LIMIT = 4 - - # Only the last tweet is shown - LAST_TWEETS_LIMIT = 1 - - # The 3 closest events are shown - LAST_EVENTS_LIMIT = 3 - - +Application.Controllers.controller "HomeController", ['$scope', '$stateParams', 'Twitter', 'lastMembersPromise', 'lastProjectsPromise', 'upcomingEventsPromise', 'homeBlogpostPromise', 'twitterNamePromise', ($scope, $stateParams, Twitter, lastMembersPromise, lastProjectsPromise, upcomingEventsPromise, homeBlogpostPromise, twitterNamePromise)-> ### PUBLIC SCOPE ### ## The last registered members who confirmed their addresses - $scope.last_members = [] + $scope.lastMembers = lastMembersPromise ## The last tweets from the Fablab official twitter account - $scope.last_tweets = [] + $scope.lastTweets = [] ## The last projects published/documented on the plateform - $scope.last_projects = [] + $scope.lastProjects = lastProjectsPromise ## The closest upcoming events - $scope.upcoming_events = [] + $scope.upcomingEvents = upcomingEventsPromise + + ## The admin blogpost + $scope.homeBlogpost = homeBlogpostPromise.setting.value + + ## Twitter username + $scope.twitterName = twitterNamePromise.setting.value + + ## + # Test if the provided event run on a single day or not + # @param event {Object} single event from the $scope.upcomingEvents array + # @returns {boolean} false if the event runs on more that 1 day + ## + $scope.isOneDayEvent = (event) -> + moment(event.start_date).isSame(event.end_date, 'day') @@ -39,20 +38,15 @@ Application.Controllers.controller "homeController", ['$scope', '$stateParams', # Kind of constructor: these actions will be realized first when the controller is loaded ## initialize = -> - # display the reset password dialog if the parameter was provided + # we retrieve tweets from here instead of ui-router's promise because, if adblock stop the API request, + # this prevent the whole home page to be blocked + $scope.lastTweets = Twitter.query(limit: 1) + + # if we recieve a token to reset the password as GET parameter, trigger the + # changePassword modal from the parent controller if $stateParams.reset_password_token $scope.$parent.editPassword($stateParams.reset_password_token) - # initialize the homepage data - Member.lastSubscribed {limit: LAST_MEMBERS_LIMIT}, (members) -> - $scope.last_members = members - Twitter.query {limit: LAST_TWEETS_LIMIT}, (tweets) -> - $scope.last_tweets = tweets - Project.lastPublished (projects) -> - $scope.last_projects = projects - Event.upcoming {limit: LAST_EVENTS_LIMIT}, (events) -> - $scope.upcoming_events = events - ## !!! MUST BE CALLED AT THE END of the controller diff --git a/app/assets/javascripts/controllers/machines.coffee.erb b/app/assets/javascripts/controllers/machines.coffee.erb index f5bfa0ec1..de4587a94 100644 --- a/app/assets/javascripts/controllers/machines.coffee.erb +++ b/app/assets/javascripts/controllers/machines.coffee.erb @@ -74,19 +74,96 @@ class MachinesController +## +# Manages the transition when a user clicks on the reservation button. +# According to the status of user currently logged into the system, redirect him to the reservation page, +# or display a modal window asking him to complete a training before he can book a machine reservation. +# @param machine {{id:number}} An object containg the id of the machine to book, +# the object will be completed before the fonction returns. +# @param e {Object} see https://docs.angularjs.org/guide/expression#-event- +## +_reserveMachine = (machine, e) -> + _this = this + e.preventDefault() + e.stopPropagation() + + # retrieve the full machine object + machine = _this.Machine.get {id: machine.id}, -> + + # if the currently logged'in user has completed the training for this machine, or this machine does not require + # a prior training, just redirect him to the machine's booking page + if machine.current_user_is_training or machine.trainings.length == 0 + _this.$state.go('app.logged.machines_reserve', {id: machine.id}) + else + # otherwise, if a user is authenticated ... + if _this.$scope.isAuthenticated() + # ... and have booked a training for this machine, tell him that he must wait for an admin to validate + # the training before he can book the reservation + if machine.current_user_training_reservation + _this.$uibModal.open + templateUrl: '<%= asset_path "machines/training_reservation_modal.html" %>' + controller: ['$scope', '$uibModalInstance', '$state', ($scope, $uibModalInstance, $state) -> + $scope.machine = machine + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + ] + # ... but does not have booked the training, tell him to register for a training session first + else + _this.$uibModal.open + templateUrl: '<%= asset_path "machines/request_training_modal.html" %>' + controller: ['$scope', '$uibModalInstance', '$state', ($scope, $uibModalInstance, $state) -> + $scope.machine = machine + $scope.member = _this.$scope.currentUser + + # transform the name of the trainings associated with the machine to integrate them in a sentence + $scope.humanizeTrainings = -> + text = '' + angular.forEach $scope.machine.trainings, (training) -> + if text.length > 0 + text += _this._t('_or_the_') + text += training.name.substr(0,1).toLowerCase() + training.name.substr(1) + text + + # modal is close with validation + $scope.ok = -> + $state.go('app.logged.trainings_reserve') + $uibModalInstance.close(machine) + + # modal is closed with escaping + $scope.cancel = (e)-> + e.preventDefault() + $uibModalInstance.dismiss('cancel') + ] + # if the user is not logged, open the login modal window + else + _this.$scope.login() + + + + ## # Controller used in the public listing page, allowing everyone to see the list of machines ## -Application.Controllers.controller "machinesController", ["$scope", "$state", 'Machine', '$modal', ($scope, $state, Machine, $modal) -> +Application.Controllers.controller "MachinesController", ["$scope", "$state", '_t', 'Machine', '$uibModal', 'machinesPromise', ($scope, $state, _t, Machine, $uibModal, machinesPromise) -> - ## Retrieve the list of machines - $scope.machines = Machine.query() +## Retrieve the list of machines + $scope.machines = machinesPromise ## # Redirect the user to the machine details page ## $scope.showMachine = (machine) -> $state.go('app.public.machines_show', {id: machine.slug}) + + ## + # Callback to book a reservation for the current machine + ## + $scope.reserveMachine = _reserveMachine.bind + $scope: $scope + $state: $state + _t: _t + $uibModal: $uibModal + Machine: Machine ] @@ -94,7 +171,7 @@ Application.Controllers.controller "machinesController", ["$scope", "$state", 'M ## # Controller used in the machine creation page (admin) ## -Application.Controllers.controller "newMachineController", ["$scope", "$state", 'CSRF', ($scope, $state, CSRF) -> +Application.Controllers.controller "NewMachineController", ["$scope", "$state", 'CSRF',($scope, $state, CSRF) -> CSRF.setMetaTags() ## API URL where the form will be posted @@ -116,8 +193,11 @@ Application.Controllers.controller "newMachineController", ["$scope", "$state", ## # Controller used in the machine edition page (admin) ## -Application.Controllers.controller "editMachineController", ["$scope", "$state", '$stateParams', 'Machine', 'CSRF', ($scope, $state, $stateParams, Machine, CSRF) -> - CSRF.setMetaTags() +Application.Controllers.controller "EditMachineController", ["$scope", '$state', '$stateParams', 'machinePromise', 'CSRF', ($scope, $state, $stateParams, machinePromise, CSRF) -> + + + + ### PUBLIC SCOPE ### ## API URL where the form will be posted $scope.actionUrl = "/api/machines/" + $stateParams.id @@ -126,14 +206,24 @@ Application.Controllers.controller "editMachineController", ["$scope", "$state", $scope.method = "put" ## Retrieve the details for the machine id in the URL, if an error occurs redirect the user to the machines list - $scope.machine = Machine.get {id: $stateParams.id} - , -> - return - , -> - $state.go('app.public.machines_list') + $scope.machine = machinePromise - ## Using the MachinesController - new MachinesController($scope, $state) + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + CSRF.setMetaTags() + + ## Using the MachinesController + new MachinesController($scope, $state) + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() ] @@ -141,14 +231,11 @@ Application.Controllers.controller "editMachineController", ["$scope", "$state", ## # Controller used in the machine details page (public) ## -Application.Controllers.controller "showMachineController", ['$scope', '$state', '$modal', '$stateParams', 'Machine', ($scope, $state, $modal, $stateParams, Machine) -> +Application.Controllers.controller "ShowMachineController", ['$scope', '$state', '$uibModal', '$stateParams', '_t', 'Machine', 'growl', 'machinePromise' +, ($scope, $state, $uibModal, $stateParams, _t, Machine, growl, machinePromise) -> - ## Retrieve the details for the machine id in the URL, if an error occurs redirect the user to the machines list - $scope.machine = Machine.get {id: $stateParams.id} - , -> - return - , -> - $state.go('app.public.machines_list') +## Retrieve the details for the machine id in the URL, if an error occurs redirect the user to the machines list + $scope.machine = machinePromise ## # Callback to delete the current machine (admins only) @@ -156,9 +243,730 @@ Application.Controllers.controller "showMachineController", ['$scope', '$state', $scope.delete = (machine) -> # check the permissions if $scope.currentUser.role isnt 'admin' - console.error 'Unauthorized operation' + console.error _t('unauthorized_operation') else # delete the machine then redirect to the machines listing machine.$delete -> $state.go('app.public.machines_list') + , (error)-> + growl.warning(_t('the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users')) + ## + # Callback to book a reservation for the current machine + ## + $scope.reserveMachine = _reserveMachine.bind + $scope: $scope + $state: $state + _t: _t + $uibModal: $uibModal + Machine: Machine +] + + + +## +# Controller used in the machine reservation page (for logged users who have completed the training and admins). +# This controller workflow is pretty similar to the trainings reservation controller. +## + +Application.Controllers.controller "ReserveMachineController", ["$scope", "$state", '$stateParams', "$uibModal", '_t', "moment", 'Machine', 'Auth', 'dialogs', '$timeout', 'Price', 'Member', 'Availability', 'Slot', 'Setting', 'CustomAsset', 'plansPromise', 'groupsPromise', 'growl', 'settingsPromise', +($scope, $state, $stateParams, $uibModal, _t, moment, Machine, Auth, dialogs, $timeout, Price, Member, Availability, Slot, Setting, CustomAsset, plansPromise, groupsPromise, growl, settingsPromise) -> + + + + ### PRIVATE STATIC CONSTANTS ### + + # The calendar is divided in slots of 60 minutes + BASE_SLOT = '01:00:00' + + # The calendar will be initialized positioned under 9:00 AM + DEFAULT_CALENDAR_POSITION = '09:00:00' + + # The user is unable to modify his already booked reservation 1 day before it occurs + PREVENT_BOOKING_MODIFICATION_DELAY = 1 + + # Slot already booked by the current user + FREE_SLOT_BORDER_COLOR = '#e4cd78' + + # Slot already booked by another user + UNAVAILABLE_SLOT_BORDER_COLOR = '#1d98ec' + + # Slot free to be booked + BOOKED_SLOT_BORDER_COLOR = '#b2e774' + + + + ### PUBLIC SCOPE ### + + ## after fullCalendar loads, provides access to its methods through $scope.calendar.fullCalendar() + $scope.calendar = null + + ## bind the machine availabilities with full-Calendar events + $scope.eventSources = [] + + ## fullCalendar event. The last selected slot that the user want to book + $scope.slotToPlace = null + + ## fullCalendar event. An already booked slot that the user want to modify + $scope.slotToModify = null + + ## indicates the state of the current view : calendar or plans informations + $scope.plansAreShown = false + + ## will store the user's plan if he choosed to buy one + $scope.selectedPlan = null + + ## array of fullCalendar events. Slots where the user want to book + $scope.eventsReserved = [] + + ## total amount of the bill to pay + $scope.amountTotal = 0 + + ## is the user allowed to change the date of his booking + $scope.enableBookingMove = true + + ## how many hours before the reservation, the user is still allowed to change his booking + $scope.moveBookingDelay = 24 + + ## list of plans, classified by group + $scope.plansClassifiedByGroup = [] + for group in groupsPromise + groupObj = { id: group.id, name: group.name, plans: [] } + for plan in plansPromise + groupObj.plans.push(plan) if plan.group_id == group.id + $scope.plansClassifiedByGroup.push(groupObj) + + ## the user to deal with, ie. the current user for non-admins + $scope.ctrl = + member: {} + + ## fablab users list + $scope.members = [] + + ## current machine to reserve + $scope.machine = {} + + ## fullCalendar (v2) configuration + $scope.calendarConfig = + timezone: Fablab.timezone + lang: Fablab.fullcalendar_locale + header: + left: 'month agendaWeek' + center: 'title' + right: 'today prev,next' + firstDay: 1 # Week start on monday (France) + scrollTime: DEFAULT_CALENDAR_POSITION + slotDuration: BASE_SLOT + allDayDefault: false + minTime: '00:00:00' + maxTime: '24:00:00' + height: 'auto' + buttonIcons: + prev: 'left-single-arrow' + next: 'right-single-arrow' + timeFormat: + agenda:'H:mm' + month: 'H(:mm)' + axisFormat: 'H:mm' + + allDaySlot: false + defaultView: 'agendaWeek' + editable: false + eventClick: (event, jsEvent, view) -> + calendarEventClickCb(event, jsEvent, view) + eventRender: (event, element, view) -> + eventRenderCb(event, element) + + ## Global config: message to the end user concerning the subscriptions rules + $scope.subscriptionExplicationsAlert = settingsPromise.subscription_explications_alert + + ## Gloabl config: message to the end user concerning the machine bookings + $scope.machineExplicationsAlert = settingsPromise.machine_explications_alert + + ## Global config: is the user authorized to change his bookings slots? + $scope.enableBookingMove = (settingsPromise.booking_move_enable == "true") + + ## Global config: delay in hours before a booking while changing the booking slot is forbidden + $scope.moveBookingDelay = parseInt(settingsPromise.booking_move_delay) + + ## Global config: is the user authorized to cancel his bookings? + $scope.enableBookingCancel = (settingsPromise.booking_cancel_enable == "true") + + ## Global config: delay in hours before a booking while the cancellation is forbidden + $scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay) + + ## Global config: calendar window in the morning + $scope.calendarConfig.minTime = moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss')) + + ## Global config: calendar window in the evening + $scope.calendarConfig.maxTime = moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss')) + + + + ## + # Cancel the current booking modification, removing the previously booked slot from the selection + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.removeSlotToModify = (e) -> + e.preventDefault() + if $scope.slotToPlace + $scope.slotToPlace.backgroundColor = 'white' + $scope.slotToPlace.title = '' + $scope.slotToPlace = null + $scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available') + $scope.slotToModify.backgroundColor = 'white' + $scope.slotToModify = null + $scope.calendar.fullCalendar 'rerenderEvents' + + + + ## + # When modifying an already booked reservation, cancel the choice of the new slot + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.removeSlotToPlace = (e)-> + e.preventDefault() + $scope.slotToPlace.backgroundColor = 'white' + $scope.slotToPlace.title = '' + $scope.slotToPlace = null + $scope.calendar.fullCalendar 'rerenderEvents' + + + + ## + # When modifying an already booked reservation, confirm the modification. + ## + $scope.modifyMachineSlot = -> + Slot.update {id: $scope.slotToModify.id}, + slot: + start_at: $scope.slotToPlace.start + end_at: $scope.slotToPlace.end + availability_id: $scope.slotToPlace.availability_id + , -> # success + $scope.modifiedSlots = + newReservedSlot: $scope.slotToPlace + oldReservedSlot: $scope.slotToModify + $scope.slotToPlace.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available') + $scope.slotToPlace.backgroundColor = 'white' + $scope.slotToPlace.borderColor = $scope.slotToModify.borderColor + $scope.slotToPlace.id = $scope.slotToModify.id + $scope.slotToPlace.is_reserved = true + $scope.slotToPlace.can_modify = true + $scope.slotToPlace = null + + $scope.slotToModify.backgroundColor = 'white' + $scope.slotToModify.title = '' + $scope.slotToModify.borderColor = FREE_SLOT_BORDER_COLOR + $scope.slotToModify.id = null + $scope.slotToModify.is_reserved = false + $scope.slotToModify.can_modify = false + $scope.slotToModify = null + + $scope.calendar.fullCalendar 'rerenderEvents' + , (err) -> # failure + growl.error(_t('unable_to_change_the_reservation')) + console.error(err) + + + + ## + # Cancel the current booking modification, reseting the whole process + ## + $scope.cancelModifyMachineSlot = -> + $scope.slotToPlace.backgroundColor = 'white' + $scope.slotToPlace.title = '' + $scope.slotToPlace = null + $scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available') + $scope.slotToModify.backgroundColor = 'white' + $scope.slotToModify = null + + $scope.calendar.fullCalendar 'rerenderEvents' + + + + ## + # Callback to deal with the reservations of the user selected in the dropdown list instead of the current user's + # reservations. (admins only) + ## + $scope.updateMember = -> + $scope.paidMachineSlots = null + $scope.plansAreShown = false + $scope.selectedPlan = null + updateCartPrice() + + + + ## + # Add the provided slot to the shopping cart (state transition from free to 'about to be reserved') + # and increment the total amount of the cart if needed. + # @param machineSlot {Object} fullCalendar event object + ## + $scope.validMachineSlot = (machineSlot)-> + machineSlot.isValid = true + updateCartPrice() + + + + ## + # Remove the provided slot from the shopping cart (state transition from 'about to be reserved' to free) + # and decrement the total amount of the cart if needed. + # @param machineSlot {Object} fullCalendar event object + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.removeMachineSlot = (machineSlot, e)-> + e.preventDefault() if e + machineSlot.backgroundColor = 'white' + machineSlot.borderColor = FREE_SLOT_BORDER_COLOR + machineSlot.title = '' + machineSlot.isValid = false + + if machineSlot.machine.is_reduced_amount + angular.forEach $scope.ctrl.member.machine_credits, (credit)-> + if credit.machine_id = machineSlot.machine.id + credit.hours_used-- + machineSlot.machine.is_reduced_amount = false + + index = $scope.eventsReserved.indexOf(machineSlot) + $scope.eventsReserved.splice(index, 1) + if $scope.eventsReserved.length == 0 + if $scope.plansAreShown + $scope.selectedPlan = null + $scope.plansAreShown = false + updateCartPrice() + $timeout -> + $scope.calendar.fullCalendar 'refetchEvents' + $scope.calendar.fullCalendar 'rerenderEvents' + + + + ## + # Checks that every selected slots were added to the shopping cart. Ie. will return false if + # any checked slot was not validated by the user. + ## + $scope.machineSlotsValid = -> + isValid = true + angular.forEach $scope.eventsReserved, (m)-> + isValid = false if !m.isValid + isValid + + + + ## + # Changes the user current view from the plan subsription screen to the machine reservation agenda + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.doNotSubscribePlan = (e)-> + e.preventDefault() + $scope.plansAreShown = false + $scope.selectPlan($scope.selectedPlan) + + + + ## + # Validates the shopping chart and redirect the user to the payment step + ## + $scope.payMachine = -> + + # first, we check that a user was selected + if Object.keys($scope.ctrl.member).length > 0 + reservation = mkReservation($scope.ctrl.member, $scope.eventsReserved, $scope.selectedPlan) + + if $scope.currentUser.role isnt 'admin' and $scope.amountTotal > 0 + payByStripe(reservation) + else + if $scope.currentUser.role is 'admin' or $scope.amountTotal is 0 + payOnSite(reservation) + else + # otherwise we alert, this error musn't occur when the current user is not admin + growl.error(_t('please_select_a_member_first')) + + + ## + # Switch the user's view from the reservation agenda to the plan subscription + ## + $scope.showPlans = -> + $scope.plansAreShown = true + + + + ## + # Add the provided plan to the current shopping cart + # @param plan {Object} the plan to subscribe + ## + $scope.selectPlan = (plan) -> + if $scope.isAuthenticated() + angular.forEach $scope.eventsReserved, (machineSlot)-> + angular.forEach $scope.ctrl.member.machine_credits, (credit)-> + if credit.machine_id = machineSlot.machine.id + credit.hours_used = 0 + machineSlot.machine.is_reduced_amount = false + + if $scope.selectedPlan != plan + $scope.selectedPlan = plan + else + $scope.selectedPlan = null + updateCartPrice() + else + $scope.login null, -> + $scope.selectedPlan = plan + updateCartPrice() + + + + ## + # Checks if $scope.slotToModify and $scope.slotToPlace have tag incompatibilities + # @returns {boolean} true in case of incompatibility + ## + $scope.tagMissmatch = -> + for tag in $scope.slotToModify.tags + if tag.id not in $scope.slotToPlace.tag_ids + return true + false + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + Availability.machine {machineId: $stateParams.id}, (availabilities) -> + $scope.eventSources.push + events: availabilities + textColor: 'black' + + if $scope.currentUser.role isnt 'admin' + $scope.ctrl.member = $scope.currentUser + else + Member.query {requested_attributes:'[subscription,credits]'}, (members) -> + $scope.members = members + + $scope.machine = Machine.get {id: $stateParams.id} + , -> + return + , -> + $state.go('app.public.machines_list') + + + + ## + # Create an hash map implementing the Reservation specs + # @param member {Object} User as retreived from the API: current user / selected user if current is admin + # @param slots {Array} Array of fullCalendar events: slots selected on the calendar + # @param [plan] {Object} Plan as retrived from the API: plan to buy with the current reservation + # @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array, plan_id:Number|null}} + ## + mkReservation = (member, slots, plan = null) -> + reservation = + user_id: member.id + reservable_id: (slots[0].machine.id if slots.length > 0) + reservable_type: 'Machine' + slots_attributes: [] + plan_id: (plan.id if plan) + angular.forEach slots, (slot, key) -> + reservation.slots_attributes.push + start_at: slot.start + end_at: slot.end + availability_id: slot.availability_id + offered: slot.offered || false + + reservation + + + + ## + # Update the total price of the current selection/reservation + ## + updateCartPrice = -> + if Object.keys($scope.ctrl.member).length > 0 + r = mkReservation($scope.ctrl.member, $scope.eventsReserved, $scope.selectedPlan) + Price.compute {reservation: r}, (res) -> + $scope.amountTotal = res.price + setSlotsDetails(res.details) + else + # otherwise we alert, this error musn't occur when the current user is not admin + growl.warning(_t('please_select_a_member_first')) + $scope.amountTotal = null + + + setSlotsDetails = (details) -> + angular.forEach $scope.eventsReserved, (slot) -> + angular.forEach details.slots, (s) -> + if moment(s.start_at).isSame(slot.start) + slot.promo = s.promo + slot.price = s.price + + + ## + # Triggered when the user click on a reservation slot in the agenda. + # Defines the behavior to adopt depending on the slot status (already booked, free, ready to be reserved ...), + # the user's subscription (current or about to be took) and the time (the user cannot modify a booked reservation + # if it's too late). + ## + calendarEventClickCb = (event, jsEvent, view) -> + + if !event.is_reserved && !$scope.slotToModify + index = $scope.eventsReserved.indexOf(event) + if index == -1 + event.backgroundColor = FREE_SLOT_BORDER_COLOR + event.title = _t('i_reserve') + $scope.eventsReserved.push event + else + $scope.removeMachineSlot(event) + $scope.paidMachineSlots = null + $scope.selectedPlan = null + $scope.modifiedSlots = null + else if !event.is_reserved && $scope.slotToModify + if $scope.slotToPlace + $scope.slotToPlace.backgroundColor = 'white' + $scope.slotToPlace.title = '' + $scope.slotToPlace = event + event.backgroundColor = '#bbb' + event.title = _t('i_shift') + else if event.is_reserved and (slotCanBeModified(event) or slotCanBeCanceled(event)) and !$scope.slotToModify and $scope.eventsReserved.length == 0 + event.movable = slotCanBeModified(event) + event.cancelable = slotCanBeCanceled(event) + dialogs.confirm + templateUrl: '<%= asset_path "shared/confirm_modify_slot_modal.html" %>' + resolve: + object: -> event + , (type) -> + if type == 'move' + $scope.modifiedSlots = null + $scope.slotToModify = event + event.backgroundColor = '#eee' + event.title = _t('i_change') + $scope.calendar.fullCalendar 'rerenderEvents' + else if type == 'cancel' + dialogs.confirm + resolve: + object: -> + title: _t('confirmation_required') + msg: _t('do_you_really_want_to_cancel_this_reservation') + , -> # cancel confirmed + Slot.cancel {id: event.id}, -> # successfully canceled + growl.success _t('reservation_was_cancelled_successfully') + $scope.canceledSlot = event + $scope.canceledSlot.backgroundColor = 'white' + $scope.canceledSlot.title = '' + $scope.canceledSlot.borderColor = FREE_SLOT_BORDER_COLOR + $scope.canceledSlot.id = null + $scope.canceledSlot.is_reserved = false + $scope.canceledSlot.can_modify = false + $scope.canceledSlot = null + $scope.calendar.fullCalendar 'rerenderEvents' + , -> # error while canceling + growl.error _t('cancellation_failed') + , -> + $scope.paidMachineSlots = null + $scope.selectedPlan = null + $scope.modifiedSlots = null + + $scope.calendar.fullCalendar 'rerenderEvents' + + updateCartPrice() + + + + + ## + # Triggered when fullCalendar tries to graphicaly render an event block. + # Append the event tag into the block, just after the event title. + # @see http://fullcalendar.io/docs/event_rendering/eventRender/ + ## + eventRenderCb = (event, element) -> + if $scope.currentUser.role is 'admin' and event.tags.length > 0 + html = '' + for tag in event.tags + html += "#{tag.name}" + element.find('.fc-time').append(html) + + + + ## + # Open a modal window that allows the user to process a credit card payment for his current shopping cart. + ## + payByStripe = (reservation) -> + + $uibModal.open + templateUrl: '<%= asset_path "stripe/payment_modal.html" %>' + size: 'md' + resolve: + reservation: -> + reservation + price: -> + Price.compute({reservation: reservation}).$promise + cgv: -> + CustomAsset.get({name: 'cgv-file'}).$promise + controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation) -> + # Price + $scope.amount = price.price + + # CGV + $scope.cgv = cgv.custom_asset + + # Reservation + $scope.reservation = reservation + + ## + # Callback to process the payment with Stripe, triggered on button click + ## + $scope.payment = (status, response) -> + if response.error + growl.error(response.error.message) + else + $scope.attempting = true + $scope.reservation.card_token = response.id + Reservation.save reservation: $scope.reservation, (reservation) -> + $uibModalInstance.close(reservation) + , (response)-> + $scope.alerts = [] + $scope.alerts.push + msg: response.data.card[0] + type: 'danger' + $scope.attempting = false + ] + .result['finally'](null).then (reservation)-> + afterPayment(reservation) + + + + ## + # Open a modal window that allows the user to process a local payment for his current shopping cart (admin only). + ## + payOnSite = (reservation) -> + + $uibModal.open + templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>' + size: 'sm' + resolve: + reservation: -> + reservation + price: -> + Price.compute({reservation: reservation}).$promise + controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation) -> + + # Price + $scope.amount = price.price + + # Reservation + $scope.reservation = reservation + + # Button label + if $scope.amount > 0 + $scope.validButtonName = _t('confirm_(payment_on_site)') + else + $scope.validButtonName = _t('confirm') + + ## + # Callback to process the local payment, triggered on button click + ## + $scope.ok = -> + $scope.attempting = true + Reservation.save reservation: $scope.reservation, (reservation) -> + $uibModalInstance.close(reservation) + $scope.attempting = true + , (response)-> + $scope.alerts = [] + $scope.alerts.push({msg: _t('a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' }) + $scope.attempting = false + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + ] + .result['finally'](null).then (reservation)-> + afterPayment(reservation) + + + + ## + # Determines if the provided booked slot is able to be modified by the user. + # @param slot {Object} fullCalendar event object + ## + slotCanBeModified = (slot)-> + return true if $scope.currentUser.role is 'admin' + slotStart = moment(slot.start) + now = moment() + if slot.can_modify and $scope.enableBookingMove and slotStart.diff(now, "hours") >= $scope.moveBookingDelay + return true + else + return false + + + + ## + # Determines if the provided booked slot is able to be canceled by the user. + # @param slot {Object} fullCalendar event object + ## + slotCanBeCanceled = (slot) -> + return true if $scope.currentUser.role is 'admin' + slotStart = moment(slot.start) + now = moment() + if slot.can_modify and $scope.enableBookingCancel and slotStart.diff(now, "hours") >= $scope.cancelBookingDelay + return true + else + return false + + + ## + # Once the reservation is booked (payment process successfully completed), change the event style + # in fullCalendar, update the user's subscription and free-credits if needed + # @param reservation {Object} + ## + afterPayment = (reservation)-> + angular.forEach $scope.eventsReserved, (machineSlot, key) -> + machineSlot.is_reserved = true + machineSlot.can_modify = true + if $scope.currentUser.role isnt 'admin' + machineSlot.title = _t('i_ve_reserved') + machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR + updateMachineSlot(machineSlot, reservation, $scope.currentUser) + else + machineSlot.title = _t('not_available') + machineSlot.borderColor = UNAVAILABLE_SLOT_BORDER_COLOR + updateMachineSlot(machineSlot, reservation, $scope.ctrl.member) + machineSlot.backgroundColor = 'white' + $scope.paidMachineSlots = $scope.eventsReserved + + $scope.eventsReserved = [] + + if $scope.selectedPlan + $scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan) + Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan) + $scope.plansAreShown = false + + $scope.calendar.fullCalendar 'refetchEvents' + $scope.calendar.fullCalendar 'rerenderEvents' + + + + ## + # After payment, update the id of the newly reserved slot with the id returned by the server. + # This will allow the user to modify the reservation he just booked. The associated user will also be registered + # with the slot. + # @param slot {Object} + # @param reservation {Object} + # @param user {Object} user associated with the slot + ## + updateMachineSlot = (slot, reservation, user)-> + angular.forEach reservation.slots, (s)-> + if slot.start.isSame(s.start_at) + slot.id = s.id + slot.user = user + + + + ## + # Search for the requested plan in the provided array and return its price. + # @param plansArray {Array} full list of plans + # @param planId {Number} plan identifier + # @returns {Number|null} price of the given plan or null if not found + ## + findAmountByPlanId = (plansArray, planId)-> + for plan in plansArray + return plan.amount if plan.plan_id == planId + return null + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() ] diff --git a/app/assets/javascripts/controllers/main_nav.coffee.erb b/app/assets/javascripts/controllers/main_nav.coffee.erb index 0f05d63c9..96b1c5775 100644 --- a/app/assets/javascripts/controllers/main_nav.coffee.erb +++ b/app/assets/javascripts/controllers/main_nav.coffee.erb @@ -3,55 +3,97 @@ ## # Navigation controller. List the links availables in the left navigation pane and their icon. ## -Application.Controllers.controller "mainNavController", ["$scope", "$location", "$cookies", ($scope, $location, $cookies) -> +Application.Controllers.controller "MainNavController", ["$scope", "$location", "$cookies", ($scope, $location, $cookies) -> -## Common links (public application) + ## Common links (public application) $scope.navLinks = [ { state: 'app.public.home' - linkText: 'Accueil' + linkText: 'home' linkIcon: 'home' } { state: 'app.public.machines_list' - linkText: 'Liste des machines' - linkIcon: 'gears' + linkText: 'reserve_a_machine' + linkIcon: 'calendar' + } + { + state: 'app.logged.trainings_reserve' + linkText: 'trainings_registrations' + linkIcon: 'graduation-cap' } { state: 'app.public.events_list' - linkText: 'Liste des stages et ateliers' + linkText: 'courses_and_workshops_registrations' linkIcon: 'tags' } { state: 'app.public.projects_list' - linkText: 'Galerie de projets' + linkText: 'projects_gallery' linkIcon: 'th' } + ] - ## Admin links (backoffice application) + unless Fablab.withoutPlans + $scope.navLinks.push({ + state: 'app.public.plans' + linkText: 'subscriptions' + linkIcon: 'credit-card' + }) + + $scope.adminNavLinks = [ + { + state: 'app.admin.trainings' + linkText: 'trainings_monitoring' + linkIcon: 'graduation-cap' + } + { + state: 'app.admin.calendar' + linkText: 'manage_the_calendar' + linkIcon: 'calendar' + } { state: 'app.admin.members' - linkText: 'Suivi utilisateurs' + linkText: 'manage_the_users' linkIcon: 'users' } + { + state: 'app.admin.invoices' + linkText: 'manage_the_invoices' + linkIcon: 'file-pdf-o' + } + { + state: 'app.admin.pricing' + linkText: 'subscriptions_and_prices' + linkIcon: 'money' + } { state: 'app.admin.events' - linkText: 'Suivi stages et ateliers' + linkText: 'courses_and_workshops_monitoring' linkIcon: 'tags' } { state: 'app.public.machines_list' - linkText: 'Gérer les machines' + linkText: 'manage_the_machines' linkIcon: 'cogs' } { state: 'app.admin.project_elements' - linkText: 'Gérer les éléments Projets' + linkText: 'manage_the_projects_elements' linkIcon: 'tasks' } + { + state: 'app.admin.statistics' + linkText: 'statistics' + linkIcon: 'bar-chart-o' + } + { + state: 'app.admin.settings' + linkText: 'customization' + linkIcon: 'gear' + } ] - ] diff --git a/app/assets/javascripts/controllers/members.coffee b/app/assets/javascripts/controllers/members.coffee index 594996380..bef2e9bed 100644 --- a/app/assets/javascripts/controllers/members.coffee +++ b/app/assets/javascripts/controllers/members.coffee @@ -3,23 +3,11 @@ ## # Controller used in the members listing page ## -Application.Controllers.controller "membersController", ["$scope", "$state", 'Member', ($scope, $state, Member) -> +Application.Controllers.controller "MembersController", ["$scope", 'membersPromise', ($scope, membersPromise) -> ## members list - $scope.members = Member.query() + $scope.members = membersPromise - ## Merbers ordering/sorting. Default: not sorted - $scope.orderMember = null - - ## - # Change the members ordering criterion to the one provided - # @param orderBy {string} ordering criterion - ## - $scope.setOrderMember = (orderBy)-> - if $scope.orderMember == orderBy - $scope.orderMember = '-'+orderBy - else - $scope.orderMember = orderBy ] @@ -27,24 +15,71 @@ Application.Controllers.controller "membersController", ["$scope", "$state", 'Me ## # Controller used when editing the current user's profile ## -Application.Controllers.controller "editProfileController", ["$scope", "$state", "Member", "Auth", 'growl', 'dialogs', 'CSRF', ($scope, $state, Member, Auth, growl, dialogs, CSRF) -> - CSRF.setMetaTags() +Application.Controllers.controller "EditProfileController", ["$scope", "$rootScope", "$state", "$window", '$locale', "Member", "Auth", "Session", "activeProviderPromise", 'growl', 'dialogs', 'CSRF', 'memberPromise', 'groups', '_t' +, ($scope, $rootScope, $state, $window, $locale, Member, Auth, Session, activeProviderPromise, growl, dialogs, CSRF, memberPromise, groups, _t) -> + + + + ### PUBLIC SCOPE ### ## API URL where the form will be posted $scope.actionUrl = "/api/members/" + $scope.currentUser.id + ## list of groups + $scope.groups = groups + ## Form action on the above URL $scope.method = 'patch' ## Current user's profile - $scope.user = Member.get {id: $scope.currentUser.id} + $scope.user = memberPromise + + ## default : do not show the group changing form + $scope.group = + change: false + + ## group ID of the current/selected user + $scope.userGroup = memberPromise.group_id + + ## active authentication provider parameters + $scope.activeProvider = activeProviderPromise + + ## allow the user to change his password except if he connect from an SSO + $scope.preventPassword = false + + ## mapping of fields to disable + $scope.preventField = {} ## Angular-Bootstrap datepicker configuration for birthday $scope.datePicker = - format: 'dd/MM/yyyy' + format: $locale.DATETIME_FORMATS.shortDate opened: false # default: datePicker is not shown options: - startingDay: 1 # France: the week starts on monday + startingDay: Fablab.weekStartingDay + + + + ## + # Return the group object, identified by the ID set in $scope.userGroup + ## + $scope.getUserGroup = -> + for group in $scope.groups + if group.id == $scope.userGroup + return group + + + + ## + # Change the group of the current user to the one set in $scope.userGroup + ## + $scope.selectGroup = -> + Member.update {id: $scope.user.id}, {user: {group_id: $scope.userGroup}}, (user) -> + $scope.user = user + $scope.group.change = false + growl.success(_t('your_group_has_been_successfully_changed')) + , (err) -> + growl.error(_t('an_unexpected_error_prevented_your_group_from_being_changed')) + console.error(err) @@ -81,10 +116,32 @@ Application.Controllers.controller "editProfileController", ["$scope", "$state", Auth._currentUser.name = content.name $scope.currentUser = content Auth._currentUser = content + $rootScope.currentUser = content $state.go('app.public.home') + ## + # Ask for confirmation then delete the current user's account + # @param user {Object} the current user (to delete) + ## + $scope.deleteUser = (user)-> + dialogs.confirm + resolve: + object: -> + title: _t('confirmation_required') + msg: _t('do_you_really_want_to_delete_your_account')+' '+_t('all_data_relative_to_your_projects_will_be_lost') + , -> # cancel confirmed + Member.remove { id: user.id }, -> + Auth.logout().then -> + $state.go('app.public.home') + growl.success(_t('your_user_account_has_been_successfully_deleted_goodbye')) + , (error)-> + console.log(error) + growl.error(_t('an_error_occured_preventing_your_account_from_being_deleted')) + + + ## # For use with 'ng-class', returns the CSS class name for the uploads previews. # The preview may show a placeholder or the content of the file depending on the upload state. @@ -95,6 +152,52 @@ Application.Controllers.controller "editProfileController", ["$scope", "$state", 'fileinput-exists' else 'fileinput-new' + + + ## + # Check if the of the properties editable by the user are linked to the SSO + # @return {boolean} true if some editable fields are mapped with the SSO, false otherwise + ## + $scope.hasSsoFields = -> + # if check if keys > 1 because there's a minimum of 1 mapping (id <-> provider-uid) + # so the user may want to edit his profile on the SSO if at least 2 mappings exists + Object.keys($scope.preventField).length > 1 + + + ## + # Disconnect and re-connect the user to the SSO to force the synchronisation of the profile's data + ## + $scope.syncProfile = -> + Auth.logout().then (oldUser) -> + Session.destroy() + $rootScope.currentUser = null + $rootScope.toCheckNotifications = false + $scope.notifications = [] + $window.location.href = $scope.activeProvider.link_to_sso_connect + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + CSRF.setMetaTags() + + # init the birth date to JS object + $scope.user.profile.birthday = moment($scope.user.profile.birthday).toDate() + + if $scope.activeProvider.providable_type != 'DatabaseProvider' + $scope.preventPassword = true + # bind fields protection with sso fields + angular.forEach activeProviderPromise.mapping, (map) -> + $scope.preventField[map] = true + + + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() ] @@ -102,8 +205,8 @@ Application.Controllers.controller "editProfileController", ["$scope", "$state", ## # Controller used on the public user's profile page (seeing another user's profile) ## -Application.Controllers.controller "showProfileController", ["$scope", "$stateParams", 'Member', ($scope, $stateParams, Member) -> +Application.Controllers.controller "ShowProfileController", ["$scope", "$stateParams", 'Member', 'memberPromise', ($scope, $stateParams, Member, memberPromise) -> ## Selected user's profile (id from the current URL) - $scope.user = Member.get {id: $stateParams.id} + $scope.user = memberPromise ] diff --git a/app/assets/javascripts/controllers/notifications.coffee b/app/assets/javascripts/controllers/notifications.coffee index 05c7457f6..3426aa1d0 100644 --- a/app/assets/javascripts/controllers/notifications.coffee +++ b/app/assets/javascripts/controllers/notifications.coffee @@ -4,7 +4,7 @@ # Controller used in notifications page # inherits $scope.$parent.notifications (unread notifications) from ApplicationController ## -Application.Controllers.controller "notificationsController", ["$scope", 'Notification', ($scope, Notification) -> +Application.Controllers.controller "NotificationsController", ["$scope", 'Notification', ($scope, Notification) -> @@ -32,7 +32,7 @@ Application.Controllers.controller "notificationsController", ["$scope", 'Notifi # Mark the provided notification as read, updating its status on the server and moving it # to the already read notifications list. # @param notification {{id:number}} the notification to mark as read - # @param e {Object} jQuery event object + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- ## $scope.markAsRead = (notification, e) -> e.preventDefault() diff --git a/app/assets/javascripts/controllers/plans.coffee.erb b/app/assets/javascripts/controllers/plans.coffee.erb new file mode 100644 index 000000000..0e0d3102b --- /dev/null +++ b/app/assets/javascripts/controllers/plans.coffee.erb @@ -0,0 +1,232 @@ +'use strict' + +Application.Controllers.controller "PlansIndexController", ["$scope", "$state", '$uibModal', 'Auth', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t' +, ($scope, $state, $uibModal, Auth, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t) -> + + + + ### PUBLIC SCOPE ### + + ## list of groups + $scope.groups = groupsPromise + + ## default : do not show the group changing form + $scope.changeGroup = false + + ## group ID of the current/selected user + $scope.userGroup = null + + ## list of plans, classified by group + $scope.plansClassifiedByGroup = [] + for group in groupsPromise + groupObj = { id: group.id, name: group.name, plans: [] } + for plan in plansPromise + groupObj.plans.push(plan) if plan.group_id == group.id + $scope.plansClassifiedByGroup.push(groupObj) + + ## user to deal with + $scope.ctrl = + member: null + member_id: null + + ## already subscribed plan of the current user + $scope.paidPlan = null + + ## plan to subscribe (shopping cart) + $scope.selectedPlan = null + + ## + $scope.subscriptionExplicationsAlert = subscriptionExplicationsPromise.setting.value + + ## + # Callback to deal with the subscription of the user selected in the dropdown list instead of the current user's + # subscription. (admins only) + ## + $scope.updateMember = -> + $scope.selectedPlan = null + $scope.paidPlan = null + $scope.userGroup = $scope.ctrl.member.group_id + $scope.changeGroup = false + + + + ## + # Add the provided plan to the shopping basket + # @param plan {Object} The plan to subscribe to + ## + $scope.selectPlan = (plan) -> + if $scope.isAuthenticated() + if $scope.selectedPlan != plan + $scope.selectedPlan = plan + else + $scope.selectedPlan = null + else + $scope.login() + + + + ## + # Callback to trigger the payment process of the subscription + ## + $scope.openSubscribePlanModal = -> + if $scope.currentUser.role isnt 'admin' + payByStripe() + else + payOnSite() + + + + ## + # Return the group object, identified by the ID set in $scope.userGroup + ## + $scope.getUserGroup = -> + for group in $scope.groups + if group.id == $scope.userGroup + return group + + + + ## + # Change the group of the current/selected user to the one set in $scope.userGroup + ## + $scope.selectGroup = -> + Member.update {id: $scope.ctrl.member.id}, {user: {group_id: $scope.userGroup}}, (user) -> + $scope.ctrl.member = user + $scope.changeGroup = false + if $scope.currentUser.role isnt 'admin' + $scope.currentUser = user + growl.success(_t('your_group_was_successfully_changed')) + else + growl.success(_t('the_user_s_group_was_successfully_changed')) + , (err) -> + if $scope.currentUser.role isnt 'admin' + growl.error(_t('an_error_prevented_your_group_from_being_changed')) + else + growl.error(_t('an_error_prevented_to_change_the_user_s_group')) + console.error(err) + + + ## + # Return an enumerable meaninful string for the gender of the provider user + # @param user {Object} Database user record + # @return {string} 'male' or 'female' + ## + $scope.getGender = (user) -> + if user.profile + if user.profile.gender == "true" then 'male' else 'female' + else 'other' + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + if $scope.currentUser + if $scope.currentUser.role isnt 'admin' + $scope.ctrl.member = $scope.currentUser + $scope.paidPlan = $scope.currentUser.subscribed_plan + $scope.userGroup = $scope.currentUser.group_id + else + Member.query {requested_attributes:'[subscription]'}, (members) -> + membersNoPlan = [] + angular.forEach members, (v)-> + membersNoPlan.push v unless v.subscribed_plan + $scope.members = membersNoPlan + + $scope.$on 'devise:new-session', (event, user)-> + $scope.ctrl.member = user + + + $scope.isInFuture = (dateTime)-> + if moment().diff(moment(dateTime)) < 0 + true + else + false + + ## + # Open a modal window which trigger the stripe payment process + ## + payByStripe = -> + $uibModal.open + templateUrl: '<%= asset_path "stripe/payment_modal.html" %>' + size: 'md' + resolve: + selectedPlan: -> $scope.selectedPlan + member: -> $scope.ctrl.member + controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'Subscription', 'CustomAsset', ($scope, $uibModalInstance, $state, selectedPlan, member, Subscription, CustomAsset) -> + $scope.amount = selectedPlan.amount + $scope.selectedPlan = selectedPlan + # retrieve the CGV + CustomAsset.get {name: 'cgv-file'}, (cgv) -> + $scope.cgv = cgv.custom_asset + $scope.payment = (status, response) -> + if response.error + growl.error(response.error.message) + else + $scope.attempting = true + Subscription.save + subscription: + plan_id: selectedPlan.id + user_id: member.id + card_token: response.id + , (data, status) -> # success + $uibModalInstance.close(data) + , (data, status) -> # failed + $scope.alerts = [] + $scope.alerts.push({msg: _t('an_error_occured_during_the_payment_process_please_try_again_later'), type: 'danger' }) + $scope.attempting = false + ] + .result['finally'](null).then (subscription)-> + $scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan) + Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan) + $scope.paidPlan = angular.copy($scope.selectedPlan) + $scope.selectedPlan = null + + + + ## + # Open a modal window which trigger the local payment process + ## + payOnSite = -> + $uibModal.open + templateUrl: '<%= asset_path "plans/payment_modal.html" %>' + size: 'sm' + resolve: + selectedPlan: -> $scope.selectedPlan + member: -> $scope.ctrl.member + controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'Subscription', ($scope, $uibModalInstance, $state, selectedPlan, member, Subscription) -> + $scope.plan = selectedPlan + $scope.member = member + $scope.ok = -> + $scope.attempting = true + Subscription.save + subscription: + plan_id: selectedPlan.id + user_id: member.id + , (data, status) -> # success + $uibModalInstance.close(data) + , (data, status) -> # failed + $scope.alerts = [] + $scope.alerts.push({msg: _t('an_error_occured_during_the_payment_process_please_try_again_later'), type: 'danger' }) + $scope.attempting = false + + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + ] + .result['finally'](null).then (reservation)-> + $scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan) + Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan) + index = $scope.members.indexOf($scope.ctrl.member) + $scope.members.splice(index, 1) + $scope.ctrl.member = null + $scope.paidPlan = angular.copy($scope.selectedPlan) + $scope.selectedPlan = null + + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() +] diff --git a/app/assets/javascripts/controllers/profile.coffee b/app/assets/javascripts/controllers/profile.coffee new file mode 100644 index 000000000..9162f4d28 --- /dev/null +++ b/app/assets/javascripts/controllers/profile.coffee @@ -0,0 +1,152 @@ + +'use strict' + +Application.Controllers.controller "CompleteProfileController", ["$scope", "$rootScope", "$state", "_t", "$locale", "growl", "CSRF", "Auth", "Member", "settingsPromise", "activeProviderPromise", "groupsPromise", "cguFile", "memberPromise" +, ($scope, $rootScope, $state, _t, $locale, growl, CSRF, Auth, Member, settingsPromise, activeProviderPromise, groupsPromise, cguFile, memberPromise) -> + + + + ### PUBLIC SCOPE ### + + ## API URL where the form will be posted + $scope.actionUrl = "/api/members/" + memberPromise.id + + ## Form action on the above URL + $scope.method = 'patch' + + ## genre of the application name (eg. "_le_ Fablab" or "_la_ Fabrique") + $scope.nameGenre = settingsPromise.name_genre + + ## name of the current fablab application (eg. "Fablab de la Casemate") + $scope.fablabName = settingsPromise.fablab_name + + ## informations from the current SSO provider + $scope.activeProvider = activeProviderPromise + + ## list of user's groups (student/standard/...) + $scope.groups = groupsPromise + + ## current user, contains informations retrieved from the SSO + $scope.user = memberPromise + + ## disallow the user to change his password as he connect from SSO + $scope.preventPassword = true + + ## mapping of fields to disable + $scope.preventField = {} + + ## CGU + $scope.cgu = cguFile.custom_asset + + ## Angular-Bootstrap datepicker configuration for birthday + $scope.datePicker = + format: $locale.DATETIME_FORMATS.shortDate + opened: false # default: datePicker is not shown + options: + startingDay: Fablab.weekStartingDay + + + + ## + # Callback to diplay the datepicker as a dropdown when clicking on the input field + # @param $event {Object} jQuery event object + ## + $scope.openDatePicker = ($event) -> + $event.preventDefault() + $event.stopPropagation() + $scope.datePicker.opened = true + + + + ## + # For use with ngUpload (https://github.com/twilson63/ngUpload). + # Intended to be the callback when the upload is done: any raised error will be stacked in the + # $scope.alerts array. If everything goes fine, the user's profile is updated and the user is + # redirected to the home page + # @param content {Object} JSON - The upload's result + ## + $scope.submited = (content) -> + if !content.id? + $scope.alerts = [] + angular.forEach content, (v, k)-> + angular.forEach v, (err)-> + $scope.alerts.push + msg: k+': '+err, + type: 'danger' + else + $scope.user.profile.user_avatar = content.profile.user_avatar + Auth._currentUser.profile.user_avatar = content.profile.user_avatar + $scope.user.name = content.name + Auth._currentUser.name = content.name + $scope.user = content + Auth._currentUser = content + $rootScope.currentUser = content + $state.go('app.public.home') + + ## + # For use with 'ng-class', returns the CSS class name for the uploads previews. + # The preview may show a placeholder or the content of the file depending on the upload state. + # @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules) + ## + $scope.fileinputClass = (v)-> + if v + 'fileinput-exists' + else + 'fileinput-new' + + + + ## + # Merge the current user into the account with the given auth_token + ## + $scope.registerAuthToken = -> + Member.merge {id: $rootScope.currentUser.id}, {user: {auth_token: $scope.user.auth_token}}, (user) -> + $scope.user = user + Auth._currentUser = user + $rootScope.currentUser = user + $state.go('app.public.home') + , (err) -> + if err.data.error + growl.error(err.data.error) + else + growl.error(_t('an_unexpected_error_occurred_check_your_authentication_code')) + console.error(err) + + ## + # Return the email given by the SSO provider, parsed if needed + # @return {String} E-mail of the current user + ## + $scope.ssoEmail = -> + email = memberPromise.email + if email + duplicate = email.match(/^<([^>]+)>.{20}-duplicate$/) + if duplicate + return duplicate[1] + email + + + + ### PRIVATE SCOPE ### + + + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + CSRF.setMetaTags() + + # init the birth date to JS object + $scope.user.profile.birthday = moment($scope.user.profile.birthday).toDate() + + # bind fields protection with sso fields + angular.forEach activeProviderPromise.mapping, (map) -> + $scope.preventField[map] = true + + + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() + +] \ No newline at end of file diff --git a/app/assets/javascripts/controllers/projects.coffee b/app/assets/javascripts/controllers/projects.coffee.erb similarity index 64% rename from app/assets/javascripts/controllers/projects.coffee rename to app/assets/javascripts/controllers/projects.coffee.erb index 4fa8f55ae..700c79412 100644 --- a/app/assets/javascripts/controllers/projects.coffee +++ b/app/assets/javascripts/controllers/projects.coffee.erb @@ -144,30 +144,28 @@ class ProjectsController ## # Controller used on projects listing page ## -Application.Controllers.controller "projectsController", ["$scope", "$state", 'Project', 'Machine', 'Theme', 'Component', ($scope, $state, Project, Machine, Theme, Component) -> - - +Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'Project', 'machinesPromise', 'themesPromise', 'componentsPromise' +, ($scope, $state, Project, machinesPromise, themesPromise, componentsPromise) -> ### PRIVATE STATIC CONSTANTS ### # Number of notifications added to the page when the user clicks on 'load next notifications' PROJECTS_PER_PAGE = 12 - - ### PUBLIC SCOPE ### + $scope.search = { q: "", from: undefined, machine_id: undefined, component_id: undefined, theme_id: undefined } ## list of projects to display $scope.projects = [] ## list of machines / used for filtering - $scope.machines = [] + $scope.machines = machinesPromise ## list of themes / used for filtering - $scope.themes = Theme.query() + $scope.themes = themesPromise ## list of components / used for filtering - $scope.components = Component.query() + $scope.components = componentsPromise ## By default, the pagination mode is activated to limit the page size $scope.paginateActive = true @@ -175,19 +173,31 @@ Application.Controllers.controller "projectsController", ["$scope", "$state", 'P ## The currently displayed page number $scope.page = 1 + $scope.resetFilters = -> + $scope.search.q = "" + $scope.search.from = undefined + $scope.search.machine_id = undefined + $scope.search.component_id = undefined + $scope.search.theme_id = undefined + $scope.triggerSearch() + $scope.triggerSearch = -> + Project.search { search: $scope.search, page: 1 }, (projects)-> + $scope.projects = projects + if projects.length < PROJECTS_PER_PAGE + $scope.paginateActive = false + else + $scope.paginateActive = true + $scope.page = 2 - ## - # Request the server to retrieve the next undisplayed projects and add them - # to the local projects list. - ## $scope.loadMoreProjects = -> - Project.query {page: $scope.page}, (projects) -> - $scope.projects = $scope.projects.concat projects - $scope.paginateActive = false if projects.length < PROJECTS_PER_PAGE - - $scope.page += 1 - + # Project.query {page: $scope.page}, (projects) -> + # $scope.projects = $scope.projects.concat projects + # $scope.paginateActive = false if projects.length < PROJECTS_PER_PAGE + Project.search { search: $scope.search, page: $scope.page }, (projects)-> + $scope.projects = $scope.projects.concat projects + $scope.paginateActive = false if projects.length < PROJECTS_PER_PAGE + $scope.page += 1 ## @@ -199,37 +209,9 @@ Application.Controllers.controller "projectsController", ["$scope", "$state", 'P - ## - # Callback to delete the provided project. Then, the projects list page is refreshed (admins only) - ## - $scope.delete = (project) -> - # check the permissions - if $scope.currentUser.role isnt 'admin' - console.error 'Unauthorized operation' - else - # delete the project then refresh the projects list - project.$delete -> - $state.go('app.public.projects_list', {}, {reload: true}) + ## initialization + $scope.triggerSearch() - - - ### PRIVATE SCOPE ### - - ## - # Kind of constructor: these actions will be realized first when the controller is loaded - ## - initialize = -> - Machine.query().$promise.then (data)-> - $scope.machines = data.map (d) -> - id: d.id - name: d.name - - $scope.loadMoreProjects() - - - - ## !!! MUST BE CALLED AT THE END of the controller - initialize() ] @@ -237,7 +219,8 @@ Application.Controllers.controller "projectsController", ["$scope", "$state", 'P ## # Controller used in the project creation page ## -Application.Controllers.controller "newProjectController", ["$scope", "$state", 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, CSRF) -> +Application.Controllers.controller "NewProjectController", ["$scope", "$state", 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF' +, ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, CSRF) -> CSRF.setMetaTags() ## API URL where the form will be posted @@ -246,9 +229,6 @@ Application.Controllers.controller "newProjectController", ["$scope", "$state", ## Form action on the above URL $scope.method = 'post' - ## Button litteral text value - $scope.submitName = 'Enregistrer comme brouillon' - ## Default project parameters $scope.project = project_steps_attributes: [] @@ -271,7 +251,8 @@ Application.Controllers.controller "newProjectController", ["$scope", "$state", ## # Controller used in the project edition page ## -Application.Controllers.controller "editProjectController", ["$scope", "$state", '$stateParams', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', ($scope, $state, $stateParams, Project, Machine, Member, Component, Theme, Licence, $document, CSRF) -> +Application.Controllers.controller "EditProjectController", ["$scope", "$state", '$stateParams', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', 'projectPromise' +, ($scope, $state, $stateParams, Project, Machine, Member, Component, Theme, Licence, $document, CSRF, projectPromise) -> CSRF.setMetaTags() ## API URL where the form will be posted @@ -280,15 +261,8 @@ Application.Controllers.controller "editProjectController", ["$scope", "$state", ## Form action on the above URL $scope.method = 'put' - ## Button litteral text value - $scope.submitName = 'Enregistrer' - ## Retrieve the project's details, if an error occured, redirect the user to the projects list page - $scope.project = Project.get {id: $stateParams.id} - , -> # success - return - , -> # failed - $state.go('app.public.projects_list') + $scope.project = projectPromise ## Other members list (project collaborators) Member.query().$promise.then (data)-> @@ -307,18 +281,15 @@ Application.Controllers.controller "editProjectController", ["$scope", "$state", ## # Controller used in the public project's details page ## -Application.Controllers.controller "showProjectController", ["$scope", "$state", "$stateParams", "Project", '$location', ($scope, $state, $stateParams, Project, $location) -> - - +Application.Controllers.controller "ShowProjectController", ["$scope", "$state", "projectPromise", '$location', '$uibModal', '_t' +, ($scope, $state, projectPromise, $location, $uibModal, _t) -> ### PUBLIC SCOPE ### - ## Will be set to true once the project details are loaded. Used to load the Disqus plugin at the right moment - $scope.contentLoaded = false - ## Store the project's details - $scope.project = {} - + $scope.project = projectPromise + $scope.projectUrl = $location.absUrl() + $scope.disqusShortname = Fablab.disqusShortname ## @@ -336,23 +307,63 @@ Application.Controllers.controller "showProjectController", ["$scope", "$state", - ### PRIVATE SCOPE ### + ## + # Test if the provided user has the deletion rights on the current project + # @param [user] {{id:number}} (optional) the user to check rights + # @returns boolean + ## + $scope.projectDeletableBy = (user) -> + return false if not user? + return true if $scope.project.author_id == user.id + + ## - # Kind of constructor: these actions will be realized first when the controller is loaded + # Callback to delete the current project. Then, the user is redirected to the projects list page, + # which is refreshed. Admins and project owner only are allowed to delete a project ## - initialize = -> - ## Retrieve the project content - $scope.project = Project.get {id: $stateParams.id} - , -> # success - $scope.contentLoaded = true - $scope.project_url = $location.absUrl() - return - , -> # failed, redirect the user to the projects listing - $state.go('app.public.projects_list') + $scope.deleteProject = -> + # check the permissions + if $scope.currentUser.role is 'admin' or $scope.projectDeletableBy($scope.currentUser) + # delete the project then refresh the projects list + $scope.project.$delete -> + $state.go('app.public.projects_list', {}, {reload: true}) + else + console.error _t('unauthorized_operation') + ## + # Open a modal box containg a form that allow the end-user to signal an abusive content + # @param e {Object} jQuery event + ## + $scope.signalAbuse = (e) -> + e.preventDefault() if e + $uibModal.open + templateUrl: '<%= asset_path "shared/signalAbuseModal.html" %>' + size: 'md' + resolve: + project: -> $scope.project + controller: ['$scope', '$uibModalInstance', '_t', 'growl', 'Abuse', 'project', ($scope, $uibModalInstance, _t, growl, Abuse, project) -> + + # signaler's profile & signalement infos + $scope.signaler = { + signaled_type: 'Project' + signaled_id: project.id + } + + # callback for signaling cancellation + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + + # callback for form validation + $scope.ok = -> + Abuse.save {}, {abuse: $scope.signaler}, (res) -> + # creation successful + growl.success(_t('your_report_was_successful_thanks')) + $uibModalInstance.close(res) + , (error) -> + # creation failed... + growl.error(_t('an_error_occured_while_sending_your_report')) + ] - ## !!! MUST BE CALLED AT THE END of the controller - initialize() ] diff --git a/app/assets/javascripts/controllers/trainings.coffee.erb b/app/assets/javascripts/controllers/trainings.coffee.erb new file mode 100644 index 000000000..ee1f678f4 --- /dev/null +++ b/app/assets/javascripts/controllers/trainings.coffee.erb @@ -0,0 +1,658 @@ +'use strict' + +## +# Controller used in the training reservation agenda page. +# This controller is very similar to the machine reservation controller with one major difference: here, ONLY ONE +# training can be reserved during the reservation process (the shopping cart may contains only one training and a subscription). +## + +Application.Controllers.controller "ReserveTrainingController", ["$scope", "$state", '$stateParams', "$uibModal", 'Auth', 'dialogs', '$timeout', 'Price', 'Availability', 'Slot', 'Member', 'Setting', 'CustomAsset', '$compile', 'availabilityTrainingsPromise', 'plansPromise', 'groupsPromise', 'growl', 'settingsPromise', '_t', +($scope, $state, $stateParams, $uibModal, Auth, dialogs, $timeout, Price, Availability, Slot, Member, Setting, CustomAsset, $compile, availabilityTrainingsPromise, plansPromise, groupsPromise, growl, settingsPromise, _t) -> + + + + ### PRIVATE STATIC CONSTANTS ### + + # The calendar is divided in slots of 60 minutes + BASE_SLOT = '01:00:00' + + # The calendar will be initialized positioned under 9:00 AM + DEFAULT_CALENDAR_POSITION = '09:00:00' + + # The user is unable to modify his already booked reservation 1 day before it occurs + PREVENT_BOOKING_MODIFICATION_DELAY = 1 + + # Color of the selected event backgound + SELECTED_EVENT_BG_COLOR = '#ffdd00' + + # Slot already booked by the current user + FREE_SLOT_BORDER_COLOR = '#bd7ae9' + + + + ### PUBLIC SCOPE ### + + ## after fullCalendar loads, provides access to its methods through $scope.calendar.fullCalendar() + $scope.calendar = null + + ## bind the trainings availabilities with full-Calendar events + $scope.eventSources = [ { events: availabilityTrainingsPromise, textColor: 'black' } ] + + ## the user to deal with, ie. the current user for non-admins + $scope.ctrl = + member: {} + + ## the full list of members, used by admin to select a user to interact with + $scope.members = [] + + ## list of plans, classified by group + $scope.plansClassifiedByGroup = [] + for group in groupsPromise + groupObj = { id: group.id, name: group.name, plans: [] } + for plan in plansPromise + groupObj.plans.push(plan) if plan.group_id == group.id + $scope.plansClassifiedByGroup.push(groupObj) + + ## indicates the state of the current view : calendar or plans informations + $scope.plansAreShown = false + + ## indicates if the selected training was validated (ie. added to the shopping cart) + $scope.trainingIsValid = false + + ## contains the selected training once it was payed, allows to display a firendly end-of-shopping message + $scope.paidTraining = null + + ## will store the user's plan if he choosed to buy one + $scope.selectedPlan = null + + ## fullCalendar event. Training slot that the user want to book + $scope.selectedTraining = null + + ## fullCalendar event. An already booked slot that the user want to modify + $scope.slotToModify = null + + ## Once a training reservation was modified, will contains {newReservedSlot:{}, oldReservedSlot:{}} + $scope.modifiedSlots = null + + ## fullCalendar (v2) configuration + $scope.calendarConfig = + timezone: Fablab.timezone + lang: Fablab.fullcalendar_locale + header: + left: 'month agendaWeek' + center: 'title' + right: 'today prev,next' + firstDay: 1 # Week start on monday (France) + scrollTime: DEFAULT_CALENDAR_POSITION + slotDuration: BASE_SLOT + allDayDefault: false + minTime: '00:00:00' + maxTime: '24:00:00' + height: 'auto' + buttonIcons: + prev: 'left-single-arrow' + next: 'right-single-arrow' + timeFormat: + agenda:'H:mm' + month: 'H(:mm)' + axisFormat: 'H:mm' + + allDaySlot: false + defaultView: 'agendaWeek' + editable: false + eventClick: (event, jsEvent, view) -> + calendarEventClickCb(event, jsEvent, view) + eventAfterAllRender: (view)-> + $scope.events = $scope.calendar.fullCalendar 'clientEvents' + eventRender: (event, element, view) -> + eventRenderCb(event, element, view) + + ## Custom settings + $scope.subscriptionExplicationsAlert = settingsPromise.subscription_explications_alert + $scope.trainingExplicationsAlert = settingsPromise.training_explications_alert + $scope.trainingInformationMessage = settingsPromise.training_information_message + $scope.enableBookingMove = (settingsPromise.booking_move_enable == "true") + $scope.moveBookingDelay = parseInt(settingsPromise.booking_move_delay) + $scope.enableBookingCancel = (settingsPromise.booking_cancel_enable == "true") + $scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay) + $scope.calendarConfig.minTime = moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss')) + $scope.calendarConfig.maxTime = moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss')) + + + + ## + # Callback to deal with the reservations of the user selected in the dropdown list instead of the current user's + # reservations. (admins only) + ## + $scope.updateMember = -> + if $scope.ctrl.member + Availability.trainings {member_id: $scope.ctrl.member.id}, (trainings) -> + $scope.calendar.fullCalendar 'removeEvents' + $scope.eventSources.push + events: trainings + textColor: 'black' + $scope.trainingIsValid = false + $scope.paidTraining = null + $scope.plansAreShown = false + $scope.selectedPlan = null + $scope.selectedTraining = null + $scope.slotToModify = null + $scope.modifiedSlots = null + + + + ## + # Callback to mark the selected training as validated (add it to the shopping cart). + ## + $scope.validTraining = -> + $scope.trainingIsValid = true + $scope.updatePrices() + + + + ## + # Remove the training from the shopping cart + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.removeTraining = (e) -> + e.preventDefault() + + $scope.selectedTraining.backgroundColor = 'white' + $scope.selectedTraining = null + $scope.plansAreShown = false + $scope.selectedPlan = null + $scope.trainingIsValid = false + $timeout -> + $scope.calendar.fullCalendar 'refetchEvents' + $scope.calendar.fullCalendar 'rerenderEvents' + + + + ## + # Validates the shopping chart and redirect the user to the payment step + ## + $scope.payTraining = -> + + # first, we check that a user was selected + if Object.keys($scope.ctrl.member).length > 0 + reservation = mkReservation($scope.ctrl.member, $scope.selectedTraining, $scope.selectedPlan) + + if $scope.currentUser.role isnt 'admin' and $scope.amountTotal > 0 + payByStripe(reservation) + else + if $scope.currentUser.role is 'admin' or $scope.amountTotal is 0 + payOnSite(reservation) + else + # otherwise we alert, this error musn't occur when the current user is not admin + growl.error(_t('please_select_a_member_first')) + + + + ## + # Add the provided plan to the current shopping cart + # @param plan {Object} the plan to subscribe + ## + $scope.selectPlan = (plan) -> + if $scope.isAuthenticated() + if $scope.selectedPlan != plan + $scope.selectedPlan = plan + $scope.updatePrices() + else + $scope.selectedPlan = null + $scope.updatePrices() + else + $scope.login null, -> + $scope.selectedPlan = plan + $scope.updatePrices() + + + + ## + # Changes the user current view from the plan subsription screen to the machine reservation agenda + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.doNotSubscribePlan = (e)-> + e.preventDefault() + $scope.plansAreShown = false + $scope.selectedPlan = null + $scope.updatePrices() + + ## + # Switch the user's view from the reservation agenda to the plan subscription + ## + $scope.showPlans = -> + $scope.plansAreShown = true + + ## + # Cancel the current booking modification, removing the previously booked slot from the selection + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.removeSlotToModify = (e) -> + e.preventDefault() + if $scope.slotToPlace + $scope.slotToPlace.backgroundColor = 'white' + $scope.slotToPlace.title = $scope.slotToPlace.training.name + $scope.slotToPlace = null + $scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then $scope.slotToModify.training.name + " - " + _t('i_ve_reserved') else $scope.slotToModify.training.name + $scope.slotToModify.backgroundColor = 'white' + $scope.slotToModify = null + $scope.calendar.fullCalendar 'rerenderEvents' + + + + ## + # When modifying an already booked reservation, cancel the choice of the new slot + # @param e {Object} see https://docs.angularjs.org/guide/expression#-event- + ## + $scope.removeSlotToPlace = (e)-> + e.preventDefault() + $scope.slotToPlace.backgroundColor = 'white' + $scope.slotToPlace.title = $scope.slotToPlace.training.name + $scope.slotToPlace = null + $scope.calendar.fullCalendar 'rerenderEvents' + + + + ## + # When modifying an already booked reservation, confirm the modification. + ## + $scope.modifyTrainingSlot = -> + Slot.update {id: $scope.slotToModify.slot_id}, + slot: + start_at: $scope.slotToPlace.start + end_at: $scope.slotToPlace.end + availability_id: $scope.slotToPlace.id + , -> # success + $scope.modifiedSlots = + newReservedSlot: $scope.slotToPlace + oldReservedSlot: $scope.slotToModify + $scope.slotToPlace.title = if $scope.currentUser.role isnt 'admin' then $scope.slotToPlace.training.name + " - " + _t('i_ve_reserved') else $scope.slotToPlace.training.name + $scope.slotToPlace.backgroundColor = 'white' + $scope.slotToPlace.borderColor = $scope.slotToModify.borderColor + $scope.slotToPlace.slot_id = $scope.slotToModify.slot_id + $scope.slotToPlace.is_reserved = true + $scope.slotToPlace.can_modify = true + $scope.slotToPlace = null + + $scope.slotToModify.backgroundColor = 'white' + $scope.slotToModify.title = $scope.slotToModify.training.name + $scope.slotToModify.borderColor = FREE_SLOT_BORDER_COLOR + $scope.slotToModify.slot_id = null + $scope.slotToModify.is_reserved = false + $scope.slotToModify.can_modify = false + $scope.slotToModify.is_completed = false if $scope.slotToModify.is_completed + $scope.slotToModify = null + $scope.calendar.fullCalendar 'rerenderEvents' + , -> # failure + growl.error('an_error_occured_preventing_the_booked_slot_from_being_modified') + + + + ## + # Cancel the current booking modification, reseting the whole process + ## + $scope.cancelModifyMachineSlot = -> + $scope.slotToPlace.backgroundColor = 'white' + $scope.slotToPlace.title = $scope.slotToPlace.training.name + $scope.slotToPlace = null + $scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then $scope.slotToModify.training.name + " - " + _t('i_ve_reserved') else $scope.slotToModify.training.name + $scope.slotToModify.backgroundColor = 'white' + $scope.slotToModify = null + $scope.calendar.fullCalendar 'rerenderEvents' + + + + ## + # Update the prices, based on the current selection + ## + $scope.updatePrices = -> + if Object.keys($scope.ctrl.member).length > 0 + r = mkReservation($scope.ctrl.member, $scope.selectedTraining, $scope.selectedPlan) + Price.compute {reservation: r}, (res) -> + $scope.amountTotal = res.price + else + $scope.amountTotal = null + + + + ### PRIVATE SCOPE ### + + ## + # Kind of constructor: these actions will be realized first when the controller is loaded + ## + initialize = -> + if $scope.currentUser.role isnt 'admin' + Member.get id: $scope.currentUser.id, (member) -> + $scope.ctrl.member = member + else + Member.query {requested_attributes:'[subscription,credits]'}, (members) -> + $scope.members = members + + + + ## + # Create an hash map implementing the Reservation specs + # @param member {Object} User as retreived from the API: current user / selected user if current is admin + # @param training {Object} fullCalendar event: training slot selected on the calendar + # @param [plan] {Object} Plan as retrived from the API: plan to buy with the current reservation + # @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array, plan_id:Number|null}} + ## + mkReservation = (member, training, plan = null) -> + reservation = + user_id: member.id + reservable_id: training.training.id + reservable_type: 'Training' + slots_attributes: [] + plan_id: (plan.id if plan) + + reservation.slots_attributes.push + start_at: training.start + end_at: training.end + availability_id: training.id + offered: training.offered || false + + reservation + + + + ## + # Triggered when the user clicks on a reservation slot in the agenda. + # Defines the behavior to adopt depending on the slot status (already booked, free, ready to be reserved ...), + # the user's subscription (current or about to be took) and the time (the user cannot modify a booked reservation + # if it's too late). + # @see http://fullcalendar.io/docs/mouse/eventClick/ + ## + calendarEventClickCb = (event, jsEvent, view) -> + if $scope.ctrl.member + # reserve a training if this training will not be reserved and is not about to move and not is completed + if !event.is_reserved && !$scope.slotToModify && !event.is_completed + if event != $scope.selectedTraining + $scope.selectedTraining = event + $scope.selectedTraining.offered = false + event.backgroundColor = SELECTED_EVENT_BG_COLOR + computeTrainingAmount($scope.selectedTraining) + else + $scope.selectedTraining = null + event.backgroundColor = 'white' + $scope.trainingIsValid = false + $scope.paidTraining = null + $scope.selectedPlan = null + $scope.modifiedSlots = null + # clean all others events background + angular.forEach $scope.events, (e)-> + if event.id != e.id + e.backgroundColor = 'white' + $scope.calendar.fullCalendar 'rerenderEvents' + # two if below for move training reserved + # if training isnt reserved and have a training to modify and same training and not complete + else if !event.is_reserved && $scope.slotToModify && slotCanBePlaced(event) + if $scope.slotToPlace + $scope.slotToPlace.backgroundColor = 'white' + $scope.slotToPlace.title = event.training.name + $scope.slotToPlace = event + event.backgroundColor = '#bbb' + event.title = event.training.name + ' - ' + _t('i_shift') + $scope.calendar.fullCalendar 'rerenderEvents' + # if training reserved can modify + else if event.is_reserved and (slotCanBeModified(event) or slotCanBeCanceled(event)) and !$scope.slotToModify and !$scope.selectedTraining + event.movable = slotCanBeModified(event) + event.cancelable = slotCanBeCanceled(event) + if $scope.currentUser.role is 'admin' + event.user = + name: $scope.ctrl.member.name + dialogs.confirm + templateUrl: '<%= asset_path "shared/confirm_modify_slot_modal.html" %>' + resolve: + object: -> event + , (type) -> # success + if type == 'move' + $scope.modifiedSlots = null + $scope.slotToModify = event + event.backgroundColor = '#eee' + event.title = event.training.name + ' - ' + _t('i_change') + $scope.calendar.fullCalendar 'rerenderEvents' + else if type == 'cancel' + dialogs.confirm + resolve: + object: -> + title: _t('confirmation_required') + msg: _t('do_you_really_want_to_cancel_this_reservation') + , -> # cancel confirmed + Slot.cancel {id: event.slot_id}, -> # successfully canceled + growl.success _t('reservation_was_successfully_cancelled') + $scope.canceledSlot = event + $scope.canceledSlot.backgroundColor = 'white' + $scope.canceledSlot.title = event.training.name + $scope.canceledSlot.borderColor = FREE_SLOT_BORDER_COLOR + $scope.canceledSlot.slot_id = null + $scope.canceledSlot.is_reserved = false + $scope.canceledSlot.can_modify = false + $scope.canceledSlot.is_completed = false if event.is_completed + $scope.canceledSlot = null + $scope.calendar.fullCalendar 'rerenderEvents' + , -> # error while canceling + growl.error _t('cancellation_failed') + , -> # canceled + $scope.paidMachineSlots = null + $scope.selectedPlan = null + $scope.modifiedSlots = null + + + + ## + # When events are rendered, adds attributes for popover and compile + # @see http://fullcalendar.io/docs/event_rendering/eventRender/ + ## + eventRenderCb = (event, element, view)-> + element.attr( + 'uib-popover': event.training.description + 'popover-trigger': 'mouseenter' + 'popover-append-to-body': true + ) + $compile(element)($scope) + + + + ## + # Open a modal window that allows the user to process a credit card payment for his current shopping cart. + ## + payByStripe = (reservation) -> + + $uibModal.open + templateUrl: '<%= asset_path "stripe/payment_modal.html" %>' + size: 'md' + resolve: + reservation: -> + reservation + price: -> + Price.compute({reservation: reservation}).$promise + cgv: -> + CustomAsset.get({name: 'cgv-file'}).$promise + controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation) -> + # Price + $scope.amount = price.price + + # CGV + $scope.cgv = cgv.custom_asset + + # Reservation + $scope.reservation = reservation + + ## + # Callback to process the payment with Stripe, triggered on button click + ## + $scope.payment = (status, response) -> + if response.error + growl.error(response.error.message) + else + $scope.attempting = true + $scope.reservation.card_token = response.id + Reservation.save reservation: $scope.reservation, (reservation) -> + $uibModalInstance.close(reservation) + , (response)-> + $scope.alerts = [] + if response.data.card + $scope.alerts.push + msg: response.data.card[0] + type: 'danger' + else + $scope.alerts.push({msg: _t('a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' }) + $scope.attempting = false + ] + .result['finally'](null).then (reservation)-> + afterPayment(reservation) + + + + ## + # Open a modal window that allows the user to process a local payment for his current shopping cart (admin only). + ## + payOnSite = (reservation) -> + + $uibModal.open + templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>' + size: 'sm' + resolve: + reservation: -> + reservation + price: -> + Price.compute({reservation: reservation}).$promise + controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation) -> + # Price + $scope.amount = price.price + + # Reservation + $scope.reservation = reservation + + # Button label + if $scope.amount > 0 + $scope.validButtonName = _t('confirm_(payment_on_site)') + else + $scope.validButtonName = _t('confirm') + + ## + # Callback to process the local payment, triggered on button click + ## + $scope.ok = -> + $scope.attempting = true + Reservation.save reservation: $scope.reservation, (reservation) -> + $uibModalInstance.close(reservation) + $scope.attempting = true + , (response)-> + $scope.alerts = [] + $scope.alerts.push({msg: _t('a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' }) + $scope.attempting = false + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') + ] + .result['finally'](null).then (reservation)-> + afterPayment(reservation) + + + + ## + # Computes the training amount depending of the member's credit + # @param training {Object} training slot + ## + computeTrainingAmount = (training)-> + # first we check that a user was selected + if Object.keys($scope.ctrl.member).length > 0 + r = mkReservation($scope.ctrl.member, training) # reservation without any Plan -> we get the training price + Price.compute {reservation: r}, (res) -> + $scope.selectedTrainingAmount = res.price + else + $scope.selectedTrainingAmount = null + + + + ## + # Once the reservation is booked (payment process successfully completed), change the event style + # in fullCalendar, update the user's subscription and free-credits if needed + # @param reservation {Object} + ## + afterPayment = (reservation)-> + $scope.paidTraining = $scope.selectedTraining + $scope.paidTraining.backgroundColor = 'white' + $scope.paidTraining.is_reserved = true + $scope.paidTraining.can_modify = true + updateTrainingSlotId($scope.paidTraining, reservation) + $scope.paidTraining.borderColor = '#b2e774' + $scope.paidTraining.title = $scope.paidTraining.training.name + " - " + _t('i_ve_reserved') + + $scope.selectedTraining = null + $scope.trainingIsValid = false + + if $scope.selectedPlan + $scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan) + Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan) + $scope.plansAreShown = false + $scope.selectedPlan = null + $scope.ctrl.member.training_credits = angular.copy(reservation.user.training_credits) + $scope.ctrl.member.machine_credits = angular.copy(reservation.user.machine_credits) + Auth._currentUser.training_credits = angular.copy(reservation.user.training_credits) + Auth._currentUser.machine_credits = angular.copy(reservation.user.machine_credits) + + $scope.calendar.fullCalendar 'refetchEvents' + $scope.calendar.fullCalendar 'rerenderEvents' + + + + ## + # Determines if the provided booked slot is able to be modified by the user. + # @param slot {Object} fullCalendar event object + ## + slotCanBeModified = (slot)-> + return true if $scope.currentUser.role is 'admin' + slotStart = moment(slot.start) + now = moment(new Date()) + if slot.can_modify and $scope.enableBookingMove and slotStart.diff(now, "hours") >= $scope.moveBookingDelay + return true + else + return false + + + + ## + # Determines if the provided booked slot is able to be canceled by the user. + # @param slot {Object} fullCalendar event object + ## + slotCanBeCanceled = (slot) -> + return true if $scope.currentUser.role is 'admin' + slotStart = moment(slot.start) + now = moment() + if slot.can_modify and $scope.enableBookingCancel and slotStart.diff(now, "hours") >= $scope.cancelBookingDelay + return true + else + return false + + + + ## + # For booking modifications, checks that the newly selected slot is valid + # @param slot {Object} fullCalendar event object + ## + slotCanBePlaced = (slot)-> + if slot.training.id == $scope.slotToModify.training.id and !slot.is_completed + return true + else + return false + + + + ## + # After payment, update the id of the newly reserved slot with the id returned by the server. + # This will allow the user to modify the reservation he just booked. + # @param slot {Object} + # @param reservation {Object} + ## + updateTrainingSlotId = (slot, reservation)-> + angular.forEach reservation.slots, (s)-> + if slot.start_at == slot.start_at + slot.slot_id = s.id + + + + ## !!! MUST BE CALLED AT THE END of the controller + initialize() + +] diff --git a/app/assets/javascripts/directives/bs-jasny-fileinput.js b/app/assets/javascripts/directives/bs-jasny-fileinput.js index b95a615fc..0c178aff6 100644 --- a/app/assets/javascripts/directives/bs-jasny-fileinput.js +++ b/app/assets/javascripts/directives/bs-jasny-fileinput.js @@ -28,7 +28,7 @@ Application.Directives.directive('bsJasnyFileinput', [function(){ ngModelCtrl.$setValidity('filetype', true); else ngModelCtrl.$setValidity('filetype', false); - } + }; } $scope.$apply(); }); diff --git a/app/assets/javascripts/directives/confirmation_needed.coffee b/app/assets/javascripts/directives/confirmation_needed.coffee new file mode 100644 index 000000000..76b026112 --- /dev/null +++ b/app/assets/javascripts/directives/confirmation_needed.coffee @@ -0,0 +1,20 @@ +Application.Directives.directive 'confirmationNeeded', [-> + return { + priority: 1 + terminal: true + link: (scope, element, attrs)-> + msg = attrs.confirmationNeeded || "Are you sure?" + clickAction = attrs.ngClick + element.bind 'click', -> + if attrs.confirmationNeededIf? + confirmNeededIf = scope.$eval(attrs.confirmationNeededIf) + if confirmNeededIf == true + if ( window.confirm(msg) ) + scope.$eval(clickAction) + else + scope.$eval(clickAction) + else + if ( window.confirm(msg) ) + scope.$eval(clickAction) + } +] diff --git a/app/assets/javascripts/directives/directives.coffee b/app/assets/javascripts/directives/directives.coffee index 8c4d5a748..0d00b4986 100644 --- a/app/assets/javascripts/directives/directives.coffee +++ b/app/assets/javascripts/directives/directives.coffee @@ -21,6 +21,8 @@ Application.Directives.directive 'bsHolder', [ -> { link: (scope, element, attrs) -> Holder.addTheme("icon", { background: "white", foreground: "#e9e9e9", size: 80, font: "FontAwesome"}) + .addTheme("icon-xs", { background: "white", foreground: "#e0e0e0", size: 20, font: "FontAwesome"}) + .addTheme("icon-black-xs", { background: "black", foreground: "white", size: 20, font: "FontAwesome"}) .addTheme("avatar", { background: "#eeeeee", foreground: "#555555", size: 16, font: "FontAwesome"}) .run(element[0]) return @@ -66,3 +68,38 @@ Application.Directives.directive "disableAnimation", ($animate) -> attrs.$observe "disableAnimation", (value) -> $animate.enabled not value, elem + +## +# Isolate a form's scope from its parent : no nested validation +## +Application.Directives.directive 'isolateForm', [ -> + { + restrict: 'A', + require: '?form' + link: (scope, elm, attrs, ctrl) -> + return unless ctrl + + # Do a copy of the controller + ctrlCopy = {} + angular.copy(ctrl, ctrlCopy) + + # Get the form's parent + parent = elm.parent().controller('form') + # Remove parent link to the controller + parent.$removeControl(ctrl) + + # Replace form controller with a "isolated form" + isolatedFormCtrl = + $setValidity: (validationToken, isValid, control) -> + ctrlCopy.$setValidity(validationToken, isValid, control); + parent.$setValidity(validationToken, true, ctrl); + + $setDirty: -> + elm.removeClass('ng-pristine').addClass('ng-dirty'); + ctrl.$dirty = true; + ctrl.$pristine = false; + + angular.extend(ctrl, isolatedFormCtrl) + + } +] \ No newline at end of file diff --git a/app/assets/javascripts/directives/stripe-angular.js b/app/assets/javascripts/directives/stripe-angular.js new file mode 100644 index 000000000..9f9dbb182 --- /dev/null +++ b/app/assets/javascripts/directives/stripe-angular.js @@ -0,0 +1,24 @@ +'use strict'; + +// https://github.com/gtramontina/stripe-angular + +Application.Directives.directive('stripeForm', ['$window', + function($window) { + var directive = { restrict: 'A' }; + directive.link = function(scope, element, attributes) { + var form = angular.element(element); + form.bind('submit', function() { + var button = form.find('button'); + button.prop('disabled', true); + $window.Stripe.createToken(form[0], function() { + var args = arguments; + scope.$apply(function() { + scope[attributes.stripeForm].apply(scope, args); + }); + //button.prop('disabled', false); + }); + }); + }; + return directive; + + }]); diff --git a/app/assets/javascripts/directives/validators.coffee b/app/assets/javascripts/directives/validators.coffee new file mode 100644 index 000000000..71f19c80a --- /dev/null +++ b/app/assets/javascripts/directives/validators.coffee @@ -0,0 +1,34 @@ +'use strict' + +Application.Directives.directive 'url', [ -> + URL_REGEXP = /^(https?:\/\/)([\da-z\.-]+)\.([-a-z0-9\.]{2,30})([\/\w \.-]*)*\/?$/ + { + require: 'ngModel' + link: (scope, element, attributes, ctrl) -> + ctrl.$validators.url = (modelValue, viewValue) -> + if ctrl.$isEmpty(modelValue) + return true + if URL_REGEXP.test(viewValue) + return true + + # otherwise, this is invalid + return false + } +] + + +Application.Directives.directive 'endpoint', [ -> + ENDPOINT_REGEXP = /^\/([-._~:?#\[\]@!$&'()*+,;=%\w]+\/?)*$/ + { + require: 'ngModel' + link: (scope, element, attributes, ctrl) -> + ctrl.$validators.endpoint = (modelValue, viewValue) -> + if ctrl.$isEmpty(modelValue) + return true + if ENDPOINT_REGEXP.test(viewValue) + return true + + # otherwise, this is invalid + return false + } +] \ No newline at end of file diff --git a/app/assets/javascripts/filters/filters.coffee b/app/assets/javascripts/filters/filters.coffee index e3277355a..2f3090870 100644 --- a/app/assets/javascripts/filters/filters.coffee +++ b/app/assets/javascripts/filters/filters.coffee @@ -1,7 +1,19 @@ 'use strict' +Application.Filters.filter 'array', [ -> + (arrayLength) -> + if (arrayLength) + arrayLength = Math.ceil(arrayLength) + arr = new Array(arrayLength) + + for i in [0 ... arrayLength] + arr[i] = i + + arr +] + # filter for projects and trainings -Application.Controllers.filter "machineFilter", [ -> +Application.Filters.filter "machineFilter", [ -> (elements, selectedMachine) -> if !angular.isUndefined(elements) and !angular.isUndefined(selectedMachine) and elements? and selectedMachine? filteredElements = [] @@ -13,7 +25,7 @@ Application.Controllers.filter "machineFilter", [ -> elements ] -Application.Controllers.filter "projectMemberFilter", [ "Auth", (Auth)-> +Application.Filters.filter "projectMemberFilter", [ "Auth", (Auth)-> (projects, selectedMember) -> if !angular.isUndefined(projects) and angular.isDefined(selectedMember) and projects? and selectedMember? and selectedMember != "" filteredProject = [] @@ -32,7 +44,7 @@ Application.Controllers.filter "projectMemberFilter", [ "Auth", (Auth)-> projects ] -Application.Controllers.filter "themeFilter", [ -> +Application.Filters.filter "themeFilter", [ -> (projects, selectedTheme) -> if !angular.isUndefined(projects) and !angular.isUndefined(selectedTheme) and projects? and selectedTheme? filteredProjects = [] @@ -44,7 +56,7 @@ Application.Controllers.filter "themeFilter", [ -> projects ] -Application.Controllers.filter "componentFilter", [ -> +Application.Filters.filter "componentFilter", [ -> (projects, selectedComponent) -> if !angular.isUndefined(projects) and !angular.isUndefined(selectedComponent) and projects? and selectedComponent? filteredProjects = [] @@ -56,7 +68,7 @@ Application.Controllers.filter "componentFilter", [ -> projects ] -Application.Controllers.filter "projectsByAuthor", [ -> +Application.Filters.filter "projectsByAuthor", [ -> (projects, authorId) -> if !angular.isUndefined(projects) and angular.isDefined(authorId) and projects? and authorId? and authorId != "" filteredProject = [] @@ -68,7 +80,7 @@ Application.Controllers.filter "projectsByAuthor", [ -> projects ] -Application.Controllers.filter "projectsCollabored", [ -> +Application.Filters.filter "projectsCollabored", [ -> (projects, memberId) -> if !angular.isUndefined(projects) and angular.isDefined(memberId) and projects? and memberId? and memberId != "" filteredProject = [] @@ -81,24 +93,84 @@ Application.Controllers.filter "projectsCollabored", [ -> ] # depend on humanize.js lib in /vendor -Application.Controllers.filter "humanize", [ -> +Application.Filters.filter "humanize", [ -> (element, param) -> Humanize.truncate(element, param, null) ] -Application.Controllers.filter "breakFilter", [ -> +Application.Filters.filter "breakFilter", [ -> (text) -> if text != undefined text.replace(/\n/g, '
') ] -Application.Controllers.filter "toTrusted", [ "$sce", ($sce) -> +Application.Filters.filter "toTrusted", [ "$sce", ($sce) -> (text) -> $sce.trustAsHtml text ] -Application.Controllers.filter "eventsFilter", [ -> +Application.Filters.filter "planIntervalFilter", [ -> + (interval, intervalCount) -> + if typeof intervalCount != 'number' + switch interval + when 'day' then return 'jour' + when 'week' then return 'semaine' + when 'month' then return 'mois' + when 'year' then return 'année' + else + if intervalCount == 1 + switch interval + when 'day' then return 'un jour' + when 'week' then return 'une semaine' + when 'month' then return 'un mois' + when 'year' then return 'un an' + else + switch interval + when 'day' then return intervalCount+ ' jours' + when 'week' then return intervalCount+ ' semaines' + when 'month' then return intervalCount+ ' mois' + when 'year' then return intervalCount+ ' ans' +] + +Application.Filters.filter "humanReadablePlanName", ['$filter', ($filter)-> + (plan, groups, short) -> + if plan? + result = plan.base_name + if groups? + for group in groups + if group.id == plan.group_id + if short? + result += " - #{group.slug}" + else + result += " - #{group.name}" + result += " - #{$filter('planIntervalFilter')(plan.interval, plan.interval_count)}" + result +] + +Application.Filters.filter "trainingReservationsFilter", [ -> + (elements, selectedScope) -> + if !angular.isUndefined(elements) and !angular.isUndefined(selectedScope) and elements? and selectedScope? + filteredElements = [] + angular.forEach elements, (element)-> + switch selectedScope + when "future" + if new Date(element.start_at) > new Date + filteredElements.push(element) + when "passed" + if new Date(element.start_at) <= new Date and !element.is_valid + filteredElements.push(element) + when "valided" + if new Date(element.start_at) <= new Date and element.is_valid + filteredElements.push(element) + else + return [] + filteredElements + else + elements +] + +Application.Filters.filter "eventsReservationsFilter", [ -> (elements, selectedScope) -> if !angular.isUndefined(elements) and !angular.isUndefined(selectedScope) and elements? and selectedScope? and selectedScope != "" filteredElements = [] @@ -117,3 +189,43 @@ Application.Controllers.filter "eventsFilter", [ -> else elements ] + +Application.Filters.filter "groupFilter", [ -> + (elements, member) -> + if !angular.isUndefined(elements) and !angular.isUndefined(member) and elements? and member? + filteredElements = [] + angular.forEach elements, (element)-> + if member.group_id == element.id + filteredElements.push(element) + filteredElements + else + elements +] + +Application.Filters.filter "groupByFilter", [ -> + _.memoize (elements, field)-> + _.groupBy(elements, field) +] + +Application.Filters.filter "capitalize", [-> + (text)-> + "#{text.charAt(0).toUpperCase()}#{text.slice(1).toLowerCase()}" +] + + +Application.Filters.filter 'reverse', [ -> + (items) -> + unless angular.isArray(items) + return items + + items.slice().reverse() +] + +Application.Filters.filter 'toArray', [ -> + (obj) -> + return obj unless (obj instanceof Object) + _.map obj, (val, key) -> + if angular.isObject(val) + Object.defineProperty(val, '$key', {__proto__: null, value: key}) + +] \ No newline at end of file diff --git a/app/assets/javascripts/router.coffee.erb b/app/assets/javascripts/router.coffee.erb index 96ec7a69b..d9b97fc97 100644 --- a/app/assets/javascripts/router.coffee.erb +++ b/app/assets/javascripts/router.coffee.erb @@ -1,222 +1,844 @@ angular.module('application.router', ['ui.router']). - config ['$stateProvider', '$urlRouterProvider', '$locationProvider', ($stateProvider, $urlRouterProvider, $locationProvider) -> - $locationProvider.hashPrefix('!') - $urlRouterProvider.otherwise("/") + config ['$stateProvider', '$urlRouterProvider', '$locationProvider', ($stateProvider, $urlRouterProvider, $locationProvider) -> + $locationProvider.hashPrefix('!') + $urlRouterProvider.otherwise("/") - # abstract root parents states - # these states controls the access rights to the various routes inherited from them - $stateProvider - .state 'app', - abstract: true - views: - 'header': { templateUrl: '<%= asset_path "shared/header.html" %>' } - 'leftnav': - templateUrl: '<%= asset_path "shared/leftnav.html" %>' - controller: 'mainNavController' - 'main': - templateUrl: '<%= asset_path "home.html" %>' - controller: 'homeController' - .state 'app.public', - abstract: true - .state 'app.logged', - abstract: true - data: - authorizedRoles: ['member', 'admin'] - resolve: - currentUser: ['Auth', (Auth)-> - Auth.currentUser() - ] - onEnter: ["currentUser", "$rootScope", (currentUser, $rootScope)-> - $rootScope.currentUser = currentUser - ] - .state 'app.admin', - abstract: true - data: - authorizedRoles: ['admin'] - resolve: - currentUser: ['Auth', (Auth)-> - Auth.currentUser() - ] - onEnter: ["currentUser", "$rootScope", (currentUser, $rootScope)-> - $rootScope.currentUser = currentUser - ] + # abstract root parents states + # these states controls the access rights to the various routes inherited from them + $stateProvider + .state 'app', + abstract: true + views: + 'header': + templateUrl: '<%= asset_path "shared/header.html" %>' + 'leftnav': + templateUrl: '<%= asset_path "shared/leftnav.html" %>' + controller: 'MainNavController' + 'main': {} + resolve: + logoFile: ['CustomAsset', (CustomAsset) -> + CustomAsset.get({name: 'logo-file'}).$promise + ] + logoBlackFile: ['CustomAsset', (CustomAsset) -> + CustomAsset.get({name: 'logo-black-file'}).$promise + ] + commonTranslations: [ 'Translations', (Translations) -> + Translations.query(['app.public.common', 'app.shared.buttons', 'app.shared.elements']).$promise + ] + onEnter: ['$rootScope', 'logoFile', 'logoBlackFile', ($rootScope, logoFile, logoBlackFile) -> + ## Application logo + $rootScope.logo = logoFile.custom_asset + $rootScope.logoBlack = logoBlackFile.custom_asset + ] + .state 'app.public', + abstract: true + .state 'app.logged', + abstract: true + data: + authorizedRoles: ['member', 'admin'] + resolve: + currentUser: ['Auth', (Auth)-> + Auth.currentUser() + ] + onEnter: ["$state", "$timeout", "currentUser", "$rootScope", ($state, $timeout, currentUser, $rootScope)-> + $rootScope.currentUser = currentUser + ] + .state 'app.admin', + abstract: true + data: + authorizedRoles: ['admin'] + resolve: + currentUser: ['Auth', (Auth)-> + Auth.currentUser() + ] + onEnter: ["$state", "$timeout", "currentUser", "$rootScope", ($state, $timeout, currentUser, $rootScope)-> + $rootScope.currentUser = currentUser + ] - # main pages - .state 'app.public.about', - url: '/about' - views: - 'content@': { templateUrl: '<%= asset_path "shared/about.html" %>' } - .state 'app.public.home', - url: '/?reset_password_token' - views: - 'main': - templateUrl: '<%= asset_path "home.html" %>' - controller: 'homeController' + # main pages + .state 'app.public.about', + url: '/about' + views: + 'content@': + templateUrl: '<%= asset_path "shared/about.html" %>' + controller: 'AboutController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query('app.public.about').$promise + ] + .state 'app.public.home', + url: '/?reset_password_token' + views: + 'main@': + templateUrl: '<%= asset_path "home.html" %>' + controller: 'HomeController' + resolve: + lastMembersPromise: ['Member', (Member)-> + Member.lastSubscribed(limit: 4).$promise + ] + lastProjectsPromise: ['Project', (Project)-> + Project.lastPublished().$promise + ] + upcomingEventsPromise: ['Event', (Event)-> + Event.upcoming(limit: 3).$promise + ] + homeBlogpostPromise: ['Setting', (Setting)-> + Setting.get(name: 'home_blogpost').$promise + ] + twitterNamePromise: ['Setting', (Setting)-> + Setting.get(name: 'twitter_name').$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.public.home').$promise + ] - - # dashboard - .state 'app.logged.dashboard_profile', - url: '/dashboard/profile' - views: - 'main@': - templateUrl: '<%= asset_path "dashboard/profile.html" %>' - controller: 'editProfileController' - .state 'app.logged.dashboard_projects', - url: '/dashboard/projects' - views: - 'main@': - templateUrl: '<%= asset_path "dashboard/projects.html" %>' - controller: 'dashboardProjectsController' - - - # members - .state 'app.logged.members_show', - url: '/members/:id' - views: - 'main@': - templateUrl: '<%= asset_path "members/show.html" %>' - controller: 'showProfileController' - .state 'app.logged.members', - url: '/members' - views: - 'main@': - templateUrl: '<%= asset_path "members/index.html" %>' - controller: 'membersController' - - - # projects - .state 'app.public.projects_list', - url: '/projects' - views: - 'main@': - templateUrl: '<%= asset_path "projects/index.html" %>' - controller: 'projectsController' - .state 'app.public.projects_show', - url: '/projects/:id' - views: - 'main@': - templateUrl: '<%= asset_path "projects/show.html" %>' - controller: 'showProjectController' - .state 'app.logged.projects_new', - url: '/projects/new' - views: - 'main@': - templateUrl: '<%= asset_path "projects/new.html" %>' - controller: 'newProjectController' - .state 'app.logged.projects_edit', - url: '/projects/:id/edit' - views: - 'main@': - templateUrl: '<%= asset_path "projects/edit.html" %>' - controller: 'editProjectController' + # profile completion (SSO import passage point) + .state 'app.logged.profileCompletion', + url: '/profile_completion' + views: + 'main@': + templateUrl: '<%= asset_path "profile/complete.html"%>' + controller: 'CompleteProfileController' + resolve: + settingsPromise: ['Setting', (Setting)-> + Setting.query(names: "['fablab_name', 'name_genre']").$promise + ] + activeProviderPromise: ['AuthProvider', (AuthProvider) -> + AuthProvider.active().$promise + ] + groupsPromise: ['Group', (Group)-> + Group.query().$promise + ] + cguFile: ['CustomAsset', (CustomAsset) -> + CustomAsset.get({name: 'cgu-file'}).$promise + ] + memberPromise: ['Member', 'currentUser', (Member, currentUser)-> + Member.get(id: currentUser.id).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.logged.profileCompletion', 'app.shared.user']).$promise + ] - # machines - .state 'app.public.machines_list', - url: '/machines' - views: - 'main@': - templateUrl: '<%= asset_path "machines/index.html" %>' - controller: 'machinesController' - .state 'app.public.machines_show', - url: '/machines/:id' - views: - 'main@': - templateUrl: '<%= asset_path "machines/show.html" %>' - controller: 'showMachineController' - .state 'app.admin.machines_new', - url: '/machines/new' - views: - 'main@': - templateUrl: '<%= asset_path "machines/new.html" %>' - controller: 'newMachineController' - .state 'app.admin.machines_edit', - url: '/machines/:id/edit' - views: - 'main@': - templateUrl: '<%= asset_path "machines/edit.html" %>' - controller: 'editMachineController' + # dashboard + .state 'app.logged.dashboard', + abstract: true + url: '/dashboard' + resolve: + memberPromise: ['Member', 'currentUser', (Member, currentUser)-> + Member.get(id: currentUser.id).$promise + ] + .state 'app.logged.dashboard.profile', + url: '/profile' + views: + 'main@': + templateUrl: '<%= asset_path "dashboard/profile.html" %>' + controller: 'EditProfileController' + resolve: + groups: ['Group', (Group)-> + Group.query().$promise + ] + activeProviderPromise: ['AuthProvider', (AuthProvider) -> + AuthProvider.active().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.logged.dashboard.profile', 'app.shared.user']).$promise + ] + .state 'app.logged.dashboard.projects', + url: '/projects' + views: + 'main@': + templateUrl: '<%= asset_path "dashboard/projects.html" %>' + controller: 'DashboardController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query('app.logged.dashboard.projects').$promise + ] + .state 'app.logged.dashboard.trainings', + url: '/trainings' + views: + 'main@': + templateUrl: '<%= asset_path "dashboard/trainings.html" %>' + controller: 'DashboardController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query('app.logged.dashboard.trainings').$promise + ] + .state 'app.logged.dashboard.events', + url: '/events' + views: + 'main@': + templateUrl: '<%= asset_path "dashboard/events.html" %>' + controller: 'DashboardController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query('app.logged.dashboard.events').$promise + ] + .state 'app.logged.dashboard.invoices', + url: '/invoices' + views: + 'main@': + templateUrl: '<%= asset_path "dashboard/invoices.html" %>' + controller: 'DashboardController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query('app.logged.dashboard.invoices').$promise + ] - # notifications - .state 'app.logged.notifications', - url: '/notifications' - views: - 'main@': - templateUrl: '<%= asset_path "notifications/index.html" %>' - controller: 'notificationsController' + # members + .state 'app.logged.members_show', + url: '/members/:id' + views: + 'main@': + templateUrl: '<%= asset_path "members/show.html" %>' + controller: 'ShowProfileController' + resolve: + memberPromise: ['$stateParams', 'Member', ($stateParams, Member)-> + Member.get(id: $stateParams.id).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.logged.members_show').$promise + ] + .state 'app.logged.members', + url: '/members' + views: + 'main@': + templateUrl: '<%= asset_path "members/index.html" %>' + controller: 'MembersController' + resolve: + membersPromise: ['Member', (Member)-> + Member.query({requested_attributes:'[profile]'}).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.logged.members').$promise + ] + + # projects + .state 'app.public.projects_list', + url: '/projects' + views: + 'main@': + templateUrl: '<%= asset_path "projects/index.html" %>' + controller: 'ProjectsController' + resolve: + themesPromise: ['Theme', (Theme)-> + Theme.query().$promise + ] + componentsPromise: ['Component', (Component)-> + Component.query().$promise + ] + machinesPromise: ['Machine', (Machine)-> + Machine.query().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.public.projects_list').$promise + ] + .state 'app.logged.projects_new', + url: '/projects/new' + views: + 'main@': + templateUrl: '<%= asset_path "projects/new.html" %>' + controller: 'NewProjectController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query(['app.logged.projects_new', 'app.shared.project']).$promise + ] + .state 'app.public.projects_show', + url: '/projects/:id' + views: + 'main@': + templateUrl: '<%= asset_path "projects/show.html" %>' + controller: 'ShowProjectController' + resolve: + projectPromise: ['$stateParams', 'Project', ($stateParams, Project)-> + Project.get(id: $stateParams.id).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.public.projects_show').$promise + ] + .state 'app.logged.projects_edit', + url: '/projects/:id/edit' + views: + 'main@': + templateUrl: '<%= asset_path "projects/edit.html" %>' + controller: 'EditProjectController' + resolve: + projectPromise: ['$stateParams', 'Project', ($stateParams, Project)-> + Project.get(id: $stateParams.id).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.logged.projects_edit', 'app.shared.project']).$promise + ] - # events - .state 'app.public.events_list', - url: '/events' - views: - 'main@': - templateUrl: '<%= asset_path "events/index.html" %>' - controller: 'eventsController' - .state 'app.public.events_show', - url: '/events/:id' - views: - 'main@': - templateUrl: '<%= asset_path "events/show.html" %>' - controller: 'showEventController' + # machines + .state 'app.public.machines_list', + url: '/machines' + views: + 'main@': + templateUrl: '<%= asset_path "machines/index.html" %>' + controller: 'MachinesController' + resolve: + machinesPromise: ['Machine', (Machine)-> + Machine.query().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.public.machines_list', 'app.shared.training_reservation_modal', 'app.shared.request_training_modal']).$promise + ] + .state 'app.admin.machines_new', + url: '/machines/new' + views: + 'main@': + templateUrl: '<%= asset_path "machines/new.html" %>' + controller: 'NewMachineController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query(['app.admin.machines_new', 'app.shared.machine']).$promise + ] + .state 'app.public.machines_show', + url: '/machines/:id' + views: + 'main@': + templateUrl: '<%= asset_path "machines/show.html" %>' + controller: 'ShowMachineController' + resolve: + machinePromise: ['Machine', '$stateParams', (Machine, $stateParams)-> + Machine.get(id: $stateParams.id).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.public.machines_show', 'app.shared.training_reservation_modal', 'app.shared.request_training_modal']).$promise + ] + .state 'app.logged.machines_reserve', + url: '/machines/:id/reserve' + views: + 'main@': + templateUrl: '<%= asset_path "machines/reserve.html" %>' + controller: 'ReserveMachineController' + resolve: + plansPromise: ['Plan', (Plan)-> + Plan.query(attributes_requested: "['machines_credits']").$promise + ] + groupsPromise: ['Group', (Group)-> + Group.query().$promise + ] + settingsPromise: ['Setting', (Setting)-> + Setting.query(names: "['machine_explications_alert', + 'booking_window_start', + 'booking_window_end', + 'booking_move_enable', + 'booking_move_delay', + 'booking_cancel_enable', + 'booking_cancel_delay', + 'subscription_explications_alert']").$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.logged.machines_reserve', 'app.shared.plan_subscribe', 'app.shared.member_select', + 'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.confirm_modify_slot_modal']).$promise + ] + .state 'app.admin.machines_edit', + url: '/machines/:id/edit' + views: + 'main@': + templateUrl: '<%= asset_path "machines/edit.html" %>' + controller: 'EditMachineController' + resolve: + machinePromise: ['Machine', '$stateParams', (Machine, $stateParams)-> + Machine.get(id: $stateParams.id).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.admin.machines_edit', 'app.shared.machine']).$promise + ] + + # trainings + .state 'app.logged.trainings_reserve', + url: '/trainings/reserve' + views: + 'main@': + templateUrl: '<%= asset_path "trainings/reserve.html" %>' + controller: 'ReserveTrainingController' + resolve: + explicationAlertPromise: ['Setting', (Setting)-> + Setting.get(name: 'training_explications_alert').$promise + ] + plansPromise: ['Plan', (Plan)-> + Plan.query(attributes_requested: "['trainings_credits']").$promise + ] + groupsPromise: ['Group', (Group)-> + Group.query().$promise + ] + availabilityTrainingsPromise: ['Availability', (Availability)-> + Availability.trainings().$promise + ] + settingsPromise: ['Setting', (Setting)-> + Setting.query(names: "['booking_window_start', + 'booking_window_end', + 'booking_move_enable', + 'booking_move_delay', + 'booking_cancel_enable', + 'booking_cancel_delay', + 'subscription_explications_alert', + 'training_explications_alert', + 'training_information_message']").$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.logged.trainings_reserve', 'app.shared.plan_subscribe', 'app.shared.member_select', + 'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.confirm_modify_slot_modal']).$promise + ] + # notifications + .state 'app.logged.notifications', + url: '/notifications' + views: + 'main@': + templateUrl: '<%= asset_path "notifications/index.html" %>' + controller: 'NotificationsController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query('app.logged.notifications').$promise + ] + + # pricing + .state 'app.public.plans', + url: '/plans' + abstract: Fablab.withoutPlans + views: + 'main@': + templateUrl: '<%= asset_path "plans/index.html" %>' + controller: 'PlansIndexController' + resolve: + subscriptionExplicationsPromise: ['Setting', (Setting)-> + Setting.get(name: 'subscription_explications_alert').$promise + ] + plansPromise: ['Plan', (Plan)-> + Plan.query(shallow: true).$promise + ] + groupsPromise: ['Group', (Group)-> + Group.query().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.public.plans', 'app.shared.member_select', 'app.shared.stripe']).$promise + ] + + # events + .state 'app.public.events_list', + url: '/events' + views: + 'main@': + templateUrl: '<%= asset_path "events/index.html" %>' + controller: 'EventsController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query('app.public.events_list').$promise + ] + .state 'app.public.events_show', + url: '/events/:id' + views: + 'main@': + templateUrl: '<%= asset_path "events/show.html" %>' + controller: 'ShowEventController' + resolve: + eventPromise: ['Event', '$stateParams', (Event, $stateParams)-> + Event.get(id: $stateParams.id).$promise + ] + reducedAmountAlert: ['Setting', (Setting)-> + Setting.get(name: 'event_reduced_amount_alert').$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.public.events_show', 'app.shared.member_select', 'app.shared.stripe', 'app.shared.valid_reservation_modal']).$promise + ] + + # --- namespace /admin/... --- + # calendar + .state 'app.admin.calendar', + url: '/admin/calendar' + views: + 'main@': + templateUrl: '<%= asset_path "admin/calendar/calendar.html" %>' + controller: 'AdminCalendarController' + resolve: + availabilitiesPromise: ['Availability', (Availability)-> + Availability.query().$promise + ] + bookingWindowStart: ['Setting', (Setting)-> + Setting.get(name: 'booking_window_start').$promise + ] + bookingWindowEnd: ['Setting', (Setting)-> + Setting.get(name: 'booking_window_end').$promise + ] + machinesPromise: ['Machine', (Machine) -> + Machine.query().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.calendar').$promise + ] + + # project's elements + .state 'app.admin.project_elements', + url: '/admin/project_elements' + views: + 'main@': + templateUrl: '<%= asset_path "admin/project_elements/index.html" %>' + controller: 'ProjectElementsController' + resolve: + componentsPromise: ['Component', (Component)-> + Component.query().$promise + ] + licencesPromise: ['Licence', (Licence)-> + Licence.query().$promise + ] + themesPromise: ['Theme', (Theme)-> + Theme.query().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.project_elements').$promise + ] + + # trainings + .state 'app.admin.trainings', + url: '/admin/trainings' + views: + 'main@': + templateUrl: '<%= asset_path "admin/trainings/index.html" %>' + controller: 'TrainingsController' + resolve: + trainingsPromise: ['Training', (Training)-> + Training.query().$promise + ] + machinesPromise: ['Machine', (Machine)-> + Machine.query().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.trainings').$promise + ] + + # events + .state 'app.admin.events', + url: '/admin/events' + views: + 'main@': + templateUrl: '<%= asset_path "admin/events/index.html" %>' + controller: 'AdminEventsController' + resolve: + eventsPromise: ['Event', (Event)-> + Event.query(page: 1).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.events').$promise + ] + .state 'app.admin.events_new', + url: '/admin/events/new' + views: + 'main@': + templateUrl: '<%= asset_path "events/new.html" %>' + controller: 'NewEventController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query(['app.admin.events_new', 'app.shared.event']).$promise + ] + .state 'app.admin.events_edit', + url: '/admin/events/:id/edit' + views: + 'main@': + templateUrl: '<%= asset_path "events/edit.html" %>' + controller: 'EditEventController' + resolve: + eventPromise: ['Event', '$stateParams', (Event, $stateParams)-> + Event.get(id: $stateParams.id).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.admin.events_edit', 'app.shared.event']).$promise + ] + .state 'app.admin.event_reservations', + url: '/admin/events/:id/reservations' + views: + 'main@': + templateUrl: '<%= asset_path "admin/events/reservations.html" %>' + controller: 'ShowEventReservationsController' + resolve: + eventPromise: ['Event', '$stateParams', (Event, $stateParams)-> + Event.get(id: $stateParams.id).$promise + ] + reservationsPromise: ['Reservation', '$stateParams', (Reservation, $stateParams)-> + Reservation.query(reservable_id: $stateParams.id, reservable_type: 'Event').$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.event_reservations').$promise + ] + + # pricing + .state 'app.admin.pricing', + url: '/admin/pricing' + views: + 'main@': + templateUrl: '<%= asset_path "admin/pricing/index.html" %>' + controller: 'EditPricingController' + resolve: + plans: ['Plan', (Plan) -> + Plan.query().$promise + ] + groups: ['Group', (Group) -> + Group.query().$promise + ] + machinesPricesPromise: ['Price', (Price)-> + Price.query(priceable_type: 'Machine', plan_id: 'null').$promise + ] + trainingsPricingsPromise: ['TrainingsPricing', (TrainingsPricing)-> + TrainingsPricing.query().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.pricing').$promise + ] + + # plans + .state 'app.admin.plans', + abstract: true + resolve: + prices: ['Pricing', (Pricing) -> + Pricing.query().$promise + ] + machines: ['Machine', (Machine) -> + Machine.query().$promise + ] + groups: ['Group', (Group) -> + Group.query().$promise + ] + plans: ['Plan', (Plan) -> + Plan.query().$promise + ] + partners: ['User', (User) -> + User.query({role: 'partner'}).$promise + ] + .state 'app.admin.plans.new', + url: '/admin/plans/new' + views: + 'main@': + templateUrl: '<%= asset_path "admin/plans/new.html" %>' + controller: 'NewPlanController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query(['app.admin.plans.new', 'app.shared.plan']).$promise + ] + .state 'app.admin.plans.edit', + url: '/admin/plans/:id/edit' + views: + 'main@': + templateUrl: '<%= asset_path "admin/plans/edit.html" %>' + controller: 'EditPlanController' + resolve: + planPromise: ['Plan', '$stateParams', (Plan, $stateParams) -> + Plan.get({id: $stateParams.id}).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.admin.plans.edit', 'app.shared.plan']).$promise + ] - # --- namespace /admin/... --- - # project's elements - .state 'app.admin.project_elements', - url: '/admin/project_elements' - views: - 'main@': - templateUrl: '<%= asset_path "admin/project_elements/index.html" %>' - controller: 'projectElementsController' + # invoices + .state 'app.admin.invoices', + url: '/admin/invoices' + views: + 'main@': + templateUrl: '<%= asset_path "admin/invoices/index.html" %>' + controller: 'InvoicesController' + resolve: + settings: ['Setting', (Setting)-> + Setting.query(names: "[ + 'invoice_legals', + 'invoice_text', + 'invoice_VAT-rate', + 'invoice_VAT-active', + 'invoice_order-nb', + 'invoice_code-value', + 'invoice_code-active', + 'invoice_reference', + 'invoice_logo' + ]").$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.invoices').$promise + ] - # events - .state 'app.admin.events', - url: '/admin/events' - views: - 'main@': - templateUrl: '<%= asset_path "admin/events/index.html" %>' - controller: 'adminEventsController' - .state 'app.admin.events_new', - url: '/admin/events/new' - views: - 'main@': - templateUrl: '<%= asset_path "events/new.html" %>' - controller: 'newEventController' - .state 'app.admin.events_edit', - url: '/admin/events/:id/edit' - views: - 'main@': - templateUrl: '<%= asset_path "events/edit.html" %>' - controller: 'editEventController' + # members + .state 'app.admin.members', + url: '/admin/members' + views: + 'main@': + templateUrl: '<%= asset_path "admin/members/index.html" %>' + controller: 'AdminMembersController' + 'groups@app.admin.members': + templateUrl: '<%= asset_path "admin/groups/index.html" %>' + controller: 'GroupsController' + 'tags@app.admin.members': + templateUrl: '<%= asset_path "admin/tags/index.html" %>' + controller: 'TagsController' + 'authentification@app.admin.members': + templateUrl: '<%= asset_path "admin/authentications/index.html" %>' + controller: 'AuthentificationController' + resolve: + membersPromise: ['Member', (Member)-> + Member.query({requested_attributes:'[profile,group,subscription]'}).$promise + ] + adminsPromise: ['Admin', (Admin)-> + Admin.query().$promise + ] + groupsPromise: ['Group', (Group)-> + Group.query().$promise + ] + tagsPromise: ['Tag', (Tag)-> + Tag.query().$promise + ] + authProvidersPromise: ['AuthProvider', (AuthProvider)-> + AuthProvider.query().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.members').$promise + ] + .state 'app.admin.members_new', + url: '/admin/members/new' + views: + 'main@': + templateUrl: '<%= asset_path "admin/members/new.html" %>' + controller: 'NewMemberController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query(['app.admin.members_new', 'app.shared.user', 'app.shared.user_admin']).$promise + ] + .state 'app.admin.members_edit', + url: '/admin/members/:id/edit' + views: + 'main@': + templateUrl: '<%= asset_path "admin/members/edit.html" %>' + controller: 'EditMemberController' + resolve: + memberPromise: ['Member', '$stateParams', (Member, $stateParams)-> + Member.get(id: $stateParams.id).$promise + ] + tagsPromise: ['Tag', (Tag)-> + Tag.query().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.admin.members_edit', 'app.shared.user', 'app.shared.user_admin']).$promise + ] + .state 'app.admin.admins_new', + url: '/admin/admins/new' + views: + 'main@': + templateUrl: '<%= asset_path "admin/admins/new.html" %>' + controller: 'NewAdminController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.admins_new').$promise + ] - # members - .state 'app.admin.members', - url: '/admin/members' - views: - 'main@': - templateUrl: '<%= asset_path "admin/members/index.html" %>' - controller: 'membersController' - .state 'app.admin.members_new', - url: '/admin/members/new' - views: - 'main@': - templateUrl: '<%= asset_path "admin/members/new.html" %>' - controller: 'newMemberController' - .state 'app.admin.members_edit', - url: '/admin/members/:id/edit' - views: - 'main@': - templateUrl: '<%= asset_path "admin/members/edit.html" %>' - controller: 'editMemberController' + # authentification providers + .state 'app.admin.authentication_new', + url: '/admin/authentications/new' + views: + 'main@': + templateUrl: '<%= asset_path "admin/authentications/new.html" %>' + controller: 'NewAuthenticationController' + resolve: + mappingFieldsPromise: ['AuthProvider', (AuthProvider)-> + AuthProvider.mapping_fields().$promise + ] + authProvidersPromise: ['AuthProvider', (AuthProvider)-> + AuthProvider.query().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.admin.authentication_new', 'app.shared.authentication', 'app.shared.oauth2']).$promise + ] + .state 'app.admin.authentication_edit', + url: '/admin/authentications/:id/edit' + views: + 'main@': + templateUrl: '<%= asset_path "admin/authentications/edit.html" %>' + controller: 'EditAuthenticationController' + resolve: + providerPromise: ['AuthProvider', '$stateParams', (AuthProvider, $stateParams)-> + AuthProvider.get(id: $stateParams.id).$promise + ] + mappingFieldsPromise: ['AuthProvider', (AuthProvider)-> + AuthProvider.mapping_fields().$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query(['app.admin.authentication_edit', 'app.shared.authentication', 'app.shared.oauth2']).$promise + ] + + + + # statistics + .state 'app.admin.statistics', + url: '/admin/statistics' + views: + 'main@': + templateUrl: '<%= asset_path "admin/statistics/index.html" %>' + controller: 'StatisticsController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.statistics').$promise + ] + .state 'app.admin.stats_graphs', + url: '/admin/statistics/evolution' + views: + 'main@': + templateUrl: '<%= asset_path "admin/statistics/graphs.html" %>' + controller: 'GraphsController' + resolve: + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.stats_graphs').$promise + ] + + # configurations + .state 'app.admin.settings', + url: '/admin/settings' + views: + 'main@': + templateUrl: '<%= asset_path "admin/settings/index.html" %>' + controller: 'SettingsController' + resolve: + settingsPromise: ['Setting', (Setting)-> + Setting.query(names: "[ + 'twitter_name', + 'about_title', + 'about_body', + 'about_contacts', + 'home_blogpost', + 'machine_explications_alert', + 'training_explications_alert', + 'training_information_message', + 'subscription_explications_alert', + 'event_reduced_amount_alert', + 'booking_window_start', + 'booking_window_end', + 'booking_move_enable', + 'booking_move_delay', + 'booking_cancel_enable', + 'booking_cancel_delay', + 'main_color', + 'secondary_color', + 'fablab_name', + 'name_genre' + ]").$promise + ] + cguFile: ['CustomAsset', (CustomAsset) -> + CustomAsset.get({name: 'cgu-file'}).$promise + ] + cgvFile: ['CustomAsset', (CustomAsset) -> + CustomAsset.get({name: 'cgv-file'}).$promise + ] + faviconFile: ['CustomAsset', (CustomAsset) -> + CustomAsset.get({name: 'favicon-file'}).$promise + ] + translations: [ 'Translations', (Translations) -> + Translations.query('app.admin.settings').$promise + ] ] diff --git a/app/assets/javascripts/services/_t.coffee b/app/assets/javascripts/services/_t.coffee new file mode 100644 index 000000000..b282aa427 --- /dev/null +++ b/app/assets/javascripts/services/_t.coffee @@ -0,0 +1,6 @@ +'use strict' + +Application.Services.factory '_t', ["$filter", ($filter)-> + (key, interpolation = undefined, options = undefined) -> + $filter('translate')(key, interpolation, options) +] diff --git a/app/assets/javascripts/services/abuse.coffee b/app/assets/javascripts/services/abuse.coffee new file mode 100644 index 000000000..9445c6f14 --- /dev/null +++ b/app/assets/javascripts/services/abuse.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'Abuse', ["$resource", ($resource)-> + $resource "/api/abuses/:id", + {id: "@id"}, + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/admin.coffee b/app/assets/javascripts/services/admin.coffee new file mode 100644 index 000000000..ce46ee2c5 --- /dev/null +++ b/app/assets/javascripts/services/admin.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'Admin', ["$resource", ($resource)-> + $resource "/api/admins/:id", + {id: "@id"}, + query: + isArray: false +] diff --git a/app/assets/javascripts/services/authProvider.coffee b/app/assets/javascripts/services/authProvider.coffee new file mode 100644 index 000000000..4acb0b731 --- /dev/null +++ b/app/assets/javascripts/services/authProvider.coffee @@ -0,0 +1,14 @@ +'use strict' + +Application.Services.factory 'AuthProvider', ["$resource", ($resource)-> + $resource "/api/auth_providers/:id", + {id: "@id"}, + update: + method: 'PUT' + mapping_fields: + method: 'GET' + url: '/api/auth_providers/mapping_fields' + active: + method: 'GET' + url: '/api/auth_providers/active' +] diff --git a/app/assets/javascripts/services/availability.coffee b/app/assets/javascripts/services/availability.coffee new file mode 100644 index 000000000..c96ca8e7b --- /dev/null +++ b/app/assets/javascripts/services/availability.coffee @@ -0,0 +1,21 @@ +'use strict' + +Application.Services.factory 'Availability', ["$resource", ($resource)-> + $resource "/api/availabilities/:id", + {id: "@id"}, + machine: + method: 'GET' + url: '/api/availabilities/machines/:machineId' + params: {machineId: "@machineId"} + isArray: true + reservations: + method: 'GET' + url: '/api/availabilities/:id/reservations' + isArray: true + trainings: + method: 'GET' + url: '/api/availabilities/trainings' + isArray: true + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/credit.coffee b/app/assets/javascripts/services/credit.coffee new file mode 100644 index 000000000..9e1aae2a6 --- /dev/null +++ b/app/assets/javascripts/services/credit.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'Credit', ["$resource", ($resource)-> + $resource "/api/credits/:id", + {id: "@id"}, + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/customAsset.coffee b/app/assets/javascripts/services/customAsset.coffee new file mode 100644 index 000000000..65c8e64fc --- /dev/null +++ b/app/assets/javascripts/services/customAsset.coffee @@ -0,0 +1,6 @@ +'use strict' + +Application.Services.factory 'CustomAsset', ["$resource", ($resource)-> + $resource "/api/custom_assets/:name", + {name: "@name"} +] diff --git a/app/assets/javascripts/services/dialogs.coffee.erb b/app/assets/javascripts/services/dialogs.coffee.erb index d10983921..0422624be 100644 --- a/app/assets/javascripts/services/dialogs.coffee.erb +++ b/app/assets/javascripts/services/dialogs.coffee.erb @@ -1,6 +1,6 @@ 'use strict' -Application.Services.factory 'dialogs', ["$modal", ($modal) -> +Application.Services.factory 'dialogs', ["$uibModal", ($uibModal) -> confirm: (options, success, error)-> defaultOpts = templateUrl: '<%= asset_path "shared/confirm_modal.html" %>' @@ -8,20 +8,20 @@ Application.Services.factory 'dialogs', ["$modal", ($modal) -> resolve: object: -> title: 'Titre de confirmation' - msg: 'Message de confiramtion' - controller: ['$scope', '$modalInstance', '$state', 'object', ($scope, $modalInstance, $state, object) -> + msg: 'Message de confirmation' + controller: ['$scope', '$uibModalInstance', '$state', 'object', ($scope, $uibModalInstance, $state, object) -> $scope.object = object - $scope.ok = -> - $modalInstance.close() + $scope.ok = (info) -> + $uibModalInstance.close( info ) $scope.cancel = -> - $modalInstance.dismiss('cancel') + $uibModalInstance.dismiss('cancel') ] angular.extend(defaultOpts, options) if angular.isObject options - $modal.open defaultOpts - .result['finally'](null).then -> + $uibModal.open defaultOpts + .result['finally'](null).then (info)-> if angular.isFunction(success) - success() - , -> + success(info) + , (reason)-> if angular.isFunction(error) - error() + error(reason) ] diff --git a/app/assets/javascripts/services/elastic.js.erb b/app/assets/javascripts/services/elastic.js.erb new file mode 100644 index 000000000..7bed59acd --- /dev/null +++ b/app/assets/javascripts/services/elastic.js.erb @@ -0,0 +1,3 @@ +Application.Services.service('es', function (esFactory) { + return esFactory({ host: window.location.origin }); +}); diff --git a/app/assets/javascripts/services/group.coffee b/app/assets/javascripts/services/group.coffee index a4192f7d6..c444531e3 100644 --- a/app/assets/javascripts/services/group.coffee +++ b/app/assets/javascripts/services/group.coffee @@ -2,5 +2,7 @@ Application.Services.factory 'Group', ["$resource", ($resource)-> $resource "/api/groups/:id", - {id: "@id"} + {id: "@id"}, + update: + method: 'PUT' ] diff --git a/app/assets/javascripts/services/invoice.coffee b/app/assets/javascripts/services/invoice.coffee new file mode 100644 index 000000000..d2b3c34ff --- /dev/null +++ b/app/assets/javascripts/services/invoice.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'Invoice', ["$resource", ($resource)-> + $resource "/api/invoices/:id", + {id: "@id"}, + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/member.coffee b/app/assets/javascripts/services/member.coffee index 4d2a7afd3..bd1fbf8dc 100644 --- a/app/assets/javascripts/services/member.coffee +++ b/app/assets/javascripts/services/member.coffee @@ -3,9 +3,14 @@ Application.Services.factory 'Member', ["$resource", ($resource)-> $resource "/api/members/:id", {id: "@id"}, + update: + method: 'PUT' lastSubscribed: method: 'GET' url: '/api/last_subscribed/:limit' params: {limit: "@limit"} isArray: true + merge: + method: 'PUT' + url: '/api/members/:id/merge' ] diff --git a/app/assets/javascripts/services/plan.coffee b/app/assets/javascripts/services/plan.coffee new file mode 100644 index 000000000..d761cf267 --- /dev/null +++ b/app/assets/javascripts/services/plan.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'Plan', ["$resource", ($resource)-> + $resource "/api/plans/:id", + {id: "@id"}, + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/price.coffee b/app/assets/javascripts/services/price.coffee new file mode 100644 index 000000000..66b8270fe --- /dev/null +++ b/app/assets/javascripts/services/price.coffee @@ -0,0 +1,14 @@ +'use strict' + +Application.Services.factory 'Price', ["$resource", ($resource)-> + $resource "/api/prices/:id", + {}, + query: + isArray: false + update: + method: 'PUT' + compute: + method: 'POST' + url: '/api/prices/compute' + isArray: false +] diff --git a/app/assets/javascripts/services/pricing.coffee b/app/assets/javascripts/services/pricing.coffee new file mode 100644 index 000000000..a49cdb732 --- /dev/null +++ b/app/assets/javascripts/services/pricing.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'Pricing', ["$resource", ($resource)-> + $resource "/api/pricing", + {}, + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/project.coffee b/app/assets/javascripts/services/project.coffee index 637f59b53..94471b2a0 100644 --- a/app/assets/javascripts/services/project.coffee +++ b/app/assets/javascripts/services/project.coffee @@ -7,4 +7,8 @@ Application.Services.factory 'Project', ["$resource", ($resource)-> method: 'GET' url: '/api/projects/last_published' isArray: true + search: + method: 'GET' + url: '/api/projects/search' + isArray: true ] diff --git a/app/assets/javascripts/services/reservation.coffee b/app/assets/javascripts/services/reservation.coffee new file mode 100644 index 000000000..8f216950e --- /dev/null +++ b/app/assets/javascripts/services/reservation.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'Reservation', ["$resource", ($resource)-> + $resource "/api/reservations/:id", + {id: "@id"}, + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/setting.coffee b/app/assets/javascripts/services/setting.coffee new file mode 100644 index 000000000..258781124 --- /dev/null +++ b/app/assets/javascripts/services/setting.coffee @@ -0,0 +1,10 @@ +'use strict' + +Application.Services.factory 'Setting', ["$resource", ($resource)-> + $resource "/api/settings/:name", + {name: "@name"}, + update: + method: 'PUT' + query: + isArray: false +] diff --git a/app/assets/javascripts/services/slot.coffee b/app/assets/javascripts/services/slot.coffee new file mode 100644 index 000000000..0050b179c --- /dev/null +++ b/app/assets/javascripts/services/slot.coffee @@ -0,0 +1,11 @@ +'use strict' + +Application.Services.factory 'Slot', ["$resource", ($resource)-> + $resource "/api/slots/:id", + {id: "@id"}, + update: + method: 'PUT' + cancel: + method: 'PUT' + url: '/api/slots/:id/cancel' +] diff --git a/app/assets/javascripts/services/statistics.coffee b/app/assets/javascripts/services/statistics.coffee new file mode 100644 index 000000000..5e1c2969d --- /dev/null +++ b/app/assets/javascripts/services/statistics.coffee @@ -0,0 +1,5 @@ +'use strict' + +Application.Services.factory 'Statistics', ["$resource", ($resource)-> + $resource "/api/statistics" +] diff --git a/app/assets/javascripts/services/subscription.coffee b/app/assets/javascripts/services/subscription.coffee new file mode 100644 index 000000000..5606fefb9 --- /dev/null +++ b/app/assets/javascripts/services/subscription.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'Subscription', ["$resource", ($resource)-> + $resource "/api/subscriptions/:id", + {id: "@id"}, + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/tag.coffee b/app/assets/javascripts/services/tag.coffee new file mode 100644 index 000000000..ab1cf29ba --- /dev/null +++ b/app/assets/javascripts/services/tag.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'Tag', ["$resource", ($resource)-> + $resource "/api/tags/:id", + {id: "@id"}, + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/training.coffee b/app/assets/javascripts/services/training.coffee new file mode 100644 index 000000000..c62f277d0 --- /dev/null +++ b/app/assets/javascripts/services/training.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'Training', ["$resource", ($resource)-> + $resource "/api/trainings/:id", + {id: "@id"}, + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/trainings_pricing.coffee b/app/assets/javascripts/services/trainings_pricing.coffee new file mode 100644 index 000000000..9e75a4165 --- /dev/null +++ b/app/assets/javascripts/services/trainings_pricing.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'TrainingsPricing', ["$resource", ($resource)-> + $resource "/api/trainings_pricings/:id", + {}, + update: + method: 'PUT' +] diff --git a/app/assets/javascripts/services/translations.coffee b/app/assets/javascripts/services/translations.coffee new file mode 100644 index 000000000..68abb76bf --- /dev/null +++ b/app/assets/javascripts/services/translations.coffee @@ -0,0 +1,13 @@ +'use strict' + +Application.Services.factory 'Translations', ["$translatePartialLoader", "$translate", ($translatePartialLoader, $translate)-> + return { + query: (stateName) -> + if angular.isArray (stateName) + angular.forEach stateName, (state) -> + $translatePartialLoader.addPart(state) + else + $translatePartialLoader.addPart(stateName) + $translate.refresh() + } +] diff --git a/app/assets/javascripts/services/user.coffee b/app/assets/javascripts/services/user.coffee new file mode 100644 index 000000000..a93234915 --- /dev/null +++ b/app/assets/javascripts/services/user.coffee @@ -0,0 +1,8 @@ +'use strict' + +Application.Services.factory 'User', ["$resource", ($resource)-> + $resource "/api/users", + {}, + query: + isArray: false +] diff --git a/app/assets/stylesheets/app.base.scss b/app/assets/stylesheets/app.base.scss index 6d415b0c2..4ff669009 100644 --- a/app/assets/stylesheets/app.base.scss +++ b/app/assets/stylesheets/app.base.scss @@ -16,7 +16,7 @@ h1, .page-title { font-weight: 900; } h2 { - color: $red; + //color: $red; line-height: rem-calc(24); font-weight: 900; } @@ -26,7 +26,7 @@ h5 { display: inline-block; position: relative; line-height: rem-calc(18); - color: $red; + //color: $red; font-size: rem-calc(16); &:after { position: absolute; @@ -35,7 +35,7 @@ h5 { content: ''; width: 35%; height: 1px; - background-color: $red; + //background-color: $red; } } @@ -43,12 +43,12 @@ h5 { // ------------------------- a { - color: $link-color; + //color: $link-color; text-decoration: none; } a:hover, a:focus { - color: $link-hover-color; + //color: $link-hover-color; text-decoration: none; } @@ -127,7 +127,8 @@ dd { // transition:0.5s linear all; // } -[ui-view].ng-enter, [ui-view].ng-leave { +// only for main content +#content-main.ng-enter, #content-main.ng-leave { position: absolute; left: 0; right: 0; diff --git a/app/assets/stylesheets/app.buttons.scss b/app/assets/stylesheets/app.buttons.scss index cf6f2299f..49f97dd1c 100644 --- a/app/assets/stylesheets/app.buttons.scss +++ b/app/assets/stylesheets/app.buttons.scss @@ -17,8 +17,8 @@ .btn-warning-full { outline: 0; text-transform: uppercase; - border: 3px solid $yellow; - background-color: $yellow; + //border: 3px solid $yellow; + //background-color: $yellow; &:hover { background-color: white; } @@ -41,4 +41,18 @@ .btn-inactive{ -webkit-box-shadow: none !important; box-shadow: none !important; -} \ No newline at end of file +} + +.btn-loading:after { + margin-left: 1em; + display: inline-block; + content: "\f110"; + font-family: FontAwesome; + -webkit-animation:spin 4s linear infinite; + -moz-animation:spin 4s linear infinite; + animation:spin 4s linear infinite; +} + +@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } } +@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } +@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } } diff --git a/app/assets/stylesheets/app.colors.scss b/app/assets/stylesheets/app.colors.scss index def64aeb0..41a7bcbd5 100644 --- a/app/assets/stylesheets/app.colors.scss +++ b/app/assets/stylesheets/app.colors.scss @@ -1,8 +1,9 @@ .bg-light { background-color: $brand-light; } -.bg-red { background-color: $red; color: white; } -.bg-red-dark { background-color: $red-dark; } -.bg-yellow { background-color: $yellow !important; } +//.bg-red { background-color: $red; color: white; } +//.bg-red-dark { background-color: $red-dark; } +//.bg-yellow { background-color: $yellow !important; } +.bg-token { background-color: rgba(230, 208, 137, 0.49); } .bg-machine { background-color: $beige; } .bg-formation { background-color: $violet; } .bg-atelier { background-color: $blue; } @@ -30,7 +31,7 @@ .text-black-light { color: #424242 !important; } .text-gray { color: #5a5a5a !important; } .text-white { color: #fff !important; } -.text-yellow { color: $yellow !important; } +//.text-yellow { color: $yellow !important; } .text-blue { color: $blue; } .text-muted { color: $text-muted; } .text-danger, .red { color: $red !important; } diff --git a/app/assets/stylesheets/app.components.scss b/app/assets/stylesheets/app.components.scss index aa38f91bf..f4b3b2076 100644 --- a/app/assets/stylesheets/app.components.scss +++ b/app/assets/stylesheets/app.components.scss @@ -7,11 +7,11 @@ font-weight: 600; color: black; } - h1 { - font-size: rem-calc(16); text-transform: uppercase; + h1 { + font-size: rem-calc(16); text-transform: uppercase; } h2 { font-weight: bold; } - h3 { color: $red; } + //h3 { color: $red; } h4 { font-size: rem-calc(12); margin: 8px 0; @@ -50,13 +50,14 @@ left: 0; right: 0; margin: 0 auto; + max-height: 44px; } h1 { margin: 25px 0 20px 0; font-weight: bold; text-transform: uppercase; text-align: center; - color: $red; + //color: $red; } } @@ -129,14 +130,14 @@ } .article-thumbnail { - max-height: 400px; + // max-height: 400px; overflow: hidden; } } .label-staging { position: absolute; - top: 50px; + top: 50px; } .notification-open { @@ -197,6 +198,7 @@ margin: 10px 0; font-size: rem-calc(16); text-transform: uppercase; + color: black; } .content { padding: 15px 0; @@ -206,7 +208,7 @@ display: inline-block; background: white; @include border-radius(50%); - border: 3px solid $yellow; + border: 3px solid;// $yellow; } .price { position: relative; @@ -238,15 +240,15 @@ background-color: white; padding-left: 30px; padding-right: 30px; - &:hover { background-color: $yellow; } + //&:hover { background-color: $yellow; } } } } .well { &.well-warning { - border-color: #ffdc4e; - background-color: #ffdc4e; + //border-color: #ffdc4e; + //background-color: #ffdc4e; @include border-radius(3px); padding: 5px 10px; } @@ -324,10 +326,10 @@ .block-link { cursor: pointer; - &:hover { background-color: $yellow; } + //&:hover { background-color: $yellow; } } -.form-control.form-control-ui-select .select2-choices .select2-search-choice { +.form-control .ui-select-choices, .form-control .ui-select-match { font-size: 85% !important; } @@ -351,16 +353,15 @@ .about-picture { padding: 70px 0; height: 326px; - background: white asset-url("about-fablab.jpg") no-repeat; background-size: cover; margin-bottom: 30px; } - .about-title { + .about-title, .about-title p { margin: 0; font-size: rem-calc(50); line-height: rem-calc(48); color: #fff; - font-weight: 900; //black + font-weight: 900; //black } .about-title-aside { @@ -393,7 +394,7 @@ } .event:hover { -background-color: #cb1117; +//background-color: #cb1117; color: white; } @@ -441,3 +442,25 @@ padding: 10px; } } + +// angular-bootstrap accordions (enlightened version) +.light-accordion > .panel-heading { + padding-top: 0.2em; + padding-bottom: 0.2em; +} +.light-accordion > .panel-heading > .panel-title { + font-size: 12pt; +} + +.app-generator { + position: absolute; + bottom: 0; right: 10px; + z-index: 100; + padding: 3px 15px; + border: 1px solid $border-color; + border-top-left-radius: 8px; + background: $bg-gray; + @media only screen and (max-width: 768px) { + display: none; + } +} diff --git a/app/assets/stylesheets/app.layout.scss b/app/assets/stylesheets/app.layout.scss index ed17d7543..3fe0d7907 100644 --- a/app/assets/stylesheets/app.layout.scss +++ b/app/assets/stylesheets/app.layout.scss @@ -76,11 +76,17 @@ cursor: pointer; color: black; &:hover { - background-color: $yellow; + //background-color: $yellow; } i:before { content: "\f177"; } } } + .heading-icon { + width: 100%; + padding: 35px 40%; + display: inline-block; + color: black; + } .heading-title { overflow: hidden; height: 94px; @@ -341,4 +347,131 @@ body.container{ } } } +} + +.customMenuButton { + min-width: 15em; + max-width: 15em; + overflow-x: hidden; +} + +.customMenuInput { + width:100% !important; +} + + +.reservation-canceled { + color: #606060; + border-radius: 0.2em; + background-color: #e4e4e4; + padding: 0.7em 0.7em; + font-size: 90%; + display:inline-block; + vertical-align:middle; + + .reservation-time { + color: #606060; + } + + &:before { + content: "Annulée"; + display: inline-block; + background-color: #c44242; + border-radius: 0.25em; + padding: 0.1em 0.5em; + font-weight: bold; + color: #fff; + float: left; + margin-right: 1em; + } + +} + +.custom-logo-container { + max-width: 240px; + height: 100%; + + .custom-logo { + height: 100px; + width: 100%; + position: relative; + background-size: cover; + background-repeat: no-repeat; + border: 1px dashed #c4c4c4; + border-radius: 0.7em; + padding: 1.6em; + margin-left: 1em; + + img { + display: block; + width: auto; + max-height: 44px; + max-width: 100%; + margin:auto; + } + + &:hover .tools-box { + opacity: 1; + } + + .tools-box { + opacity: 0; + position: absolute; + bottom: 10px; + left: 0; + right: 0; + text-align: center; + } + } + + .bg-dark { + background-color: #000; + opacity: 0.9; + } +} + +.custom-favicon-container { + max-width: 70px; + height: 100%; + + .custom-favicon { + height: 70px; + width: 100%; + position: relative; + background-size: cover; + background-repeat: no-repeat; + border: 1px dashed #c4c4c4; + border-radius: 0.7em; + padding: 1.6em; + margin-left: 1em; + + img { + display: block; + width: auto; + max-height: 16px; + max-width: 16px; + margin:auto; + } + + &:hover .tools-box { + opacity: 1; + } + + .tools-box { + opacity: 0; + position: absolute; + bottom: -7px; + left: 51px; + right: 0; + text-align: center; + } + } +} + +.flash-message { + position: absolute; + top: 1%; + z-index: 1001; + width: 33%; + left: 33%; } \ No newline at end of file diff --git a/app/assets/stylesheets/app.nav.scss b/app/assets/stylesheets/app.nav.scss index 85ce4d539..097b83f52 100644 --- a/app/assets/stylesheets/app.nav.scss +++ b/app/assets/stylesheets/app.nav.scss @@ -425,7 +425,7 @@ #nav { // border-right: 1px solid $red-dark; .nav { - background-color: $red; + //background-color: $red; > li { > a { padding: 13px 17px; @@ -433,11 +433,11 @@ color: white; &:hover, &:focus, &.active { - background-color: $red-light; + //background-color: $red-light; color: white; } &.active { - border-left: 3px solid #870003; + border-left: 3px solid;// #870003; } } } diff --git a/app/assets/stylesheets/app.plugins.scss b/app/assets/stylesheets/app.plugins.scss index 08c4b3241..9d39c291c 100644 --- a/app/assets/stylesheets/app.plugins.scss +++ b/app/assets/stylesheets/app.plugins.scss @@ -1,37 +1,108 @@ +// medium editor placeholder +.medium-editor-placeholder { + min-height: 30px; // fix for firefox +} + +//xeditable +.editable-buttons{ + button[type=submit].btn-primary{ + @extend .btn-warning; + } +} + //summernote - .note-editor .note-editable { background-color: white; } + // Growl .growl { top: 90px; z-index: 1100; } +// fullcalendar - -// UI Select - -.form-control { - &.form-control-ui-select { - height: auto; - .select2-choices { - border: none; - background: transparent; - .select2-search-choice { - @extend .label; - padding-left: .9em; - font-size: 100%; - font-weight: normal; - - } - } - } +.fc-view-container .fc-body tr { + height: 40px !important; } +.fc-toolbar { + height: 40px; + background-color: #fff; +} + +.fc-toolbar .fc-button { + background: #F2F2F2; + border: none; + box-shadow: none; + text-shadow: none; + margin: 0; + height: 40px; + line-height: 18px; + padding: 10px; + //&:hover, &:active, &.fc-state-active { background-color: $yellow; } +} + +.fc-toolbar h2 { + font-size: 15px; + line-height: 40px; + margin: 0; +} + +.fc-view-container .fc-widget-header, +.fc-view-container .fc-widget-content { + border-color: #e8e8e8; + font-weight: normal; +} + +.fc-content-skeleton .fc-event { + padding: 2px; + border-left: solid 3px; +} + +.fc-event { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.fc-event .fc-time span, .fc-event .fc-title { + font-size: rem-calc(10); + line-height: rem-calc(12); +} + +.fc-event .fc-time span.label { + font-size: rem-calc(8); + margin-left: 0.7em; +} + +// croix de suppression pour un créneau de disponibilité +.remove-event { + position: absolute; + float: right; + right: 0; + top: 0; + padding: 0; + font-size: 11px; + color: black; + cursor: pointer; + z-index: 9999; + text-align: right; + .training-reserve &, .machine-reserve & { display: none; } +} + +.fc-v-event.fc-end { + border-bottom-width: 2px; +} + +.fc-divider { + display: none !important; +} + + @@ -59,6 +130,7 @@ line-height: rem-calc(24); color: white; font-weight: 800; + text-align: center; a { color: white; &:hover { color: $yellow; } @@ -77,7 +149,7 @@ background: none; @include border-radius($border-radius-base); &:hover, &:focus { - color: $yellow; + //color: $yellow; } .glyphicon-chevron-left { @@ -99,6 +171,31 @@ display: none; } +// .carousel-control { +// // position: absolute; +// display: block; +// margin-bottom: -20px; +// padding: 20px; +// color: white; +// width: 58px; +// height: 58px; +// border: 3px solid white; +// border-radius: 50%; + +// .glyphicon-chevron-right:before { +// // //Reset the icon +// // content: " "; +// // //Give layout +// // display:block; +// // //Your image as background +// // background:url('http://yourarrow.png') no-repeat; +// // //To show full image set the dimensions +// // width:30px; +// // height:30px; +// } +// } + + .banner { } diff --git a/app/assets/stylesheets/app.printer.scss b/app/assets/stylesheets/app.printer.scss index f87ded6ed..5a7e2c1db 100644 --- a/app/assets/stylesheets/app.printer.scss +++ b/app/assets/stylesheets/app.printer.scss @@ -1,3 +1,3 @@ /* - * Require here your print media stylesheets + *= require fullcalendar/dist/fullcalendar.print */ \ No newline at end of file diff --git a/app/assets/stylesheets/app.utilities.scss b/app/assets/stylesheets/app.utilities.scss index 2e077f4b5..962289652 100644 --- a/app/assets/stylesheets/app.utilities.scss +++ b/app/assets/stylesheets/app.utilities.scss @@ -111,7 +111,7 @@ p, .widget p { .b{border: 1px solid rgba(0, 0, 0, 0.05)} .b-a{border: 1px solid $border-color} .b-t{border-top: 1px solid $border-color} -.b-r{border-right: 1px solid $border-color} +.b-r{border-right: 1px solid $border-color !important;} .b-b{border-bottom: 1px solid $border-color} .b-l{border-left: 1px solid $border-color} .b-light{border-color: darken($brand-light, 5%)} diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 014cf9206..bb4619d2b 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -1,17 +1,23 @@ /* *= require_self - *= require select2/select2 + *= require angular-ui-select/dist/select + *= require fullcalendar/dist/fullcalendar *= require jasny-bootstrap/dist/css/jasny-bootstrap - *= require angular-growl/build/angular-growl.min.css + *= require angular-growl-v2/build/angular-growl *= require angular-xeditable/dist/css/xeditable *= require angular-loading-bar/src/loading-bar + *= require nvd3/build/nv.d3 *= require font-awesome + *= require medium-editor/dist/css/medium-editor + *= require medium-editor/dist/css/themes/default + *= require bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.min *= require summernote/dist/summernote + *= require jquery-minicolors/jquery.minicolors.css */ - - @import "app.functions"; +@import "bootstrap-compass"; +@import "bootstrap-sprockets"; @import "compass"; @import "bootstrap_and_overrides"; @@ -25,5 +31,6 @@ @import "app.buttons"; @import "app.components"; @import "app.plugins"; +@import "modules/invoice"; @import "app.responsive"; diff --git a/app/assets/stylesheets/bootstrap_and_overrides.scss b/app/assets/stylesheets/bootstrap_and_overrides.scss index aa27906b0..6dda4f54d 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.scss +++ b/app/assets/stylesheets/bootstrap_and_overrides.scss @@ -78,10 +78,10 @@ $link-hover-decoration: underline; // Semibold = 600, Bold = 700, ExtraB = 800 -$font-family-sans-serif: "Open Sans", Helvetica, Arial, sans-serif !default; -$font-proxima-condensed: "Open Sans Condensed", Helvetica, Arial, sans-serif !default; -$font-family-serif: Georgia, "Times New Roman", Times, serif !default; -$font-felt: "Loved by the King", sans-serif; +$font-family-sans-serif: 'proxima-nova', 'Open Sans', Helvetica, Arial, sans-serif !default; +$font-proxima-condensed: 'proxima-nova-condensed', 'Open Sans Condensed', Helvetica, Arial, sans-serif !default; +$font-family-serif: Georgia, 'Times New Roman', Times, serif !default; +$font-felt: 'felt-tip-roman', 'Loved by the King', cursive, sans-serif; //** Default monospace fonts for ``, ``, and `
`.
 // $font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace !default;
@@ -952,16 +952,16 @@ $hr-border:                   $gray-lighter !default;
 @import "bootstrap/input-groups";
 @import "bootstrap/navs";
 @import "bootstrap/navbar";
-// @import "bootstrap/breadcrumbs";
+@import "bootstrap/breadcrumbs";
 @import "bootstrap/pagination";
-// @import "bootstrap/pager";
+@import "bootstrap/pager";
 @import "bootstrap/labels";
 @import "bootstrap/badges";
-//@import "bootstrap/jumbotron";
+@import "bootstrap/jumbotron";
 @import "bootstrap/thumbnails";
 @import "bootstrap/alerts";
 @import "bootstrap/progress-bars";
-// @import "bootstrap/media";
+@import "bootstrap/media";
 @import "bootstrap/list-group";
 @import "bootstrap/panels";
 @import "bootstrap/responsive-embed";
diff --git a/app/assets/stylesheets/modules/invoice.scss b/app/assets/stylesheets/modules/invoice.scss
new file mode 100644
index 000000000..452138f54
--- /dev/null
+++ b/app/assets/stylesheets/modules/invoice.scss
@@ -0,0 +1,181 @@
+
+// admin invoices
+
+.invoice-placeholder {
+  width: 80%;
+  max-width: 800px;
+  height: 100%;
+  margin: auto;
+  margin-top: 2em;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #aeaeae #979797 #7b7b7b;
+  box-shadow: 2px 3px 6px 0 #898989,
+              -2px 3px 6px 0 #898989;
+  padding: 2em;
+
+  .invoice-buyer-infos {
+    float: right;
+    text-align: right;
+    margin-left: 1em;
+  }
+
+  .invoice-logo {
+    height: 6em;
+    width: 100%;
+    position: relative;
+    background-size: cover;
+    background-repeat: no-repeat;
+
+    img {
+      display: block;
+      width: auto;
+      max-height: 100%;
+      max-width: 60%;
+    }
+
+    &:hover .tools-box {
+      opacity: 1;
+    }
+
+    .tools-box {
+      opacity: 0;
+      position: absolute;
+      bottom: 10px;
+      left: 0;
+      right: 0;
+      text-align: center;
+    }
+  }
+
+  .invoice-object, .invoice-data, .invoice-data p, .invoice-text, .invoice-legals {
+    margin-top: 2em;
+  }
+
+  .invoice-data table {
+    width: 100%
+  }
+
+  .invoice-data tr, .invoice-data th, .invoice-data td {
+    border: 1px solid;
+    padding: 4px;
+  }
+
+  .invoice-text {
+    font-weight: bold;
+  }
+
+  .invoice-legals {
+    text-align: right;
+  }
+
+  .invoice-editable:hover {
+    background-color: $yellow;
+    overflow-x: hidden;
+  }
+
+  .invoice-activable {
+    font-style: italic;
+    color: #c4c4c4;
+    overflow-x: hidden;
+  }
+
+  .invoice-activable:hover {
+    background-color: $yellow;
+    border: 1px dashed #c4c4c4;
+  }
+
+
+  &:after {
+    content:"";
+    display:block;
+    margin-top:30%;
+  }
+
+  .vat-line {
+    background-color: #e4e4e4;
+    text-align: right;
+  }
+
+  .bold {
+    font-weight: bold;
+  }
+
+  .italic {
+    font-style: italic;
+  }
+
+  .right {
+    text-align: right;
+  }
+
+}
+
+.custom-invoice {
+  .modal-header {
+    @extend .modal-header;
+    // padding-left: 4em;
+    text-align: center;
+    background-color: #e6e6e6;
+  }
+
+  .modal-body {
+
+    .elements ul {
+      @extend .list-unstyled;
+    }
+
+    .elements li {
+      @extend .btn;
+      @extend .btn-default;
+      width: 100%;
+    }
+
+    table.invoice-element-legend {
+      min-width: 15em;
+    }
+
+    .invoice-element-legend tr, .invoice-element-legend th, .invoice-element-legend td {
+      border: 1px solid;
+      padding: 4px;
+    }
+
+    .bottom-notes {
+      font-style: italic;
+    }
+
+  }
+
+}
+
+
+.partial-avoir-table tr {
+  float:left;
+
+  .input-col { min-width: 2em; }
+  .label-col { min-width: 18em; }
+  .amount-col { min-width: 6em; }
+}
+
+.partial-avoir-selected-item {
+  background-color: $yellow;
+  display: block;
+  position: relative;
+
+  &:after {
+    content:"Rembourser";
+    display:inline-block;
+    position: absolute;
+    right: 0.8em;
+    top: 1.8em;
+    color: $red;
+    width: 6.7em;
+    transform: rotate(-22.5deg);
+    overflow: visible;
+    font-weight: bold;
+    border: 1px solid;
+    padding: 0 4px;
+    border-radius: 5px;
+    font-size: small;
+  }
+}
\ No newline at end of file
diff --git a/app/assets/templates/admin/admins/new.html.erb b/app/assets/templates/admin/admins/new.html.erb
new file mode 100644
index 000000000..a53506394
--- /dev/null
+++ b/app/assets/templates/admin/admins/new.html.erb
@@ -0,0 +1,34 @@
+
+
+
+
+ +
+
+
+
+

{{ 'add_an_administrator' }}

+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ + + +
+
+ +
+
diff --git a/app/assets/templates/admin/authentications/_form.html.erb b/app/assets/templates/admin/authentications/_form.html.erb new file mode 100644 index 000000000..efe2dc71b --- /dev/null +++ b/app/assets/templates/admin/authentications/_form.html.erb @@ -0,0 +1,30 @@ +
+ +
+ + {{ 'provider_name_is_required' }} +
+
+ +
+ +
+ + + {{ 'authentication_type_is_required' }} +
+
\ No newline at end of file diff --git a/app/assets/templates/admin/authentications/_oauth2.html.erb b/app/assets/templates/admin/authentications/_oauth2.html.erb new file mode 100644 index 000000000..50d0300e8 --- /dev/null +++ b/app/assets/templates/admin/authentications/_oauth2.html.erb @@ -0,0 +1,93 @@ +
+ +
+ +
+ + {{ 'common_url_is_required' }} + {{ 'provided_url_is_not_a_valid_url' }} +
+
+ +
+ +
+ + {{ 'oauth2_authorization_endpoint_is_required' }} + {{ 'provided_endpoint_is_not_valid' }} +
+
+ +
+ +
+ + {{ 'oauth2_token_acquisition_endpoint_is_required' }} + {{ 'provided_endpoint_is_not_valid' }} +
+
+ +
+ +
+ + {{ 'profile_edition_url_is_required' }} + {{ 'provided_url_is_not_a_valid_url' }} +
+
+ +
+ +
+ + {{ 'oauth2_client_identifier_is_required' }} {{ 'obtain_it_when_registering_with_your_provider' }} +
+
+ +
+ +
+ + {{ 'oauth2_client_secret_is_required' }} {{ 'obtain_it_when_registering_with_your_provider' }} +
+
+ + \ No newline at end of file diff --git a/app/assets/templates/admin/authentications/_oauth2_mapping.html.erb b/app/assets/templates/admin/authentications/_oauth2_mapping.html.erb new file mode 100644 index 000000000..bb16e52f1 --- /dev/null +++ b/app/assets/templates/admin/authentications/_oauth2_mapping.html.erb @@ -0,0 +1,76 @@ +

{{ 'define_the_fields_mapping' }}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'model' }}{{ 'field' }}{{ 'api_endpoint_url' }}{{ 'api_type' }}{{ 'api_fields' }}
{{m.local_model}}{{m.local_field}}{{m.api_endpoint}}{{m.api_data_type}}{{m.api_field}} + +
+ + + + + + + + + + + + +
\ No newline at end of file diff --git a/app/assets/templates/admin/authentications/edit.html.erb b/app/assets/templates/admin/authentications/edit.html.erb new file mode 100644 index 000000000..43e282f9d --- /dev/null +++ b/app/assets/templates/admin/authentications/edit.html.erb @@ -0,0 +1,51 @@ +
+ +
+
+ +
+
+

{{ 'provider' | translate }} {{provider.name}}

+
+ +
+ +
+
+
+ {{ 'cancel' }} +
+
+ +
+ +
+ +
+ + +
+
+ +
+ +
+
+ + +
+ + + +
+
+ +
+
+
diff --git a/app/assets/templates/admin/authentications/index.html.erb b/app/assets/templates/admin/authentications/index.html.erb new file mode 100644 index 000000000..d0d9b5313 --- /dev/null +++ b/app/assets/templates/admin/authentications/index.html.erb @@ -0,0 +1,40 @@ +
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
{{ 'name' }}{{ 'type' }}{{ 'state' }}
{{ provider.name }}{{ getType(provider.providable_type) }}{{ getState(provider.status) }} + + +
+
\ No newline at end of file diff --git a/app/assets/templates/admin/authentications/new.html.erb b/app/assets/templates/admin/authentications/new.html.erb new file mode 100644 index 000000000..bdc47246b --- /dev/null +++ b/app/assets/templates/admin/authentications/new.html.erb @@ -0,0 +1,52 @@ +
+ +
+
+ +
+
+

{{ 'add_a_new_authentication_provider' }}

+
+ +
+ +
+
+
+ {{ 'cancel' }} +
+
+ +
+ +
+ +
+ + +
+
+ +
+ +
+
+ + + +
+ + + +
+
+ +
+
+
diff --git a/app/assets/templates/admin/calendar/calendar.html.erb b/app/assets/templates/admin/calendar/calendar.html.erb new file mode 100644 index 000000000..7cde3e4dd --- /dev/null +++ b/app/assets/templates/admin/calendar/calendar.html.erb @@ -0,0 +1,66 @@ +
+
+
+
+ +
+
+
+
+

{{ 'calendar_management' }}

+
+
+ +
+
+ {{ 'trainings' }}
+ {{ 'machines' }} +
+
+ +
+
+ + +
+ +
+
+
+ +
+
+
+

{{ 'ongoing_reservations' }}

+
+
+
    +
  • + {{r.user.name}} + - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }} + - {{r.reservable.name}} + +
  • +
+
{{ 'no_reservations' }}
+
+
+
+ +
+
+
+

{{ 'machines' }}

+
+
+
    +
  • + {{m.name}} + +
  • +
+
+
+
+ +
diff --git a/app/assets/templates/admin/calendar/eventModal.html.erb b/app/assets/templates/admin/calendar/eventModal.html.erb new file mode 100644 index 000000000..6cc3bda96 --- /dev/null +++ b/app/assets/templates/admin/calendar/eventModal.html.erb @@ -0,0 +1,65 @@ + + + diff --git a/app/assets/templates/admin/events/index.html.erb b/app/assets/templates/admin/events/index.html.erb index fa538b729..d8bdf9dd8 100644 --- a/app/assets/templates/admin/events/index.html.erb +++ b/app/assets/templates/admin/events/index.html.erb @@ -7,13 +7,13 @@
-

Les Stages et ateliers du Fab Lab

+

{{ 'fablab_courses_and_workshops' }}

@@ -24,43 +24,43 @@
- - + + - + @@ -71,7 +71,7 @@ diff --git a/app/assets/templates/admin/events/reservations.html.erb b/app/assets/templates/admin/events/reservations.html.erb new file mode 100644 index 000000000..f21bad55f --- /dev/null +++ b/app/assets/templates/admin/events/reservations.html.erb @@ -0,0 +1,52 @@ +
+
+
+
+ +
+
+
+
+

{{ 'reservations' | translate }} {{event.title}}

+
+
+
+ +
+
+
+ + +
TitreDates{{ 'title' }}{{ 'dates' }}
{{ event.title }} - Du {{event.start_date | amDateFormat:'DD/MM/YYYY'}} au {{event.end_date | amDateFormat:'DD/MM/YYYY'}} + {{ 'from_DATE' | translate:{DATE:(event.start_date | amDateFormat:'LL')} }} {{ 'to_date' }}{{event.end_date | amDateFormat:'LL'}}
- Toute la journée + {{ 'all_day' }} - De {{event.start_date | date:'HH:mm'}} - à - {{event.end_date | date:'HH:mm'}} + {{ 'from_TIME' | translate:{TIME:(event.start_date | amDateFormat:'LT')} }} + {{ 'to_time' }} + {{event.end_date | amDateFormat:'LT'}}
- + - <%#%>
+ + + + + + + + + + + + + + + + +
{{ 'user' }}{{ 'payment_date' }}{{ 'reserved_tickets' }}
+ {{ reservation.user_full_name }} + {{ reservation.created_at | amDateFormat:'LL LTS' }}{{ 'full_price_' | translate }} {{reservation.nb_reserve_places}}
{{ 'reduced_rate_' | translate }} {{reservation.nb_reserve_reduced_places}}
+
+ +
+
+

{{ 'no_reservations_for_now' }}

+ + + + + + diff --git a/app/assets/templates/admin/groups/index.html.erb b/app/assets/templates/admin/groups/index.html.erb new file mode 100644 index 000000000..498c29c1c --- /dev/null +++ b/app/assets/templates/admin/groups/index.html.erb @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + +
{{ 'group_name' }}
+ + {{group.name}} + + + +
+ + +
+
+ + +
+
diff --git a/app/assets/templates/admin/invoices/avoirModal.html.erb b/app/assets/templates/admin/invoices/avoirModal.html.erb new file mode 100644 index 000000000..56e72b16c --- /dev/null +++ b/app/assets/templates/admin/invoices/avoirModal.html.erb @@ -0,0 +1,60 @@ + + + \ No newline at end of file diff --git a/app/assets/templates/admin/invoices/index.html.erb b/app/assets/templates/admin/invoices/index.html.erb new file mode 100644 index 000000000..4c96d3789 --- /dev/null +++ b/app/assets/templates/admin/invoices/index.html.erb @@ -0,0 +1,383 @@ +
+
+
+
+ +
+
+
+
+

{{ 'invoices' }}

+
+
+ +
+
+ +
+
+
+ + +

{{ 'filter_invoices' | translate }}

+ +
+
+
+
+ {{ 'invoice_#_' }} + +
+
+
+ +
+
+
+ {{ 'customer_' }} + +
+
+
+ +
+
+
+ {{ "date_" | translate }} + +
+
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'invoice_#' | translate }} {{ 'date' | translate }} {{ 'price' | translate }} {{ 'customer' | translate }}
{{ invoice.reference }}{{ invoice.date | amDateFormat:'L LTS' }}{{ invoice.date | amDateFormat:'L' }}{{ invoice.total | currency}}{{ invoice.name }} + + +
+

{{ 'no_invoices_for_now' }}

+ +
+
+
+ + + + + +
+ +
+ {{ 'john_smith' }} +
{{ 'john_smith@example_com' }}
+
+
{{ 'invoice_reference_' | translate }} {{mkReference()}}
+
{{ 'code_' | translate }} {{invoice.code.model}}
+
{{ 'code_disabled' }}
+
{{ 'order_#' | translate }} {{mkNumber()}}
+
{{ 'invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}
+
+ {{ 'object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }} +
+
+ {{ 'order_summary' | translate }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'details' }}{{ 'amount' }}
{{ 'machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}}{{30.0 | currency}}
{{ 'total_amount' }}{{ 'total_including_all_taxes' }}{{30.0 | currency}}
{{ 'VAT_disabled' }}
{{ 'including_VAT' | translate }} {{invoice.VAT.rate}} %{{30*invoice.VAT.rate/100 | currency}}
{{ 'including_total_excluding_taxes' }}{{30-(30*invoice.VAT.rate/100) | currency}}
{{ 'including_amount_payed_on_ordering' }}{{30.0 | currency}}
+

+ {{ 'settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }} +

+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/templates/admin/members/_form.html.erb b/app/assets/templates/admin/members/_form.html.erb index 6de0a9c35..d7c1831d0 100644 --- a/app/assets/templates/admin/members/_form.html.erb +++ b/app/assets/templates/admin/members/_form.html.erb @@ -1,9 +1,61 @@
- +
- - Le type d'utilisateur est obligatoire + {{ 'group_is_required' }}
+ +
+ +
+ + +
+
+ {{ 'no_more_invoices_will_be_generated_for_' | translate }} {{ '_the_payments_carried_out_at_the_reception_' }} {{ '_regarding_this_user' | translate }} +
+
+ +
+ +
+ + + + + + + + + + +
+
+ +
+ +
+ + + + + + + + + + +
+
diff --git a/app/assets/templates/admin/members/edit.html.erb b/app/assets/templates/admin/members/edit.html.erb index 3e26192af..92107c960 100644 --- a/app/assets/templates/admin/members/edit.html.erb +++ b/app/assets/templates/admin/members/edit.html.erb @@ -9,15 +9,15 @@
-

Utilisateur : {{ user.name }}

+

{{ 'user' | translate }} {{ user.name }}

-
- Annuler +
+ {{ 'cancel' }}
@@ -31,22 +31,191 @@
-
+ -
-
- + + - +
+
+ -
+ + +
-
+ + + + + + +
+
+
+

{{ subscription.plan | humanReadablePlanName }}

+

+ {{ 'duration' | translate }} {{ subscription.plan.interval | planIntervalFilter: subscription.plan.interval_count }} +

+

+ {{ 'expires_at' | translate }} {{ subscription.expired_at | amDateFormat: 'L' }} +

+

+ {{ 'price_' | translate }} {{ subscription.plan.amount | currency}} +

+ + +
+ + +
+

+ {{ 'user_has_no_current_subscription' }} +

+ +
+ +
+ +
+
+ + +
+
+
+

{{ 'next_trainings' | translate }}

+
+
+
    +
  • + {{r.reservable.name}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }} +
  • +
+
{{ 'no_trainings' }}
+
+
- - +
+
+
+

{{ 'passed_trainings' | translate }}

+
+
+
    +
  • + {{r.reservable.name}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }} + + +
  • +
+
{{ 'no_trainings' }}
+
+
+
+
+
+
+

{{ 'validated_trainings' | translate }}

+
+
+
    +
  • + {{t.name}} +
  • +
+
{{ 'no_trainings' }}
+
+
+
+
+ + +
+
+
+

{{ 'next_courses_and_workshops' | translate }}

+
+
+
    +
  • + {{r.reservable.title}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }} + +
    + {{ 'NUMBER_full_price_tickets_reserved' }} +
    + +
    + {{ 'NUMBER_reduced_rate_tickets_reserved' }} +
    +
  • +
+
{{ 'no_upcomning_courses_or_workshops'}}
+
+
+
+
+
+
+

{{ 'passed_courses_and_workshops' | translate }}

+
+
+
    +
  • + {{r.reservable.title}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }} +
  • +
+
{{ 'no_passed_courses_or_workshop' }}
+
+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + +
{{ 'invoice_#' }}{{ 'date' }}{{ 'price' }}
{{ invoice.reference }}{{ invoice.date | amDateFormat:'L LTS' }}{{ invoice.date | amDateFormat:'L' }}{{ invoice.total | currency }} + +
+

{{ 'no_invoices_for_now' }}

+ +
+
+
diff --git a/app/assets/templates/admin/members/index.html.erb b/app/assets/templates/admin/members/index.html.erb index ed0a9e35e..3d0f6df69 100644 --- a/app/assets/templates/admin/members/index.html.erb +++ b/app/assets/templates/admin/members/index.html.erb @@ -7,7 +7,7 @@
-

Liste des membres

+

{{ 'users_management' }}

@@ -16,56 +16,127 @@
-
-
-
- - -
+
+ + + +
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'surname' | translate }} {{ 'first_name' | translate }} {{ 'email' | translate }} {{ 'phone' | translate }} {{ 'user_type' | translate }} {{ 'subscription' | translate }}
{{ member.profile.last_name }}{{ member.profile.first_name }}{{ member.email }}{{ member.profile.phone }}{{ member.group.name }}{{ member.subscribed_plan | humanReadablePlanName }} +
+ +
+
+
+
+ + +
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'surname' | translate }} {{ 'first_name' | translate }} {{ 'email' | translate }} {{ 'phone' | translate }}
{{ admin.profile_attributes.last_name }}{{ admin.profile_attributes.first_name }}{{ admin.email }}{{ admin.profile_attributes.phone }} + +
+
+
+ + +
+
+ + +
+
+ + + +
+
+
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nom Prénom Email Tel. Type utilisateur
{{ member.profile.last_name }}{{ member.profile.first_name }}{{ member.email }}{{ member.profile.phone }}{{ member.group.name }} -
- -
-
-
diff --git a/app/assets/templates/admin/members/new.html.erb b/app/assets/templates/admin/members/new.html.erb index 9a141a64b..30c049de1 100644 --- a/app/assets/templates/admin/members/new.html.erb +++ b/app/assets/templates/admin/members/new.html.erb @@ -9,15 +9,15 @@
-

Ajouter un membre

+

{{ 'add_a_member' }}

-
- Annuler +
+ {{ 'cancel' }}
@@ -42,7 +42,7 @@ diff --git a/app/assets/templates/admin/plans/_form.html.erb b/app/assets/templates/admin/plans/_form.html.erb new file mode 100644 index 000000000..03d38582d --- /dev/null +++ b/app/assets/templates/admin/plans/_form.html.erb @@ -0,0 +1,158 @@ +

{{ 'general_informations' }}

+ + +
+ + + {{ 'name_is_required' }} + {{ 'name_length_must_be_less_than_24_characters' }} +
+
+ + + {{ 'type_is_required' }} +
+
+ + + {{ 'group_is_required' }} +
+ +
+ + + {{ 'period_is_required' }} +
+ +
+ + + {{ 'number_of_periods_is_required' }} +
+ +
+
+ +
+ {{currencySymbol}} + +
+ {{ 'price_is_required' }} +
+
+ +
+ + + + {{ 'on_the_subscriptions_page_the_most_prominent_subscriptions_will_be_placed_at_the_top_of_the_list' | translate }} + {{ 'an_evelated_number_means_a_higher_prominence' | translate }} + +
+ +
+ + + {{ (plan.is_rolling ? 'yes' : 'no') | translate }} + + + {{ 'a_rolling_subscription_will_begin_the_day_of_the_first_training' | translate }} + {{ 'otherwise_it_will_begin_as_soon_as_it_is_bought' | translate }} + +
+ + + + + + +
+
+ {{file.attachment || plan.plan_file_attributes.attachment_identifier}} +
+ {{ 'attach_an_information_sheet' }} + {{ 'change' }} + +
+ +
+ + +
+ + + + +
+ {{ 'as_part_of_a_partner_subscription_some_notifications_may_be_sent_to_this_user' }} +
+ +
+ + + + +
\ No newline at end of file diff --git a/app/assets/templates/admin/plans/edit.html.erb b/app/assets/templates/admin/plans/edit.html.erb new file mode 100644 index 000000000..5c9020129 --- /dev/null +++ b/app/assets/templates/admin/plans/edit.html.erb @@ -0,0 +1,69 @@ +
+
+
+
+ +
+
+
+
+

{{ 'subscription_plan' | translate }} {{ plan.base_name }}

+
+
+ +
+
+ {{ 'cancel' }} +
+ +
+ + +
+
+ +
+
+ +
+
+ + + +

{{ 'prices' }}

+
+ + + +
+

{{ 'machines' }}

+ + + + + + + + + + + + +
{{ 'machine' }}{{ 'hourly_rate' }}
{{ price.priceable_name }} (id {{ price.priceable_id }}) * +
+ {{currencySymbol}} + + +
+
+ + +
+
+ +
+
diff --git a/app/assets/templates/admin/plans/new.html.erb b/app/assets/templates/admin/plans/new.html.erb new file mode 100644 index 000000000..bd5621fb8 --- /dev/null +++ b/app/assets/templates/admin/plans/new.html.erb @@ -0,0 +1,33 @@ +
+
+
+
+ +
+
+
+
+

{{ 'add_a_subscription_plan' }}

+
+
+ +
+
+ +
+
+ +
+
+ + + + + +
+
+ +
+
diff --git a/app/assets/templates/admin/pricing/index.html.erb b/app/assets/templates/admin/pricing/index.html.erb new file mode 100644 index 000000000..4a5ce4acf --- /dev/null +++ b/app/assets/templates/admin/pricing/index.html.erb @@ -0,0 +1,219 @@ +
+
+
+
+ +
+
+
+
+

{{ 'pricing_management' }}

+
+
+ +
+
+ + +
+
+ +
+ + + +

{{ 'list_of_the_subscription_plans' }}

+ +
+ {{ 'beware_the_subscriptions_are_disabled_on_this_application' | translate }} + {{ 'you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager' | translate }} +
{{ 'for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later' | translate }} +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'type' | translate }} {{ 'name' | translate }} {{ 'duration' | translate }} {{ 'group' | translate }} {{ 'prominence' | translate }} {{ 'price' | translate }}
{{getPlanType(plan.type)}}{{plan.base_name}}{{ plan.interval | planIntervalFilter }}{{getGroupFromId(groups, plan.group_id).name}}{{plan.ui_weight}}{{plan.amount | currency}}
+
+ + + + + + + + + + + + + + + + +
{{ 'trainings' }} + {{group.name}} +
+ {{ training.name }} + + + {{ findTrainingsPricing(trainingsPricings, training.id, group.id).amount | currency}} + +
+
+ + +
+ {{ 'these_prices_match_machine_hours_rates_' | translate }} {{ '_without_subscriptions' }}. +
+ + + + + + + + + + + + + +
{{ 'machines' }} + {{group.name}} +
+ {{ machine.name }} + + + {{ findPriceBy(machinesPrices, machine.id, group.id).amount | currency}} + +
+
+ + +

{{ 'trainings' }}

+ + + + + + + + + + + + + + + + + + +
{{ 'subscription' }}{{ 'credits' }}{{ 'related_trainings' }}
+ {{ plan | humanReadablePlanName: groups }} + + + {{ plan.training_credit_nb }} + + + + {{ showTrainings(trainingIds) }} + + +
+ + +
+
+ +
+
+ +

{{ 'machines' }}

+
+ +
+ + + + + + + + + + + + + + + + + + +
{{ 'machine' }}{{ 'hours' }}{{ 'related_subscriptions' }}
+ + {{ showCreditableName(mc) }} + + + + {{ mc.hours }} + + + + {{ getPlanFromId(mc.plan_id) | humanReadablePlanName: groups: 'short' }} + + +
+ + +
+
+ + +
+
+
+
+
+ +
+
diff --git a/app/assets/templates/admin/project_elements/index.html.erb b/app/assets/templates/admin/project_elements/index.html.erb index 3254f262f..b4264d2bc 100644 --- a/app/assets/templates/admin/project_elements/index.html.erb +++ b/app/assets/templates/admin/project_elements/index.html.erb @@ -7,7 +7,7 @@
-

Gestion des éléments projets

+

{{ 'projects_elements_management' }}

@@ -19,14 +19,14 @@
- - - + + + - + @@ -49,7 +49,7 @@
Nom{{ 'name' }}
-
- - + + + - + @@ -89,7 +89,7 @@
Nom{{ 'name' }}
-
- - + + + - - + + @@ -135,7 +135,7 @@
Nom{{ 'name' }}
-
-
+ +
diff --git a/app/assets/templates/admin/settings/index.html b/app/assets/templates/admin/settings/index.html new file mode 100644 index 000000000..2fa5158c7 --- /dev/null +++ b/app/assets/templates/admin/settings/index.html @@ -0,0 +1,463 @@ +
+
+
+
+ +
+
+
+
+

{{ 'customize_the_application' }}

+
+
+ +
+
+ +
+
+ +
+ + + +
+
+ {{ 'title' }} +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+

{{ 'title_concordance' }}

+
+ + +
+ + +
+ +
+
+
+
+
+ +
+
+ {{ 'customize_information_messages' }} +
+
+ + +
+
+

{{ 'message_of_the_machine_booking_page' }}

+
+ +
+ +
+
+

{{ 'warning_message_of_the_training_booking_page'}}

+
+ +
+ +
+
+

{{ 'information_message_of_the_training_reservation_page'}}

+
+ +
+ +
+
+

{{ 'message_of_the_subscriptions_page' }}

+
+
+ +
+
+
+
+

{{ 'message_of_the_event_page_relative_to_the_reduced_rate_availability_conditions' }}

+
+
+ +
+
+ +
+
+ +
+
+ {{ 'legal_documents'}} +
+
+
+ {{ 'if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user' }} +
+
+
+ + + +
+
+
+ {{cgvFile.custom_asset_file_attributes.attachment}} +
+ + {{ 'browse' }} + {{ 'change' }} + + +
+
+ +
+
+
+
+ + + +
+
+
+ {{cguFile.custom_asset_file_attributes.attachment}} +
+ + {{ 'browse' }} + {{ 'change' }} + + +
+
+ +
+
+
+
+ +
+
+ {{ 'customize_the_graphics' }} +
+
+
+ {{ 'for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height' }}
+ {{ 'concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels' }}
+
+ {{ 'remember_to_refresh_the_page_for_the_changes_to_take_effect' }} +
+
+
+
+ + +

{{ 'logo_(white_background)' }}

+ + +
+
+
+
+ + +

{{ 'logo_(black_background)' }}

+ + +
+
+
+
+ + +

{{ 'favicon' }}

+
+ + + {{customFavicon.custom_asset_file_attributes.attachment}} +
+
+
+ {{ 'change_the_favicon' | translate }} + +
+
+
+
+ +
+
+
+
+
+

{{ 'main_colour' }}

+
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+

{{ 'secondary_colour' }}

+
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+
+
+ + +
+
+
+
+

{{ 'news_of_the_home_page' }}

+
+ {{ 'leave_it_empty_to_not_bring_up_any_news_on_the_home_page' | translate }} + +
+
+

{{ 'twitter_stream' }}

+
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ +
+
+

+ {{ 'shift_enter_to_force_carriage_return' | translate }} + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ {{ 'shift_enter_to_force_carriage_return' | translate }} + +
+ +
+ +
+
+
+ +
+
+ {{ 'reservations_parameters' }} +
+
+
+
+

{{ 'confine_the_booking_agenda' }}

+
+

{{ 'opening_time' }}

+ +
+
+ +
+
+

{{ 'closing_time' }}

+ +
+
+ +
+
+
+

{{ 'ability_for_the_users_to_move_their_reservations' }}

+
+ + + +
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+

{{ 'ability_for_the_users_to_cancel_their_reservations' }}

+
+ + + +
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+
+
+
+
+
+ +
+
diff --git a/app/assets/templates/admin/statistics/graphs.html.erb b/app/assets/templates/admin/statistics/graphs.html.erb new file mode 100644 index 000000000..6095fab90 --- /dev/null +++ b/app/assets/templates/admin/statistics/graphs.html.erb @@ -0,0 +1,147 @@ +
+
+
+
+ +
+
+
+
+

{{ 'statistics' }}

+
+
+ +
+
+ + +
+
+ +
+
+ + + +
+ +
+
    +
  • + {{ 'start' }} +
    + + + + +
    +
  • +
  • + {{ 'end' }} +
    + + + + +
    +
  • +
  • + +
  • +
+
+
+
+ + + + +
+
+
+
+ + +
+
+ + +
+
+
+
+

{{ 'top_list_of' | translate}} {{stat.label}}

+
+ +
+
+
+ + +
+

{{type.label}}

+
+ +
+
+ +
+
+
+ +
+
\ No newline at end of file diff --git a/app/assets/templates/admin/statistics/index.html.erb b/app/assets/templates/admin/statistics/index.html.erb new file mode 100644 index 000000000..82743039a --- /dev/null +++ b/app/assets/templates/admin/statistics/index.html.erb @@ -0,0 +1,287 @@ +
+
+
+
+ +
+
+
+
+

{{ 'statistics' }}

+
+
+ +
+
+ + +
+
+ +
+ + +
+
+ +
+
    +
  • {{ 'start' }} +
    + + +
    +
  • +
  • {{ 'end' }} +
    + + +
    +
  • +
+
+
+ +
+ + +
+ +
+ +
+
    +
  • {{ 'criterion' }} +
    + + +
    +
  • +
  • {{ 'value' }} +
    + + + + + +
    + + + + +
    + + + + +
    +
    +
  • +
  • +
    + +
    +
  • +
+
+
+ +
+ +
+ +
+ +
+
    +
  • + {{ 'start' }} +
    + + + + +
    +
  • +
  • + {{ 'end' }} +
    + + + + +
    +
  • +
  • + +
  • +
+
+
+
+ +
+
    +
  • {{ 'entries' | translate }} {{data.length}}
  • +
  • {{ 'revenue_' | translate }} {{sumCA | currency}}
  • +
  • {{ 'average_age' | translate }} {{averageAge}} {{ 'years_old' | translate }}
  • +
  • {{ 'total' | translate }} {{type.active.label}} : {{sumStat}}
  • +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'date' }}{{ 'user' }}{{ 'gender' }}{{ 'age' }}{{ 'type' }}{{type.active.label}}{{field.label}}{{ 'revenue' | translate }} + + + + + + + +
{{formatDate(datum._source.date)}}{{getUserNameFromId(datum._source.userId)}}{{formatSex(datum._source.gender)}}{{datum._source.age}} {{ 'years_old' | translate }}{{ 'unknown' }}{{formatSubtype(datum._source.subType)}}{{datum._source.stat}} + + {{formatDate(datum._source[field.key])}} +
    +
  • {{elem.name}}
  • +
+ {{datum._source[field.key]}} +
+
{{datum._source.ca | currency}}{{ 'unknown' }}
+
+
+
+ +
+
+ diff --git a/app/assets/templates/admin/subscriptions/create_modal.html.erb b/app/assets/templates/admin/subscriptions/create_modal.html.erb new file mode 100644 index 000000000..7ff44ecdc --- /dev/null +++ b/app/assets/templates/admin/subscriptions/create_modal.html.erb @@ -0,0 +1,20 @@ + + + diff --git a/app/assets/templates/admin/subscriptions/expired_at_modal.html.erb b/app/assets/templates/admin/subscriptions/expired_at_modal.html.erb new file mode 100644 index 000000000..b2c945523 --- /dev/null +++ b/app/assets/templates/admin/subscriptions/expired_at_modal.html.erb @@ -0,0 +1,33 @@ + + + diff --git a/app/assets/templates/admin/tags/index.html.erb b/app/assets/templates/admin/tags/index.html.erb new file mode 100644 index 000000000..dd734e01a --- /dev/null +++ b/app/assets/templates/admin/tags/index.html.erb @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + +
{{ 'tag_name' }}
+ + {{tag.name}} + + + +
+ + +
+
+ + +
+
diff --git a/app/assets/templates/admin/trainings/index.html.erb b/app/assets/templates/admin/trainings/index.html.erb new file mode 100644 index 000000000..0b218b814 --- /dev/null +++ b/app/assets/templates/admin/trainings/index.html.erb @@ -0,0 +1,124 @@ +
+
+
+
+ +
+
+
+
+

{{ 'trainings_monitoring' }}

+
+
+ +
+
+ + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
{{ 'name' }}{{ 'associated_machines' }}{{ 'number_of_tickets' }}
+ + {{ training.name }} + + + + {{ showMachines(training) }} + + + + {{ training.nb_total_places }} + + +
+ + +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + +
{{ 'training' }}{{ 'date' }}
{{training_name}} + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
diff --git a/app/assets/templates/admin/trainings/modal_edit.html.erb b/app/assets/templates/admin/trainings/modal_edit.html.erb new file mode 100644 index 000000000..2c51871fa --- /dev/null +++ b/app/assets/templates/admin/trainings/modal_edit.html.erb @@ -0,0 +1,14 @@ + + + diff --git a/app/assets/templates/admin/trainings/validTrainingModal.html.erb b/app/assets/templates/admin/trainings/validTrainingModal.html.erb new file mode 100644 index 000000000..99608fef3 --- /dev/null +++ b/app/assets/templates/admin/trainings/validTrainingModal.html.erb @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/app/assets/templates/dashboard/events.html.erb b/app/assets/templates/dashboard/events.html.erb new file mode 100644 index 000000000..0903ba9dd --- /dev/null +++ b/app/assets/templates/dashboard/events.html.erb @@ -0,0 +1,47 @@ +
+ +
+
+ +
+ +
+ + +
+ +
+
+
+

{{ 'your_next_courses_and_workshops' | translate }}

+
+
+
    +
  • + {{r.reservable.title}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }} +
    {{ 'NUMBER_normal_places_reserved' }} +
    {{ 'NUMBER_reduced_fare_places_reserved' }} +
  • +
+
{{ 'no_courses_or_workshops_to_come' }}
+
+
+
+
+
+
+

{{ 'your_previous_courses_and_workshops' | translate }}

+
+
+
    +
  • + {{r.reservable.title}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }} +
  • +
+
{{ 'no_passed_courses_or_workshops' }}
+
+
+
+ +
+
diff --git a/app/assets/templates/dashboard/invoices.html.erb b/app/assets/templates/dashboard/invoices.html.erb new file mode 100644 index 000000000..5d103fc15 --- /dev/null +++ b/app/assets/templates/dashboard/invoices.html.erb @@ -0,0 +1,47 @@ +
+ +
+
+ +
+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + +
{{ 'reference_number' }}{{ 'date' }}{{ 'price' }}
{{ invoice.reference }}{{ invoice.date | amDateFormat:'L LTS' }}{{ invoice.date | amDateFormat:'L' }}{{ invoice.total | currency}} + +
+

{{ 'no_invoices_for_now' }}

+ +
+
+
diff --git a/app/assets/templates/dashboard/nav.html.erb b/app/assets/templates/dashboard/nav.html.erb index 9204b1145..0c4d5f34e 100644 --- a/app/assets/templates/dashboard/nav.html.erb +++ b/app/assets/templates/dashboard/nav.html.erb @@ -1,6 +1,6 @@
- - diff --git a/app/assets/templates/dashboard/profile.html.erb b/app/assets/templates/dashboard/profile.html.erb index b22152862..ed3b13fd1 100644 --- a/app/assets/templates/dashboard/profile.html.erb +++ b/app/assets/templates/dashboard/profile.html.erb @@ -13,19 +13,67 @@
{{user.name}}
{{user.email}}
-
Dernière activité le {{user.last_sign_in_at | amDateFormat: 'Do MMMM '}}
+
{{ 'last_activity_on_' | translate }} {{user.last_sign_in_at | amDateFormat: 'LL'}}
-
+
+
+

{{ 'group' }}

+
+ + {{getUserGroup().name}} + + +
+
+ + +
+
+
+

{{ 'subscription' }}

+
+ + {{ user.subscribed_plan | humanReadablePlanName }} +
{{ 'your_subscription_expires_on_' | translate }} {{user.subscription.expired_at | amDateFormat: 'LL'}}
+
+ +
+
{{ 'no_subscriptions' | translate }}
{{ 'i_want_to_subscribe' }}
+
-

Projets

+

{{ 'trainings' }}

+
    +
  • + {{r.reservable.name}} - {{ 'to_come' | translate }} +
  • +
  • + {{t.name}} - {{ 'approved' | translate }} +
  • +
+
{{ 'no_trainings' }}
+
+ +
+

{{ 'projects' }}

  • {{p.name}}
-
Aucun projet
+
{{ 'no_projects' }}
+ +
+

{{ 'labels' }}

+ + {{t.name}} + +
{{ 'no_labels' }}
+
+
+
+
@@ -34,18 +82,39 @@
-

Éditer votre profil

+

{{ 'edit_my_profile' }}

-
-
+
+
+

+ + {{activeProvider.name}} +

+
+
+
+ + {{ 'change_my_data' | translate }} + +

{{ 'once_your_data_are_up_to_date_' | translate }} {{ '_click_on_the_synchronization_button_opposite_' }} {{ 'or' | translate}} {{ '_disconnect_then_reconnect_' }} {{ '_for_your_changes_to_take_effect' | translate }}

+
+ +
+
+
+
diff --git a/app/assets/templates/dashboard/projects.html.erb b/app/assets/templates/dashboard/projects.html.erb index 4e464b939..6648b9525 100644 --- a/app/assets/templates/dashboard/projects.html.erb +++ b/app/assets/templates/dashboard/projects.html.erb @@ -9,12 +9,12 @@
-
Vous n'avez aucun projet.
+
{{ 'you_dont_have_any_projects' }}
-

{{project.name}}

{{project.author_id == currentUser.id ? 'Auteur' : 'Collaborateur'}} +

{{project.name}}

{{project.author_id == currentUser.id ? 'author' : 'collaborator' | translate}}
@@ -22,12 +22,9 @@
-

Description

+

{{ 'description' }}

- -
@@ -36,14 +33,14 @@
-

Machines et matériaux

+

{{ 'machines_and_materials' }}

-

Machines :

+

{{ 'machines' | translate }} :

  • {{m.name}}
-

Matériaux :

+

{{ 'materials' | translate }} :

  • {{c.name}}
@@ -54,7 +51,7 @@
-

Les collaborateurs

+

{{ 'collaborators' }}

  • diff --git a/app/assets/templates/dashboard/trainings.html.erb b/app/assets/templates/dashboard/trainings.html.erb new file mode 100644 index 000000000..e7282172d --- /dev/null +++ b/app/assets/templates/dashboard/trainings.html.erb @@ -0,0 +1,60 @@ +
    + +
    +
    + +
    + +
    + + +
    + +
    +
    +
    +

    {{ 'your_next_trainings' | translate }}

    +
    +
    +
      +
    • + {{r.reservable.name}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }} +
    • +
    +
    {{ 'no_trainings' }}
    +
    +
    +
    +
    +
    +
    +

    {{ 'your_previous_trainings' | translate }}

    +
    +
    +
      +
    • + {{r.reservable.name}} - {{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }} +
    • +
    +
    {{ 'no_trainings' }}
    +
    +
    +
    +
    +
    +
    +

    {{ 'your_approved_trainings' | translate }}

    +
    +
    +
      +
    • + {{t.name}} +
    • +
    +
    {{ 'no_trainings' }}
    +
    +
    +
    + +
    +
    diff --git a/app/assets/templates/events/_form.html.erb b/app/assets/templates/events/_form.html.erb index 26c028d36..43942f913 100644 --- a/app/assets/templates/events/_form.html.erb +++ b/app/assets/templates/events/_form.html.erb @@ -2,34 +2,34 @@
    -
    +
    - {{alert.msg}} + {{alert.msg}}
    - +
    - Titre est obligatoire + {{ 'title_is_required' }}
    - +
    - +
    - Choisir une image Modifier + {{ 'choose_a_picture' | translate }} {{ 'change' }}
    @@ -38,15 +38,15 @@
    - +
    - Description est obligatoire + {{ 'description_is_required' }}
    - +
    @@ -56,13 +56,13 @@
    {{file.attachment}}
    - Parcourir - Modifier + {{ 'browse' }} + {{ 'change' }}
    - Ajouter un nouveau fichier + {{ 'add_a_new_file' | translate }}
    @@ -80,41 +80,47 @@
    -

    Type d'évènement

    +

    {{ 'event_type' }}

    - + + + + + + + + +
    -

    Dates et horaires

    +

    {{ 'dates_and_opening_hours' }}

    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - et se terminera le + {{ '_and_ends_on' | translate }}
    - + @@ -179,30 +191,30 @@
    -

    Tarifs et disponibilités

    +

    {{ 'prices_and_availabilities' }}

    - +
    -
    +
    {{currencySymbol}}
    - 0 = gratuit + {{ '0_=_free' }}
    - +
    -
    +
    {{currencySymbol}}
    - +
    diff --git a/app/assets/templates/events/edit.html.erb b/app/assets/templates/events/edit.html.erb index 567220629..85b9b162e 100644 --- a/app/assets/templates/events/edit.html.erb +++ b/app/assets/templates/events/edit.html.erb @@ -9,7 +9,7 @@
    -

    Editer l'évènement

    +

    {{ 'edit_the_event' }}

    diff --git a/app/assets/templates/events/index.html.erb b/app/assets/templates/events/index.html.erb index f45e17024..9182191a4 100644 --- a/app/assets/templates/events/index.html.erb +++ b/app/assets/templates/events/index.html.erb @@ -7,13 +7,13 @@
    -

    Les Stages et ateliers du Fab Lab

    +

    {{ 'the_fablab_s_courses_and_workshops' }}

    @@ -28,17 +28,17 @@
    - +
    {{event.categories[0].name}}

    {{event.title}}

    -

    {{event.start_date | amDateFormat:'DD/MM'}} au {{event.end_date | amDateFormat:'DD/MM'}}

    +

    {{event.start_date | amDateFormat:'L'}} {{ 'to_date' }} {{event.end_date | amDateFormat:'L'}}

    -
    Plein tarif: {{event.amount}}€ / Tarif réduit: {{event.reduced_amount}}€
    +
    {{ 'full_price_' | translate }} {{event.amount | currency}} / {{ 'reduced_rate_' | translate }} {{event.reduced_amount | currency}}
    - +
    @@ -51,7 +51,7 @@
    diff --git a/app/assets/templates/events/modify_event_reservation_modal.html.erb b/app/assets/templates/events/modify_event_reservation_modal.html.erb new file mode 100644 index 000000000..108397ae1 --- /dev/null +++ b/app/assets/templates/events/modify_event_reservation_modal.html.erb @@ -0,0 +1,14 @@ + + + diff --git a/app/assets/templates/events/new.html.erb b/app/assets/templates/events/new.html.erb index cc1e1d15f..4bf8351e4 100644 --- a/app/assets/templates/events/new.html.erb +++ b/app/assets/templates/events/new.html.erb @@ -9,7 +9,7 @@
    -

    Ajouter un évènement

    +

    {{ 'add_an_event' }}

    diff --git a/app/assets/templates/events/show.html.erb b/app/assets/templates/events/show.html.erb index aab9e43c4..b4edb3b41 100644 --- a/app/assets/templates/events/show.html.erb +++ b/app/assets/templates/events/show.html.erb @@ -17,8 +17,8 @@ @@ -35,20 +35,20 @@ {{event.title}}
    -

    Description de l'évènement

    +

    {{ 'event_description' }}

    - +
    {{event.event_files_attributes.length}} -

    Documents à télécharger

    +

    {{ 'downloadable_documents' }}

    - +
    + +
    -

    Informations

    +

    {{ 'informations_and_booking' }}

    - +
    {{event.categories[0].name}}
    -
    Dates :
    -
    Début: {{event.start_date | amDateFormat:'DD/MM/YYYY'}}
    Fin: {{event.end_date | amDateFormat:'DD/MM/YYYY'}}
    -
    Horaires :
    -
    Toute la journée
    -
    De {{event.start_date | amDateFormat:'HH:mm'}} à {{event.end_date | amDateFormat:'HH:mm'}}
    +
    {{ 'dates' | translate }}
    +
    {{ 'beginning' | translate }} {{event.start_date | amDateFormat:'L'}}
    {{ 'ending' | translate }} {{event.end_date | amDateFormat:'L'}}
    +
    {{ 'opening_hours' | translate }}
    +
    {{ 'all_day' }}
    +
    {{ 'from_time' | translate }} {{event.start_date | amDateFormat:'LT'}} {{ 'to_time' | translate }} {{event.end_date | amDateFormat:'LT'}}
    -
    Plein tarif : {{ event.amount }} €
    -
    Tarif réduit* : {{ event.reduced_amount }} €
    +
    {{ 'full_price_' | translate }} {{ event.amount | currency}}
    +
    {{ 'reduced_rate*' | translate }} {{ event.reduced_amount | currency}}
    -
    Places disponibles: {{event.nb_total_places}}
    +
    {{ 'tickets_still_availables' | translate }} {{event.nb_free_places}}
    +
    {{ 'sold_out' }}
    -
    Entrée libre
    +
    {{ 'free_entry' }}
    + +
    +
    + +
    +
    +
    + +
    + {{ 'ticket' | translate:{NUMBER:reserve.nbReservePlaces}:"messageformat" }} +
    +
    +
    + +
    + {{ 'ticket' | translate:{NUMBER:reserve.nbReserveReducedPlaces}:"messageformat" }} +
    + +
    + +
    + + +
    +
    +
    + +
    + + + +
    {{ 'thank_you_your_payment_has_been_successfully_registered' | translate }}
    + {{ 'you_can_find_your_reservation_s_details_on_your_' | translate }} {{ 'dashboard' }} +
    +
    +
    {{ 'you_booked_(DATE)' | translate:{DATE:(reservation.created_at | amDateFormat:'L LT')} }}
    +
    {{ 'full_price_' | translate }} {{reservation.nb_reserve_places}} {{ 'ticket' | translate:{NUMBER:reservation.nb_reserve_places}:"messageformat" }}
    +
    {{ 'reduced_rate*' | translate }} {{reservation.nb_reserve_reduced_places}} {{ 'ticket' | translate:{NUMBER:reservation.nb_reserve_reduced_places}:"messageformat" }}
    + +
    + + + + +
    +
    + + + +
    - - diff --git a/app/assets/templates/home.html.erb b/app/assets/templates/home.html.erb index a3e0b331f..9cefb005a 100644 --- a/app/assets/templates/home.html.erb +++ b/app/assets/templates/home.html.erb @@ -1,81 +1,94 @@ -
    + +
    + +
    -

    Les derniers projets documentés

    +

    {{ 'latest_documented_projects' }}

    - - + + + - - + +
    - -
    + +
    -

    Les derniers tweets

    +

    {{ 'latest_tweets' }}

    - -
      -
    • -
    • + +
        +
      • +
    -

    Derniers membres inscrits

    + +

    {{ 'latest_registered_members' }}

    - +
    - +
    + + + + {{member.name}} + +
    + +
    + + +
    - +
    - +
    + +
    + +
    -

    Les prochains ateliers et stages du fablab Tous les événements

    +

    {{ 'fablab_s_next_courses_and_workshops' | translate }} {{ 'every_events' | translate }}

    -
    +
    -
    +
    -
    +
    @@ -93,25 +106,31 @@
    -
    Du {{event.start_date | amDateFormat:'DD/MM'}} au {{event.end_date | amDateFormat:'DD/MM'}}
    +
    {{ 'from_date_to_date' | translate:{START:(event.start_date | amDateFormat:'L'), END:(event.end_date | amDateFormat:'L')} }}
    +
    {{ 'on_the_date' | translate:{DATE:(event.start_date | amDateFormat:'L')} }}
    -
    Toute la journéeDe {{event.start_date | date:'HH:mm'}} à {{event.end_date | date:'HH:mm'}}
    +
    + {{ 'all_day' }} + {{ 'from_time_to_time' | translate:{START:(event.start_date | amDateFormat:'LT'), END:(event.end_date | amDateFormat:'LT')} }} +
    - Entrée Libre - Entrée Gratuite{{event.amount}} € Plein tarif
    {{event.reduced_amount}} € Tarif réduit
    -
    Événement complet.
    + {{ 'free_entry' }} + {{ 'free_admission' }} + {{event.amount | currency}} {{ 'full_price' | translate }} +
    {{event.reduced_amount | currency}} {{ 'reduced_rate' | translate }}
    +
    {{ 'event_full' }}
    -
    Consulter
    +
    {{ 'consult' }}
    @@ -120,7 +139,3 @@
  • - - -
    - diff --git a/app/assets/templates/machines/_form.html.erb b/app/assets/templates/machines/_form.html.erb index c2199c55c..5eec4d43d 100644 --- a/app/assets/templates/machines/_form.html.erb +++ b/app/assets/templates/machines/_form.html.erb @@ -2,57 +2,65 @@ -
    -
    +
    +
    - {{alert.msg}} + {{alert.msg}}
    - +
    - Le Nom est obligatoire. + {{ 'name_is_required' }}
    - +
    -
    - -
    -
    - -
    -
    - Ajouter un visuel Modifier - - Supprimer -
    +
    + +
    +
    + +
    +
    + + {{ 'add_an_illustration' | translate }} + {{ 'change' }} + + + {{ 'delete' }} +
    - +
    - La Description est obligatoire. + {{ 'description_is_required' }}
    - +
    - Les Caractéristiques techniques sont obligatoires. + {{ 'technical_specifications_are_required' }}
    - +
    @@ -62,20 +70,20 @@
    {{file.attachment}}
    - Joindre un fichier - Modifier + {{ 'attach_a_file' }} + {{ 'change' }}
    - Ajouter une pièce jointe + {{ 'add_an_attachment' | translate }}
    diff --git a/app/assets/templates/machines/edit.html.erb b/app/assets/templates/machines/edit.html.erb index 29af9bc3f..1f374a689 100644 --- a/app/assets/templates/machines/edit.html.erb +++ b/app/assets/templates/machines/edit.html.erb @@ -15,9 +15,7 @@
    -
    - Annuler -
    +
    {{ 'cancel' }}
    diff --git a/app/assets/templates/machines/index.html.erb b/app/assets/templates/machines/index.html.erb index 5d8a31e15..f6804be64 100644 --- a/app/assets/templates/machines/index.html.erb +++ b/app/assets/templates/machines/index.html.erb @@ -7,13 +7,13 @@
    -

    Les machines du FabLab

    +

    {{ 'the_fablab_s_machines' }}

    @@ -26,7 +26,7 @@
    - +
    @@ -40,9 +40,14 @@ -
    + diff --git a/app/assets/templates/projects/show.html.erb b/app/assets/templates/projects/show.html.erb index 592b9f3e0..f2989bd50 100644 --- a/app/assets/templates/projects/show.html.erb +++ b/app/assets/templates/projects/show.html.erb @@ -9,15 +9,16 @@
    -

    {{ project.name }} Brouillon

    +

    {{ project.name }} {{ 'rough_draft' }}

    @@ -33,13 +34,13 @@ {{project.name}}
    -

    Description du projet

    +

    {{ 'project_description' }}

    -

    Étape {{$index+1}} : {{step.title}}

    +

    {{ 'step_N' | translate:{INDEX:$index+1} }} : {{step.title}}

    {{step.title}} @@ -55,11 +56,8 @@
    -
    - +
    +
    @@ -72,8 +70,8 @@
    - - posté le {{project.created_at | amDateFormat: 'Do MMMM YYYY'}} + + {{ 'posted_on_' | translate }} {{project.created_at | amDateFormat: 'LL'}}
    @@ -81,7 +79,7 @@
    {{project.project_caos_attributes.length}} -

    Fichier CAO à télécharger

    +

    {{ 'CAD_file_to_download' | translate:{ COUNT: project.project_caos_attributes.length }:"messageformat" }}

      @@ -94,7 +92,7 @@
      {{project.machines.length}} -

      Machines et matériaux

      +

      {{ 'machines_and_materials' }}

        @@ -113,7 +111,7 @@
        {{project.project_users.length}} -

        Les collaborateurs

        +

        {{ 'collaborators' }}

    diff --git a/app/assets/templates/shared/_admin_form.html b/app/assets/templates/shared/_admin_form.html new file mode 100644 index 000000000..c72cfda30 --- /dev/null +++ b/app/assets/templates/shared/_admin_form.html @@ -0,0 +1,120 @@ +
    +
    +
    + + +
    + +
    +
    + + +
    + {{ 'pseudonym_is_required' }} +
    + +
    +
    + + +
    + {{ 'surname_is_required' }} +
    + +
    +
    + + +
    + {{ 'first_name_is_required' }} +
    + +
    +
    + + +
    + {{ 'email_is_required' }} +
    + +
    +
    + + +
    +
    + +
    +
    + + + +
    +
    + +
    +
    + + +
    +
    +
    + +
    diff --git a/app/assets/templates/shared/_member_form.html.erb b/app/assets/templates/shared/_member_form.html.erb index 5d60bc73f..ba7b55c16 100644 --- a/app/assets/templates/shared/_member_form.html.erb +++ b/app/assets/templates/shared/_member_form.html.erb @@ -1,4 +1,4 @@ -{{alert.msg}} +{{alert.msg}} @@ -16,9 +16,20 @@
    - Ajouter un avatarModifier - - + + {{ 'add_an_avatar' }} + {{ 'change' }} + + + +
    @@ -28,21 +39,39 @@
    - Le genre est obligatoire + {{ 'gender_is_required' }}
    - +
    - Le pseudo est obligatoire + {{ 'pseudonym_is_required' }}
    @@ -50,48 +79,86 @@
    - +
    - Le nom est obligatoire + {{ 'surname_is_required' }}
    - +
    - Le prénom est obligatoire + {{ 'first_name_is_required' }}
    - +
    - L'email est obligatoire + {{ 'email_address_is_required' }}
    -
    - +
    +
    - +
    - Le mot de passe est obligatoire - Le mot de passe est trop court (au moins 8 caractères) + {{ 'password_is_required' }} + {{ 'password_is_too_short_(minimum_8_characters)' }}
    - +
    - Le mot de passe de confirmation est obligatoire - Le mot de passe de confirmation est trop court (au moins 8 caractères) + {{ 'confirmation_of_password_is_required' }} + {{ 'confirmation_of_password_is_too_short_(minimum_8_characters)' }} + {{ 'confirmation_mismatch_with_password' }}
    @@ -102,40 +169,68 @@ class="form-control" name="user[profile_attributes][birthday]" ng-model="user.profile.birthday" - datepicker-popup="dd/MM/yyyy" + uib-datepicker-popup="{{datePicker.format}}" datepicker-options="datePicker.options" is-open="datePicker.opened" - placeholder="Date de naissance" + placeholder="{{ 'date_of_birth' | translate }}" ng-click="openDatePicker($event)" + ng-disabled="preventField['profile.birthday'] && user.profile.birthday && !userForm['user[profile_attributes][birthday]'].$dirty" required/>
    - La date de naissance est obligatoire + {{ 'date_of_birth_is_required' }}
    - - + +
    - +
    - Le numéro de téléphone est obligatoire. + {{ 'phone_number_is_required' }}
    - - + +
    - - + +
    diff --git a/app/assets/templates/shared/_member_select.html.erb b/app/assets/templates/shared/_member_select.html.erb index 57b45b043..3105984b1 100644 --- a/app/assets/templates/shared/_member_select.html.erb +++ b/app/assets/templates/shared/_member_select.html.erb @@ -1,11 +1,16 @@
    -

    Sélectionnez un membre

    +

    {{ 'select_a_member' }}

    - + + + + + + + + {{member}}
    diff --git a/app/assets/templates/shared/_partner_new_modal.html b/app/assets/templates/shared/_partner_new_modal.html new file mode 100644 index 000000000..03d1f9126 --- /dev/null +++ b/app/assets/templates/shared/_partner_new_modal.html @@ -0,0 +1,35 @@ + + + diff --git a/app/assets/templates/shared/about.html.erb b/app/assets/templates/shared/about.html.erb index 0d602806f..a4c2337e5 100644 --- a/app/assets/templates/shared/about.html.erb +++ b/app/assets/templates/shared/about.html.erb @@ -2,31 +2,22 @@
    -

    Imaginer, Fabriquer,
    Partager au Fab Lab

    +

    -

    Le Fab Lab est un atelier de fabrication numérique où l’on peut utiliser des machines de découpe, des imprimantes 3D,… permettant de travailler sur des matériaux variés : plastique, bois, carton, vinyle, … afin de créer toute sorte d’objet grâce à la conception assistée par ordinateur ou à l’électronique. Mais le Fab Lab est aussi un lieu d’échange de compétences technique.

    -

    - Le Fab Lab est un espace permanent : ouvert à tous, il offre la possibilité de réaliser des objets soi-même, de partager ses compétences et d’apprendre au contact des médiateurs du Fab Lab et des autres usagers.

    -

    - La formation au Fab Lab s’appuie sur des projets et le partage de connaissances : vous devez prendre part à la capitalisation des connaissances et à l’instruction des autres utilisateurs. + +

    + {{ 'read_the_fablab_policy' }}

    -

    Vos contacts au Fab Lab

    -
    -
    Manager Fab Lab :
    -
    @email
    -
    Responsable médiation :
    -
    @email
    -
    Animateur scientifique :
    -
    @email
    -
    +

    {{ 'your_fablab_s_contacts' }}

    +
    -
    \ No newline at end of file +
    diff --git a/app/assets/templates/shared/confirm_modal.html.erb b/app/assets/templates/shared/confirm_modal.html.erb index b062fe5e0..250bb78c1 100644 --- a/app/assets/templates/shared/confirm_modal.html.erb +++ b/app/assets/templates/shared/confirm_modal.html.erb @@ -1,11 +1,11 @@ diff --git a/app/assets/templates/shared/confirm_modify_slot_modal.html.erb b/app/assets/templates/shared/confirm_modify_slot_modal.html.erb new file mode 100644 index 000000000..92bd20d3d --- /dev/null +++ b/app/assets/templates/shared/confirm_modify_slot_modal.html.erb @@ -0,0 +1,14 @@ + + + diff --git a/app/assets/templates/shared/deviseModal.html.erb b/app/assets/templates/shared/deviseModal.html.erb index 4679e15d8..89ee86a48 100644 --- a/app/assets/templates/shared/deviseModal.html.erb +++ b/app/assets/templates/shared/deviseModal.html.erb @@ -1,10 +1,12 @@

    - Vous n'êtes pas inscrit au FAB LAB ?
    - Créer un compte
    + {{ 'not_registered_to_the_fablab' }} +
    + {{ 'create_an_account' }}

    diff --git a/app/assets/templates/shared/header.html.erb b/app/assets/templates/shared/header.html.erb index c7c1c6143..8dc4e15e1 100644 --- a/app/assets/templates/shared/header.html.erb +++ b/app/assets/templates/shared/header.html.erb @@ -1,17 +1,20 @@
    @@ -22,23 +25,35 @@
  • {{notifications.length}}
  • -
    diff --git a/app/assets/templates/shared/passwordNewModal.html.erb b/app/assets/templates/shared/passwordNewModal.html.erb index 2b220d428..c91595708 100644 --- a/app/assets/templates/shared/passwordNewModal.html.erb +++ b/app/assets/templates/shared/passwordNewModal.html.erb @@ -1,10 +1,10 @@
    diff --git a/app/assets/templates/shared/signalAbuseModal.html.erb b/app/assets/templates/shared/signalAbuseModal.html.erb new file mode 100644 index 000000000..093ef0002 --- /dev/null +++ b/app/assets/templates/shared/signalAbuseModal.html.erb @@ -0,0 +1,41 @@ + + + diff --git a/app/assets/templates/shared/signupModal.html.erb b/app/assets/templates/shared/signupModal.html.erb index 8ede78971..faa60bfd4 100644 --- a/app/assets/templates/shared/signupModal.html.erb +++ b/app/assets/templates/shared/signupModal.html.erb @@ -1,34 +1,51 @@
    - {{alert.msg}} + {{alert.msg}}
    - Sexe est obligatoire + {{ 'gender_is_required'}}
    - - Prénom est obligatoire + + {{ 'first_name_is_required' }}
    - - Nom est obligatoire + + {{ 'surname_is_required' }}
    @@ -36,9 +53,14 @@
    - +
    - Pseudo est obligatoire + {{ 'pseudonym_is_required' }}
    @@ -46,9 +68,14 @@
    - +
    - Email est obligatoire + {{ 'email_is_required' }}
    @@ -56,10 +83,16 @@
    - +
    - Mot de passe est obligatoire - Mot de passe est trop court (au moins 8 caractères) + {{ 'password_is_required' }} + {{ 'password_is_too_short_(minimum_8_characters)' }}
    @@ -67,10 +100,16 @@
    - +
    - Mot de passe de confirmation est obligatoire - Mot de passe ne concorde pas avec la confirmation + {{ 'password_confirmation_is_required' }} + {{ 'password_does_not_match_with_confirmation' }}
    @@ -78,10 +117,10 @@
    - Le profil utilisateur est obligatoire + {{ 'user_s_profile_is_required' }}
    @@ -93,14 +132,14 @@ class="form-control" name="birthday" ng-model="user.profile_attributes.birthday" - datepicker-popup="{{datePicker.format}}" + uib-datepicker-popup="{{datePicker.format}}" datepicker-options="datePicker.options" is-open="datePicker.opened" - placeholder="Date de naissance" + placeholder="{{ 'birth_date' | translate }}" ng-click="openDatePicker($event)" required/> - La date de naissance est obligatoire + {{ 'birth_date_is_required' }} @@ -108,26 +147,44 @@
    - +
    - Le numéro de téléphone est obligatoire. + {{ 'phone_number_is_required' }}
    - J'autorise les utilisateurs du Fab Lab inscrits sur le site à me contacter + + {{ 'i_authorize_Fablab_users_registered_on_the_site_to_contact_me' }}
    -
    +
    - J'accepte et J'ai Lu la charte d'utilisation du Fab Lab + + {{ 'i_ve_read_and_i_accept_' }} + {{ '_the_fablab_policy' }}
    +
    + +
    \ No newline at end of file diff --git a/app/assets/templates/shared/valid_reservation_modal.html.erb b/app/assets/templates/shared/valid_reservation_modal.html.erb new file mode 100644 index 000000000..1c2483cd6 --- /dev/null +++ b/app/assets/templates/shared/valid_reservation_modal.html.erb @@ -0,0 +1,14 @@ + + + diff --git a/app/assets/templates/stripe/payment_modal.html.erb b/app/assets/templates/stripe/payment_modal.html.erb new file mode 100644 index 000000000..a49f0df8c --- /dev/null +++ b/app/assets/templates/stripe/payment_modal.html.erb @@ -0,0 +1,58 @@ +
    + + + + + + +
    diff --git a/app/assets/templates/trainings/reserve.html.erb b/app/assets/templates/trainings/reserve.html.erb new file mode 100644 index 000000000..951ba2b6d --- /dev/null +++ b/app/assets/templates/trainings/reserve.html.erb @@ -0,0 +1,197 @@ +
    +
    +
    +
    + +
    +
    +
    +
    +

    {{ 'trainings_planning' }}

    +
    + +
    +
    +
    + + +
    +
    +
    + +
    + + +
    + +
    + +
    + + +
    +
    +

    {{ 'summary' }}

    +
    +
    +

    <%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %> + {{ 'select_a_slot_in_the_calendar' | translate }}

    +
    + +
    +
    {{ 'you_ve_just_selected_the_slot' }}
    + + +
    +
    +
    {{ 'datetime_to_time' | translate:{START_DATETIME:(selectedTraining.start | amDateFormat:'LLLL'), END_TIME:(selectedTraining.end | amDateFormat:'LT')} }}
    +
    {{ 'training_cost_' | translate }} {{selectedTrainingAmount | currency}}
    +
    + + +
    +
    + +
    + + + + +
    +

    {{ 'to_benefit_from_attractive_prices_and_a_free_training' }}

    +
    +

    ou

    +
    + +
    +
    {{ 'you_ve_just_selected_a_' | translate }}
    {{ '_subscription' }} :
    +
    +
    +
    {{ selectedPlan | humanReadablePlanName }}
    +
    {{ 'subscription_cost' | translate }} {{selectedPlan.amount | currency}}
    +
    +
    +
    +
    + +
    + + + + +
    +
    {{ 'you_have_settled_the_training' | translate }}
    {{paidTraining.training.name}} : +
    + +
    + {{ 'datetime_to_time' | translate:{START_DATETIME:(paidTraining.start | amDateFormat:'LLLL'), END_TIME:(paidTraining.end | amDateFormat:'LT') } }} +
    {{ 'training_cost_' | translate }} {{paidTraining.training.amount | currency}}
    +
    + + +
    +
    {{ 'you_have_settled_a_' | translate }}
    {{ '_subscription' }} :
    + +
    + {{selectedPlan | humanReadablePlanName }} +
    {{ 'subscription_cost' | translate }} {{selectedPlan.amount | currency}}
    +
    +
    + +
    {{ 'total_' | translate }} {{amountTotal | currency}}
    +
    {{ 'thank_you_your_payment_has_been_successfully_registered' | translate }}
    + {{ 'your_invoice_will_be_available_soon_from_your_' | translate }} {{ 'dashboard' }} +
    + +
    + + +
    + +
    +
    +

    {{ 'summary' }}

    +
    +
    +
    {{ 'i_want_to_change_the_following_reservation' }}
    + +
    +
    +
    {{ 'datetime_to_time' | translate:{START_DATETIME:(slotToModify.start | amDateFormat:'LLLL'), END_TIME:(slotToModify.end | amDateFormat:'LT') } }}
    +
    + +
    + +
    +

    <%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %> + {{ 'select_a_new_slot_in_the_calendar' | translate }}

    +
    + +
    +
    +
    {{ 'datetime_to_time' | translate:{START_DATETIME:(slotToPlace.start | amDateFormat:'LLLL'), END_TIME:(slotToPlace.end | amDateFormat:'LT') } }}
    +
    + +
    +
    + + + +
    +
    {{ 'your_booking_slot_was_successfully_moved_from_' }}
    + +
    +
    +
    {{ 'datetime_to_time' | translate:{START_DATETIME:(modifiedSlots.oldReservedSlot.start | amDateFormat:'LLLL'), END_TIME:(modifiedSlots.oldReservedSlot.end | amDateFormat:'LT') } }}
    +
    +
    + +

    {{ 'to_date' }}

    + +
    +
    +
    {{ 'datetime_to_time' | translate:{START_DATETIME:(modifiedSlots.newReservedSlot.start | amDateFormat:'LLLL'), END_TIME:(modifiedSlots.newReservedSlot.end | amDateFormat:'LT') } }}
    +
    +
    +
    + +
    + + +

    + + +

    +
    + + +

    + + +

    +
    + +
    + + +
    diff --git a/app/controllers/api/abuses_controller.rb b/app/controllers/api/abuses_controller.rb new file mode 100644 index 000000000..4ffe383e0 --- /dev/null +++ b/app/controllers/api/abuses_controller.rb @@ -0,0 +1,22 @@ +class API::AbusesController < API::ApiController + before_action :authenticate_user!, except: :create + + def index + @groups = Group.all + end + + def create + @abuse = Abuse.new(abuse_params) + if @abuse.save + render status: :created + else + render json: @abuse.errors.full_messages, status: :unprocessable_entity + end + end + + private + + def abuse_params + params.require(:abuse).permit(:signaled_type, :signaled_id, :first_name, :last_name, :email, :message) + end +end diff --git a/app/controllers/api/admins_controller.rb b/app/controllers/api/admins_controller.rb new file mode 100644 index 000000000..7f254bda7 --- /dev/null +++ b/app/controllers/api/admins_controller.rb @@ -0,0 +1,50 @@ +class API::AdminsController < API::ApiController + before_action :authenticate_user! + + def index + authorize :admin + @admins = User.admins + end + + def create + authorize :admin + generated_password = Devise.friendly_token.first(8) + @admin = User.new(admin_params.merge(password: generated_password)) + @admin.send :set_slug + + # we associate any random group to the admin as it is mandatory for users but useless for admins + @admin.group = Group.first + + # if the authentication is made through an SSO, generate a migration token + unless AuthProvider.active.providable_type == DatabaseProvider.name + @admin.generate_auth_migration_token + end + + if @admin.save(validate: false) + @admin.send_confirmation_instructions + @admin.add_role(:admin) + @admin.remove_role(:member) + UsersMailer.delay.notify_user_account_created(@admin, generated_password) + render :create, status: :created + else + render json: @admin.errors.full_messages, status: :unprocessable_entity + end + end + + def destroy + @admin = User.admins.find(params[:id]) + if current_user.is_admin? and @admin != current_user + @admin.destroy + head :no_content + else + head :unauthorized + end + end + + private + + def admin_params + params.require(:admin).permit(:username, :email, profile_attributes: [:first_name, :last_name, :gender, + :birthday, :phone, address_attributes: [:address]]) + end +end diff --git a/app/controllers/api/auth_providers_controller.rb b/app/controllers/api/auth_providers_controller.rb new file mode 100644 index 000000000..2e5cdad48 --- /dev/null +++ b/app/controllers/api/auth_providers_controller.rb @@ -0,0 +1,67 @@ +class API::AuthProvidersController < API::ApiController + + before_action :set_provider, only: [:show, :update, :destroy] + + def index + @providers = policy_scope(AuthProvider) + end + + def create + authorize AuthProvider + @provider = AuthProvider.new(provider_params) + if @provider.save + render :show, status: :created, location: @provider + else + render json: @provider.errors, status: :unprocessable_entity + end + end + + def update + authorize AuthProvider + if @provider.update(provider_params) + render :show, status: :ok, location: @provider + else + render json: @provider.errors, status: :unprocessable_entity + end + end + + def show + authorize AuthProvider + end + + def destroy + authorize AuthProvider + @provider.destroy + head :no_content + end + + def mapping_fields + authorize AuthProvider + render :mapping_fields, status: :ok + end + + def active + authorize AuthProvider + @provider = AuthProvider.active + end + + private + + def set_provider + @provider = AuthProvider.find(params[:id]) + end + + def provider_params + if params['auth_provider']['providable_type'] == DatabaseProvider.name + params.require(:auth_provider).permit(:name, :providable_type) + elsif params['auth_provider']['providable_type'] == OAuth2Provider.name + params.require(:auth_provider).permit(:name, :providable_type, providable_attributes: [ + :id, :base_url, :token_endpoint, :authorization_endpoint, :profile_url, :client_id, :client_secret, + o_auth2_mappings_attributes: [ + :id, :local_model, :local_field, :api_field, :api_endpoint, :api_data_type, :_destroy + ] + ]) + end + end + +end \ No newline at end of file diff --git a/app/controllers/api/availabilities_controller.rb b/app/controllers/api/availabilities_controller.rb new file mode 100644 index 000000000..51f3c94ea --- /dev/null +++ b/app/controllers/api/availabilities_controller.rb @@ -0,0 +1,161 @@ +class API::AvailabilitiesController < API::ApiController + before_action :authenticate_user! + before_action :set_availability, only: [:show, :update, :destroy, :reservations] + respond_to :json + + ## machine availabilities are divided in multiple slots of 60 minutes + SLOT_DURATION = 60 + + def index + authorize Availability + @availabilities = Availability.includes(:machines,:tags,:trainings).where.not(available_type: 'event') + end + + def show + authorize Availability + end + + def create + authorize Availability + @availability = Availability.new(availability_params) + if @availability.save + render :show, status: :created, location: @availability + else + render json: @availability.errors, status: :unprocessable_entity + end + end + + def update + authorize Availability + if @availability.update(availability_params) + render :show, status: :ok, location: @availability + else + render json: @availability.errors, status: :unprocessable_entity + end + end + + def destroy + authorize Availability + if @availability.safe_destroy + head :no_content + else + head :unprocessable_entity + end + end + + def machine + if params[:member_id] + @user = User.find(params[:member_id]) + else + @user = current_user + end + @machine = Machine.find(params[:machine_id]) + @slots = [] + @reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @machine.class.to_s, @machine.id).joins(:slots).where('slots.start_at > ?', Time.now) + if @user.is_admin? + @availabilities = @machine.availabilities.where("end_at > ? AND available_type = 'machines'", Time.now) + else + end_at = 1.month.since + end_at = 3.months.since if is_subscription_year(@user) + @availabilities = @machine.availabilities.includes(:availability_tags).where("end_at > ? AND end_at < ? AND available_type = 'machines'", Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil])) + end + @availabilities.each do |a| + ((a.end_at - a.start_at)/SLOT_DURATION.minutes).to_i.times do |i| + if (a.start_at + (i * SLOT_DURATION).minutes) > Time.now + slot = Slot.new(start_at: a.start_at + (i*SLOT_DURATION).minutes, end_at: a.start_at + (i*SLOT_DURATION).minutes + SLOT_DURATION.minutes, availability_id: a.id, machine: @machine, title: '') + slot = verify_machine_is_reserved(slot, @reservations) + @slots << slot + end + end + end + end + + def trainings + if params[:member_id] + @user = User.find(params[:member_id]) + else + @user = current_user + end + @slots = [] + @reservations = @user.reservations.where("reservable_type = 'Training'").joins(:slots).where('slots.start_at > ?', Time.now) + if @user.is_admin? + @availabilities = Availability.trainings.where('start_at > ?', Time.now) + else + end_at = 1.month.since + end_at = 3.months.since if can_show_slot_plus_three_months(@user) + @availabilities = Availability.trainings.includes(:availability_tags).where('start_at > ? AND start_at < ?', Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil])) + end + @availabilities.each do |a| + a = verify_training_is_reserved(a, @reservations) + end + end + + def reservations + authorize Availability + @reservation_slots = @availability.slots.includes(reservation: [user: [:profile]]).order('slots.start_at ASC') + end + + private + def set_availability + @availability = Availability.find(params[:id]) + end + + def availability_params + params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places, machine_ids: [], training_ids: [], tag_ids: [], + :machines_attributes => [:id, :_destroy]) + end + + def is_reserved(start_at, reservations) + is_reserved = false + reservations.each do |r| + r.slots.each do |s| + is_reserved = true if s.start_at == start_at + end + end + is_reserved + end + + def verify_machine_is_reserved(slot, reservations) + user = current_user + reservations.each do |r| + r.slots.each do |s| + if s.start_at == slot.start_at and s.canceled_at == nil + slot.id = s.id + slot.is_reserved = true + slot.title = t('availabilities.not_available') + slot.can_modify = true if user.is_admin? + slot.reservation = r + end + if s.start_at == slot.start_at and r.user == user and s.canceled_at == nil + slot.title = t('availabilities.i_ve_reserved') + slot.can_modify = true + slot.is_reserved_by_current_user = true + end + end + end + slot + end + + def verify_training_is_reserved(availability, reservations) + user = current_user + reservations.each do |r| + r.slots.each do |s| + if s.start_at == availability.start_at and s.canceled_at == nil and availability.trainings.first.id == r.reservable_id + availability.slot_id = s.id + availability.is_reserved = true + availability.can_modify = true if r.user == user + end + end + end + availability + end + + def can_show_slot_plus_three_months(user) + # member must have validated at least 1 training and must have a valid yearly subscription. + user.trainings.size > 0 and is_subscription_year(user) + end + + def is_subscription_year(user) + user.subscription and user.subscription.plan.interval == 'year' and user.subscription.expired_at >= Time.now + end +end diff --git a/app/controllers/api/credits_controller.rb b/app/controllers/api/credits_controller.rb new file mode 100644 index 000000000..df5664626 --- /dev/null +++ b/app/controllers/api/credits_controller.rb @@ -0,0 +1,47 @@ +class API::CreditsController < API::ApiController + before_action :authenticate_user! + before_action :set_credit, only: [:show, :update, :destroy] + + def index + authorize Credit + if params + @credits = Credit.includes(:creditable).where(params.permit(:creditable_type)) + else + @credits = Credit.includes(:creditable).all + end + end + + def create + authorize Credit + @credit = Credit.new(credit_params) + if @credit.save + render :show, status: :created, location: @credit + else + render json: @credit.errors, status: :unprocessable_entity + end + end + + def update + authorize Credit + if @credit.update(credit_params) + render :show, status: :ok, location: @credit + else + render json: @credit.errors, status: :unprocessable_entity + end + end + + def destroy + authorize Credit + @credit.destroy + head :no_content + end + + private + def set_credit + @credit = Credit.find(params[:id]) + end + + def credit_params + params.require(:credit).permit! + end +end diff --git a/app/controllers/api/custom_assets_controller.rb b/app/controllers/api/custom_assets_controller.rb new file mode 100644 index 000000000..681eb39a2 --- /dev/null +++ b/app/controllers/api/custom_assets_controller.rb @@ -0,0 +1,48 @@ +class API::CustomAssetsController < API::ApiController + before_action :authenticate_user!, only: [:index, :update, :create, :destroy] + before_action :set_custom_asset, only: [:show, :update, :destroy] + + def index + #TODO GET /api/custom_assets/ + end + + # PUT /api/custom_assets/1/ + def update + authorize CustomAsset + if @custom_asset.update(custom_asset_params.permit!) + render :show, status: :ok, location: @custom_asset + else + render json: @custom_asset.errors, status: :unprocessable_entity + end + end + + # POST /api/custom_assets/ + def create + authorize CustomAsset + @custom_asset = CustomAsset.new(custom_asset_params.permit!) + if @custom_asset.save + render :show, status: :created, location: @custom_asset + else + render json: @custom_asset.errors, status: :unprocessable_entity + end + end + + # GET /api/custom_assets/1/ + def show + end + + def destroy + #TODO DELETE /api/custom_assets/1/ + end + + private + def set_custom_asset + @custom_asset = CustomAsset.find_by(name: params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def custom_asset_params + params.required(:custom_asset).permit(:name, custom_asset_file_attributes: [:attachment]) + end + +end \ No newline at end of file diff --git a/app/controllers/api/events_controller.rb b/app/controllers/api/events_controller.rb index 76dfe85cb..d24573eee 100644 --- a/app/controllers/api/events_controller.rb +++ b/app/controllers/api/events_controller.rb @@ -39,7 +39,7 @@ class API::EventsController < API::ApiController def destroy authorize Event - if @event.destroy + if @event.safe_destroy head :no_content else head :unprocessable_entity diff --git a/app/controllers/api/feeds_controller.rb b/app/controllers/api/feeds_controller.rb index 720d5d8c8..698a39cba 100644 --- a/app/controllers/api/feeds_controller.rb +++ b/app/controllers/api/feeds_controller.rb @@ -8,7 +8,8 @@ class API::FeedsController < API::ApiController else limit = 3 end - @tweet_news = Feed.twitter.user_timeline(ENV['TWITTER_NAME'], {count: limit}) + from_account = Setting.find_by(name: 'twitter_name').try(:value) || ENV['TWITTER_NAME'] + @tweet_news = Feed.twitter.user_timeline(from_account, {count: limit}) end end diff --git a/app/controllers/api/groups_controller.rb b/app/controllers/api/groups_controller.rb index 77c8e2248..6d68167c3 100644 --- a/app/controllers/api/groups_controller.rb +++ b/app/controllers/api/groups_controller.rb @@ -1,5 +1,40 @@ class API::GroupsController < API::ApiController + before_action :authenticate_user!, except: :index + def index @groups = Group.all end + + def create + authorize Group + @group = Group.new(group_params) + if @group.save + render status: :created + else + render json: @group.errors.full_messages, status: :unprocessable_entity + end + end + + def update + authorize Group + @group = Group.find(params[:id]) + if @group.update(group_params) + render status: :ok + else + render json: @group.errors.full_messages, status: :unprocessable_entity + end + end + + def destroy + @group = Group.find(params[:id]) + authorize @group + @group.destroy + head :no_content + end + + private + + def group_params + params.require(:group).permit(:name) + end end diff --git a/app/controllers/api/invoices_controller.rb b/app/controllers/api/invoices_controller.rb new file mode 100644 index 000000000..de79dafce --- /dev/null +++ b/app/controllers/api/invoices_controller.rb @@ -0,0 +1,35 @@ +class API::InvoicesController < API::ApiController + before_action :authenticate_user! + before_action :set_invoice, only: [:show, :download] + + def index + authorize Invoice + @invoices = Invoice.includes(:avoir, :invoiced, invoice_items: [:subscription, :invoice_item], user: [:profile, :trainings]).all.order('reference DESC') + end + + def download + authorize @invoice + send_file File.join(Rails.root, @invoice.file), :type => 'application/pdf', :disposition => 'attachment' + end + + # only for create avoir + def create + authorize Invoice + invoice = Invoice.only_invoice.find(avoir_params[:invoice_id]) + @avoir = invoice.build_avoir(avoir_params) + if @avoir.save + render :avoir, status: :created + else + render json: @avoir.errors, status: :unprocessable_entity + end + end + + private + def avoir_params + params.require(:avoir).permit(:invoice_id, :avoir_date, :avoir_mode, :subscription_to_expire, :description, :invoice_items_ids => []) + end + + def set_invoice + @invoice = Invoice.find(params[:id]) + end +end diff --git a/app/controllers/api/machines_controller.rb b/app/controllers/api/machines_controller.rb index 067388dfb..0d5f3f6db 100644 --- a/app/controllers/api/machines_controller.rb +++ b/app/controllers/api/machines_controller.rb @@ -1,10 +1,10 @@ class API::MachinesController < API::ApiController before_action :authenticate_user!, except: [:index, :show] - before_action :set_machine, only: [:edit, :update, :destroy] + before_action :set_machine, only: [:update, :destroy] respond_to :json def index - @machines = Machine.all + @machines = Machine.includes(:machine_image, :plans) end def show @@ -31,7 +31,7 @@ class API::MachinesController < API::ApiController end def destroy - authorize Machine + authorize @machine @machine.destroy head :no_content end @@ -46,4 +46,13 @@ class API::MachinesController < API::ApiController machine_files_attributes: [:id, :attachment, :_destroy]) end + def is_reserved(start_at, reservations) + is_reserved = false + reservations.each do |r| + r.slots.each do |s| + is_reserved = true if s.start_at == start_at + end + end + is_reserved + end end diff --git a/app/controllers/api/members_controller.rb b/app/controllers/api/members_controller.rb index 6f72aaeb9..f7f322977 100644 --- a/app/controllers/api/members_controller.rb +++ b/app/controllers/api/members_controller.rb @@ -1,14 +1,16 @@ class API::MembersController < API::ApiController before_action :authenticate_user!, except: [:last_subscribed] - before_action :set_member, only: [:update] + before_action :set_member, only: [:update, :destroy, :merge] respond_to :json def index + @requested_attributes = params[:requested_attributes] @members = policy_scope(User) end def last_subscribed - @members = User.with_role(:member).includes(:profile).where('is_allow_contact = true AND confirmed_at IS NOT NULL').order('created_at desc').limit(params[:last]) + @members = User.active.with_role(:member).includes(:profile).where('is_allow_contact = true AND confirmed_at IS NOT NULL').order('created_at desc').limit(params[:last]) + @requested_attributes = ['profile'] render :index end @@ -26,12 +28,19 @@ class API::MembersController < API::ApiController @member = User.new(user_params.permit!) end + + # if the user is created by an admin and the authentication is made through an SSO, generate a migration token + if current_user.is_admin? and AuthProvider.active.providable_type != DatabaseProvider.name + @member.generate_auth_migration_token + end + if @member.save + @member.generate_admin_invoice @member.send_confirmation_instructions if !user_params[:password] and !user_params[:password_confirmation] - UsersMailer.delay.notify_member_account_is_created(@member, generated_password) + UsersMailer.delay.notify_user_account_created(@member, generated_password) else - UsersMailer.delay.notify_member_account_is_created(@member, user_params[:password]) + UsersMailer.delay.notify_user_account_created(@member, user_params[:password]) end render :show, status: :created, location: member_path(@member) else @@ -41,26 +50,87 @@ class API::MembersController < API::ApiController def update authorize @member + @flow_worker = MembersFlowWorker.new(@member) - if @member.update(user_params.permit!) - - # Update password without logging out - sign_in(@member, :bypass => true) unless current_user.is_admin? - render :show, status: :ok, location: member_path(@member) - else + if user_params[:group_id] and @member.group_id != user_params[:group_id].to_i and @member.subscribed_plan != nil + # here a group change is requested but unprocessable, handle the exception + @member.errors[:group_id] = t('members.unable_to_change_the_group_while_a_subscription_is_running') render json: @member.errors, status: :unprocessable_entity + else + # otherwise, run the user update + if @flow_worker.update(user_params) + # Update password without logging out + sign_in(@member, :bypass => true) unless current_user.id != params[:id].to_i + render :show, status: :ok, location: member_path(@member) + else + render json: @member.errors, status: :unprocessable_entity + end + end + end + + def destroy + authorize @member + @member.soft_destroy + sign_out(@member) + head :no_content + end + + # export abonnements + def export_subscriptions + authorize :export + @datas = Subscription.includes(:plan, :user).all + respond_to do |format| + format.html + format.xls + end + end + + # export reservations + def export_reservations + authorize :export + @datas = Reservation.includes(:user, :slots).all + respond_to do |format| + format.html + format.xls end end def export_members authorize :export - @datas = User.with_role(:member).includes(:group, :profile) + @datas = User.with_role(:member).includes(:group, :subscriptions, :profile) respond_to do |format| format.html format.xls end end + def merge + authorize @member + + # here the user query to be mapped to his already existing account + + token = params.require(:user).permit(:auth_token)[:auth_token] + + @account = User.find_by_auth_token(token) + if @account + @flow_worker = MembersFlowWorker.new(@account) + begin + if @flow_worker.merge_from_sso(@member) + @member = @account + # finally, log on the real account + sign_in(@member, :bypass => true) + render :show, status: :ok, location: member_path(@member) + else + render json: @member.errors, status: :unprocessable_entity + end + rescue DuplicateIndexError => error + render json: {error: t('members.please_input_the_authentication_code_sent_to_the_address', EMAIL: error.message)}, status: :unprocessable_entity + end + else + render json: {error: t('members.your_authentication_code_is_not_valid')}, status: :unprocessable_entity + end + end + private def set_member @member = User.find(params[:id]) @@ -68,14 +138,16 @@ class API::MembersController < API::ApiController def user_params if current_user.id == params[:id].to_i - params.require(:user).permit(:username, :email, :password, :password_confirmation, profile_attributes: [:id, :first_name, :last_name, - :gender, :birthday, :phone, :interest, :software_mastered, + params.require(:user).permit(:username, :email, :password, :password_confirmation, :group_id, :is_allow_contact, + profile_attributes: [:id, :first_name, :last_name, :gender, :birthday, :phone, :interest, :software_mastered, :user_avatar_attributes => [:id, :attachment, :_destroy], :address_attributes => [:id, :address]]) elsif current_user.is_admin? - params.require(:user).permit! - else - params.require(:user) + params.require(:user).permit(:username, :email, :password, :password_confirmation, :invoicing_disabled, + :group_id, training_ids: [], tag_ids: [], + profile_attributes: [:id, :first_name, :last_name, :gender, :birthday, :phone, :interest, :software_mastered, + user_avatar_attributes: [:id, :attachment, :_destroy], address_attributes: [:id, :address]]) + end end end diff --git a/app/controllers/api/notifications_controller.rb b/app/controllers/api/notifications_controller.rb index 5f66f5f24..a9121d91e 100644 --- a/app/controllers/api/notifications_controller.rb +++ b/app/controllers/api/notifications_controller.rb @@ -4,7 +4,7 @@ class API::NotificationsController < API::ApiController def index if params[:is_read] - @notifications = current_user.notifications.where(is_read: params[:is_read] == "true").page(params[:page]).per(15).order('created_at DESC') + @notifications = current_user.notifications.where(is_read: params[:is_read] == 'true').page(params[:page]).per(15).order('created_at DESC') else @notifications = current_user.notifications.order('created_at DESC') end diff --git a/app/controllers/api/plans_controller.rb b/app/controllers/api/plans_controller.rb new file mode 100644 index 000000000..ffc9b5c8e --- /dev/null +++ b/app/controllers/api/plans_controller.rb @@ -0,0 +1,100 @@ + class API::PlansController < API::ApiController + before_action :authenticate_user!, except: [:index] + + def index + @attributes_requested = params[:attributes_requested] + @plans = Plan.all + @plans = @plans.where(group_id: params[:group_id]) if params[:group_id] + if params[:shallow] + render :shallow_index + else + render :index + end + end + + def show + @plan = Plan.find(params[:id]) + end + + def create + authorize Plan + if plan_params[:type] and plan_params[:type] == 'PartnerPlan' + + partner = User.find(params[:plan][:partner_id]) + + if plan_params[:group_id] == 'all' + plans = PartnerPlan.create_for_all_groups(plan_params) + if plans + plans.each { |plan| partner.add_role :partner, plan } + render json: { plan_ids: plans.map(&:id) }, status: :created + else + render status: :unprocessable_entity + end + + else + @plan = PartnerPlan.new(plan_params) + if @plan.save + partner.add_role :partner, @plan + render :show, status: :created + else + render json: @plan.errors, status: :unprocessable_entity + end + end + else + if plan_params[:group_id] == 'all' + plans = Plan.create_for_all_groups(plan_params) + if plans + render json: { plan_ids: plans.map(&:id) }, status: :created + else + render status: :unprocessable_entity + end + else + @plan = Plan.new(plan_params) + if @plan.save + render :show, status: :created, location: @plan + else + render json: @plan.errors, status: :unprocessable_entity + end + end + end + end + + def update + @plan = Plan.find(params[:id]) + authorize @plan + if @plan.update(plan_params) + render :show, status: :ok + else + render json: @plan.errors, status: :unprocessable_entity + end + end + + def destroy + @plan = Plan.find(params[:id]) + authorize @plan + @plan.destroy + head :no_content + end + + private + def plan_params + if @parameters + @parameters + else + @parameters = params + @parameters[:plan][:amount] = @parameters[:plan][:amount].to_i * 100.0 if @parameters[:plan][:amount] + @parameters[:plan][:prices_attributes] = @parameters[:plan][:prices_attributes].map do |price| + { amount: price[:amount].to_i * 100.0, id: price[:id] } + end if @parameters[:plan][:prices_attributes] + + @parameters = @parameters.require(:plan).permit(:base_name, :type, :group_id, :amount, :interval, :interval_count, :is_rolling, + :training_credit_nb, + :ui_weight, + plan_file_attributes: [:id, :attachment, :_destroy], + prices_attributes: [:id, :amount] + ) + + @parameters + end + end +end diff --git a/app/controllers/api/prices_controller.rb b/app/controllers/api/prices_controller.rb new file mode 100644 index 000000000..ac7d2765f --- /dev/null +++ b/app/controllers/api/prices_controller.rb @@ -0,0 +1,67 @@ +class API::PricesController < API::ApiController + before_action :authenticate_user! + + def index + authorize Price + @prices = Price.all + if params[:priceable_type] + @prices = @prices.where(priceable_type: params[:priceable_type]) + if params[:priceable_id] + @prices = @prices.where(priceable_id: params[:priceable_id]) + end + end + if params[:plan_id] + if params[:plan_id] =~ /no|nil|null|undefined/i + plan_id = nil + else + plan_id = params[:plan_id] + end + @prices = @prices.where(plan_id: plan_id) + end + if params[:group_id] + @prices = @prices.where(group_id: params[:group_id]) + end + end + + def update + authorize Price + @price = Price.find(params[:id]) + _price_params = price_params + _price_params[:amount] = _price_params[:amount] * 100 + if @price.update(_price_params) + render status: :ok + else + render status: :unprocessable_entity + end + end + + def compute + _price_params = compute_price_params + # user + _user = User.find(_price_params[:user_id]) + # reservable + if _price_params[:reservable_id].nil? + @amount = {elements: nil, total: 0} + else + _reservable = _price_params[:reservable_type].constantize.find(_price_params[:reservable_id]) + @amount = Price.compute(current_user.is_admin?, _user, _reservable, _price_params[:slots_attributes], _price_params[:plan_id], _price_params[:nb_reserve_places], _price_params[:nb_reserve_reduced_places]) + end + + + if @amount.nil? + render status: :unprocessable_entity + else + render status: :ok + end + end + + private + def price_params + params.require(:price).permit(:amount) + end + + def compute_price_params + params.require(:reservation).permit(:reservable_id, :reservable_type, :plan_id, :user_id, :nb_reserve_places, :nb_reserve_reduced_places, + slots_attributes: [:id, :start_at, :end_at, :availability_id, :offered]) + end +end diff --git a/app/controllers/api/pricing_controller.rb b/app/controllers/api/pricing_controller.rb new file mode 100644 index 000000000..c0f351705 --- /dev/null +++ b/app/controllers/api/pricing_controller.rb @@ -0,0 +1,25 @@ +class API::PricingController < API::ApiController + before_action :authenticate_user!, except: [:index, :show] + + def index + @group_pricing = Group.includes(:plans, :trainings_pricings) + end + + def update + authorize :pricing, :update? + if params[:training].present? + training = Training.find params[:training] + params[:group_pricing].each do |group_id, amount| + if training + group = Group.includes(:plans).find(group_id) + if group + training_pricing = group.trainings_pricings.find_or_initialize_by(training_id: training.id) + training_pricing.amount = amount * 100 + training_pricing.save + end + end + end + end + head 200 + end +end diff --git a/app/controllers/api/projects_controller.rb b/app/controllers/api/projects_controller.rb index 20f71a7d3..780642e0b 100644 --- a/app/controllers/api/projects_controller.rb +++ b/app/controllers/api/projects_controller.rb @@ -1,5 +1,5 @@ class API::ProjectsController < API::ApiController - before_action :authenticate_user!, except: [:index, :show, :last_published] + before_action :authenticate_user!, except: [:index, :show, :last_published, :search] before_action :set_project, only: [:update, :destroy] respond_to :json @@ -35,7 +35,7 @@ class API::ProjectsController < API::ApiController end def destroy - authorize Project + authorize @project @project.destroy head :no_content end @@ -49,6 +49,12 @@ class API::ProjectsController < API::ApiController redirect_to root_url end + def search + query_params = JSON.parse(params[:search]) + @projects = Project.search(query_params, current_user).page(params[:page]).records + render :index + end + private def set_project @project = Project.find(params[:id]) diff --git a/app/controllers/api/reservations_controller.rb b/app/controllers/api/reservations_controller.rb new file mode 100644 index 000000000..08cee38a5 --- /dev/null +++ b/app/controllers/api/reservations_controller.rb @@ -0,0 +1,64 @@ +class API::ReservationsController < API::ApiController + before_action :authenticate_user! + before_action :set_reservation, only: [:show, :update] + respond_to :json + + def index + if params[:reservable_id] and params[:reservable_type] and params[:user_id] + if !current_user.is_admin? + params[:user_id] = current_user.id + end + @reservations = Reservation.where(params.permit(:reservable_id, :reservable_type, :user_id)) + elsif params[:reservable_id] and params[:reservable_type] and current_user.is_admin? + @reservations = Reservation.where(params.permit(:reservable_id, :reservable_type)) + else + @reservations = [] + end + end + + def show + end + + def create + if current_user.is_admin? + @reservation = Reservation.new(reservation_params) + is_reserve = @reservation.save_with_local_payment + else + @reservation = Reservation.new(reservation_params.merge(user_id: current_user.id)) + is_reserve = @reservation.save_with_payment + end + if is_reserve + reservation_user = @reservation.user + if @reservation.reservable_type == 'Training' and is_first_training_and_active_subscription(reservation_user) + reservation_user.subscription.update_expired_date_with_first_training(@reservation.slots.first.start_at) + end + render :show, status: :created, location: @reservation + else + render json: @reservation.errors, status: :unprocessable_entity + end + end + + def update + authorize @reservation + if @reservation.update(reservation_params) + render :show, status: :ok, location: @reservation + else + render json: @reservation.errors, status: :unprocessable_entity + end + end + + private + def set_reservation + @reservation = Reservation.find(params[:id]) + end + + def reservation_params + params.require(:reservation).permit(:user_id, :message, :reservable_id, :reservable_type, :card_token, :plan_id, + :nb_reserve_places, :nb_reserve_reduced_places, + slots_attributes: [:id, :start_at, :end_at, :availability_id, :offered]) + end + + def is_first_training_and_active_subscription(user) + user.reservations.where(reservable_type: 'Training').size == 1 and user.subscription and !user.subscription.is_expired? + end +end diff --git a/app/controllers/api/settings_controller.rb b/app/controllers/api/settings_controller.rb new file mode 100644 index 000000000..3cac77033 --- /dev/null +++ b/app/controllers/api/settings_controller.rb @@ -0,0 +1,30 @@ +class API::SettingsController < API::ApiController + before_action :authenticate_user!, only: :update + + def index + @settings = Setting.where(name: names_as_string_to_array) + end + + def update + authorize Setting + @setting = Setting.find_by(name: params[:name]) + if @setting.update(setting_params) + render status: :ok + else + render json: @setting.errors.full_messages, status: :unprocessable_entity + end + end + + def show + @setting = Setting.find_or_create_by(name: params[:name]) + end + + private + def setting_params + params.require(:setting).permit(:value) + end + + def names_as_string_to_array + params[:names][1..-2].split(',').map(&:strip).map { |param| param[1..-2] }.map(&:strip) + end +end diff --git a/app/controllers/api/slots_controller.rb b/app/controllers/api/slots_controller.rb new file mode 100644 index 000000000..5e5179afe --- /dev/null +++ b/app/controllers/api/slots_controller.rb @@ -0,0 +1,36 @@ +class API::SlotsController < API::ApiController + before_action :authenticate_user! + before_action :set_slot, only: [:update, :cancel] + respond_to :json + + def update + authorize @slot + if @slot.update(slot_params) + reservation_user = @slot.reservation.user + if @slot.reservation.reservable_type == 'Training' and is_first_training_and_active_subscription(reservation_user) + reservation_user.subscription.update_expired_date_with_first_training(@slot.start_at) + end + render :show, status: :created, location: @slot + else + render json: @slot.errors, status: :unprocessable_entity + end + end + + def cancel + authorize @slot + @slot.update_attributes(:canceled_at => DateTime.now) + end + + private + def set_slot + @slot = Slot.find(params[:id]) + end + + def slot_params + params.require(:slot).permit(:start_at, :end_at, :availability_id) + end + + def is_first_training_and_active_subscription(user) + user.reservations.where(reservable_type: 'Training').size == 1 and user.subscription and !user.subscription.is_expired? + end +end diff --git a/app/controllers/api/statistics_controller.rb b/app/controllers/api/statistics_controller.rb new file mode 100644 index 000000000..56523fe45 --- /dev/null +++ b/app/controllers/api/statistics_controller.rb @@ -0,0 +1,19 @@ +class API::StatisticsController < API::ApiController + before_action :authenticate_user! + + def index + authorize :statistic, :index? + @statistics = StatisticIndex.all + end + + %w(account event machine project subscription training user).each do |path| + class_eval %{ + def #{path} + authorize :statistic, :#{path}? + query = MultiJson.load(request.body.read) + results = Stats::#{path.classify}.search(query, request.query_parameters.symbolize_keys).response + render json: results + end + } + end +end diff --git a/app/controllers/api/stylesheets_controller.rb b/app/controllers/api/stylesheets_controller.rb new file mode 100644 index 000000000..76dc5dba5 --- /dev/null +++ b/app/controllers/api/stylesheets_controller.rb @@ -0,0 +1,11 @@ +class API::StylesheetsController < API::ApiController + caches_page :show # magic happens here + + def show + @stylesheet = Stylesheet.find(params[:id]) + respond_to do |format| + format.html # regular ERB template + format.css { render :text => @stylesheet.contents, :content_type => 'text/css' } + end + end +end \ No newline at end of file diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb new file mode 100644 index 000000000..368286558 --- /dev/null +++ b/app/controllers/api/subscriptions_controller.rb @@ -0,0 +1,64 @@ +class API::SubscriptionsController < API::ApiController + include FablabConfiguration + + before_action :set_subscription, only: [:show, :edit, :update, :destroy] + before_action :authenticate_user! + + def show + authorize @subscription + end + + def create + if fablab_plans_deactivated? + head 403 + else + if current_user.is_admin? + @subscription = Subscription.find_or_initialize_by(user_id: subscription_params[:user_id]) + # @subscription.expired_at = nil + @subscription.update_column(:expired_at, nil) unless @subscription.new_record? # very important + @subscription.attributes = subscription_params + is_subscribe = @subscription.save_with_local_payment(!User.find(subscription_params[:user_id]).invoicing_disabled?) + else + @subscription = Subscription.find_or_initialize_by(user_id: current_user.id) + # @subscription.expired_at = nil + @subscription.update_column(:expired_at, nil) unless @subscription.new_record? # very important + @subscription.attributes = subscription_params.merge(user_id: current_user.id) + is_subscribe = @subscription.save_with_payment + end + if is_subscribe + render :show, status: :created, location: @subscription + else + render json: @subscription.errors, status: :unprocessable_entity + end + end + end + + def update + authorize @subscription + + free_days = params[:subscription][:free] == true + + if @subscription.extend_expired_date(subscription_update_params[:expired_at], free_days) + ex_expired_at = @subscription.previous_changes[:expired_at].first + @subscription.user.generate_admin_invoice(free_days, ex_expired_at) + render status: :ok + else + render status: :unprocessable_entity + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_subscription + @subscription = Subscription.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def subscription_params + params.require(:subscription).permit(:plan_id, :user_id, :card_token) + end + + def subscription_update_params + params.require(:subscription).permit(:expired_at) + end +end diff --git a/app/controllers/api/tags_controller.rb b/app/controllers/api/tags_controller.rb new file mode 100644 index 000000000..00d4563b3 --- /dev/null +++ b/app/controllers/api/tags_controller.rb @@ -0,0 +1,47 @@ + +class API::TagsController < API::ApiController + + before_action :authenticate_user!, except: [:index, :show] + before_action :set_tag, only: [:show, :update, :destroy] + + def index + @tags = Tag.all + end + + def show + end + + def create + authorize Tag + @tag = Tag.new(tag_params) + if @tag.save + render :show, status: :created, location: @tag + else + render json: @tag.errors, status: :unprocessable_entity + end + end + + def update + authorize Tag + if @tag.update(tag_params) + render :show, status: :ok, location: @tag + else + render json: @tag.errors, status: :unprocessable_entity + end + end + + def destroy + authorize Tag + @tag.destroy + head :no_content + end + + private + def set_tag + @tag = Tag.find(params[:id]) + end + + def tag_params + params.require(:tag).permit(:name) + end +end \ No newline at end of file diff --git a/app/controllers/api/trainings_controller.rb b/app/controllers/api/trainings_controller.rb new file mode 100644 index 000000000..b06944bf5 --- /dev/null +++ b/app/controllers/api/trainings_controller.rb @@ -0,0 +1,53 @@ +class API::TrainingsController < API::ApiController + before_action :authenticate_user!, except: [:index, :show] + before_action :set_training, only: [:show, :update, :destroy] + + def index + @trainings = policy_scope(Training) + end + + def show + end + + def create + authorize Training + @training = Training.new(training_params) + if @training.save + render :show, status: :created, location: @training + else + render json: @training.errors, status: :unprocessable_entity + end + end + + def update + authorize Training + if params[:training][:users].present? + members = User.where(id: valid_training_params[:users]) + members.each do |m| + m.trainings << @training + end + else + @training.update(training_params) + end + head :no_content + end + + def destroy + authorize @training + @training.destroy + head :no_content + end + + private + def set_training + @training = Training.find(params[:id]) + end + + def valid_training_params + params.require(:training).permit(:id, users: []) + end + + def training_params + params.require(:training).permit(:id, :name, :description, :machine_ids, :plan_ids, :nb_total_places, machine_ids: [], plan_ids: []) + end +end diff --git a/app/controllers/api/trainings_pricings_controller.rb b/app/controllers/api/trainings_pricings_controller.rb new file mode 100644 index 000000000..367b1f72d --- /dev/null +++ b/app/controllers/api/trainings_pricings_controller.rb @@ -0,0 +1,26 @@ +class API::TrainingsPricingsController < API::ApiController + before_action :authenticate_user! + + def index + @trainings_pricings = TrainingsPricing.includes(:training) + end + + def update + if current_user.is_admin? + @trainings_pricing = TrainingsPricing.find(params[:id]) + _trainings_pricing_params = trainings_pricing_params + _trainings_pricing_params[:amount] = _trainings_pricing_params[:amount] * 100 + if @trainings_pricing.update(_trainings_pricing_params) + render status: :ok + else + render status: :unprocessable_entity + end + else + head 403 + end + end + + def trainings_pricing_params + params.require(:trainings_pricing).permit(:amount) + end +end diff --git a/app/controllers/api/translations_controller.rb b/app/controllers/api/translations_controller.rb new file mode 100644 index 000000000..ab47a8d92 --- /dev/null +++ b/app/controllers/api/translations_controller.rb @@ -0,0 +1,18 @@ +class API::TranslationsController < API::ApiController + before_action :set_locale + + + def show + @translations = I18n.t params[:state] + if @translations.class.name == String.name and @translations.start_with?('translation missing') + render json: {error: @translations}, status: :unprocessable_entity + else + render json: @translations, status: :ok + end + end + + def set_locale + I18n.locale = params[:locale] || I18n.default_locale + end + +end \ No newline at end of file diff --git a/app/controllers/api/users_controller.rb b/app/controllers/api/users_controller.rb new file mode 100644 index 000000000..4359dede5 --- /dev/null +++ b/app/controllers/api/users_controller.rb @@ -0,0 +1,35 @@ +class API::UsersController < API::ApiController + before_action :authenticate_user! + + def index + if current_user.is_admin? and params[:role] == 'partner' + @users = User.with_role(:partner).includes(:profile) + else + head 403 + end + end + + def create + if current_user.is_admin? + generated_password = Devise.friendly_token.first(8) + @user = User.new(email: partner_params[:email], username: "#{partner_params[:first_name]}#{partner_params[:last_name]}", + password: generated_password, password_confirmation: generated_password, group_id: Group.first.id) + @user.build_profile(first_name: partner_params[:first_name], last_name: partner_params[:last_name], gender: true, birthday: Time.now, phone: '0000000000') + + if @user.save + @user.remove_role :member + @user.add_role :partner + render status: :created + else + render json: @user.errors.full_messages, status: :unprocessable_entity + end + else + head 403 + end + end + + private + def partner_params + params.require(:user).permit(:email, :first_name, :last_name) + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d5adffb36..8ca0b4026 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,11 +4,16 @@ class ApplicationController < ActionController::Base # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception after_action :set_csrf_cookie + cache_sweeper :stylesheet_sweeper respond_to :html, :json before_action :configure_permitted_parameters, if: :devise_controller? + # Globally rescue Authorization Errors in controller. + # Returning 403 Forbidden if permission is denied + rescue_from Pundit::NotAuthorizedError, with: :permission_denied + def index end @@ -31,4 +36,8 @@ class ApplicationController < ActionController::Base def default_url_options Rails.env.production? ? { protocol: 'https' } : {} end + + def permission_denied + head 403 + end end diff --git a/app/controllers/concerns/fablab_configuration.rb b/app/controllers/concerns/fablab_configuration.rb new file mode 100644 index 000000000..1d22dfece --- /dev/null +++ b/app/controllers/concerns/fablab_configuration.rb @@ -0,0 +1,5 @@ +module FablabConfiguration + def fablab_plans_deactivated? + Rails.application.secrets.fablab_without_plans + end +end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 7744ee5f3..7208cffc6 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -9,7 +9,7 @@ class RegistrationsController < Devise::RegistrationsController if resource.active_for_authentication? set_flash_message :notice, :signed_up if is_flashing_format? - # Permet d'envoyer l'email de confirmation sans bloquer l'access au dashboard + # Allows sending the confirmation email without blocking the access to the dashboard resource.send_confirmation_instructions sign_up(resource_name, resource) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index aa36c0a6b..5488450ad 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,6 +1,15 @@ class SessionsController < Devise::SessionsController #before_action :set_csrf_headers, only: [:create, :destroy] + def new + active_provider = AuthProvider.active + if active_provider.providable_type != DatabaseProvider.name + redirect_to user_omniauth_authorize_path(active_provider.strategy_name.to_sym) + else + super + end + end + protected def set_csrf_headers cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery? diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb new file mode 100644 index 000000000..d15abe757 --- /dev/null +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -0,0 +1,83 @@ +class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController + + active_provider = AuthProvider.active + define_method active_provider.strategy_name do + if request.env['omniauth.params'].blank? + @user = User.from_omniauth(request.env['omniauth.auth']) + + # Here we create the new user or update the existing one with values retrieved from the SSO. + + if @user.id.nil? # => new user (ie. not updating existing) + # If the username is mapped, we just check its uniqueness as it would break the postgresql + # unique contraint otherwise. If the name is not unique, another unique is generated + if active_provider.sso_fields.include?('user.username') + @user.username = generate_unique_username(@user.username) + end + # If the email is mapped, we check its uniqueness. If the email is already in use, we mark it as duplicate with an + # unique random string because. + # - if it is the same user, his email will be filled from the SSO when he merge his accounts + # - if it is not the same user, this will prevent the raise of PG::UniqueViolation + if active_provider.sso_fields.include?('user.email') and email_exists?(@user.email) + old_mail = @user.email + @user.email = "<#{old_mail}>#{Devise.friendly_token}-duplicate" + flash[:alert] = t('omniauth.email_already_linked_to_another_account_please_input_your_authentication_code', OLD_MAIL: old_mail) + end + else + if username_exists?(@user.username, @user.id) + flash[:alert] = t('omniauth.your_username_is_already_linked_to_another_account_unable_to_update_it', USERNAME: @user.username) + @user.username = User.find(@user.id).username + end + + if email_exists?(@user.email, @user.id) + flash[:alert] = t('omniauth.your_email_address_is_already_linked_to_another_account_unable_to_update_it', EMAIL: @user.email) + @user.email = User.find(@user.id).email + end + end + + # We BYPASS THE VALIDATION because, in case of a new user, we want to save him anyway, we'll ask him later to complete his profile (on first login). + # In case of an existing user, we trust the SSO validation as we want the SSO to have authority on users management and policy. + @user.save(:validate => false) + sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated + else + @user = User.find_by(auth_token: request.env['omniauth.params']['auth_token']) + + # Here the user already exists in the database and request to be linked with the SSO + # so let's update its sso attributes and log him on + + begin + @user.link_with_omniauth_provider(request.env['omniauth.auth']) + sign_in_and_redirect @user, :event => :authentication + rescue DuplicateIndexError + redirect_to root_url, alert: t('omniauth.this_account_is_already_linked_to_an_user_of_the_platform', NAME: active_provider.name) + end + end + + end + + private + def username_exists?(username, exclude_id = nil) + if exclude_id.nil? + User.where(username: username).size > 0 + else + User.where(username: username).where.not(id: exclude_id).size > 0 + end + end + + def email_exists?(email, exclude_id = nil) + if exclude_id.nil? + User.where(email: email).size > 0 + else + User.where(email: email).where.not(id: exclude_id).size > 0 + end + end + + def generate_unique_username(username) + generated = username + i = 1000 + while username_exists?(generated) + generated = username + rand(1..i).to_s + i += 10 + end + generated + end +end \ No newline at end of file diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb index 89cba22c1..6c29ad365 100644 --- a/app/controllers/webhooks_controller.rb +++ b/app/controllers/webhooks_controller.rb @@ -1,4 +1,7 @@ class WebhooksController < ApplicationController + + protect_from_forgery :except => :create + def create # data_json = JSON.parse request.body.read diff --git a/app/exceptions/duplicate_index_error.rb b/app/exceptions/duplicate_index_error.rb new file mode 100644 index 000000000..5900df57b --- /dev/null +++ b/app/exceptions/duplicate_index_error.rb @@ -0,0 +1,3 @@ +# Raised when trying to create an index which already exists. +class DuplicateIndexError < IndexError +end diff --git a/app/flow_workers/members_flow_worker.rb b/app/flow_workers/members_flow_worker.rb new file mode 100644 index 000000000..e929b0ea7 --- /dev/null +++ b/app/flow_workers/members_flow_worker.rb @@ -0,0 +1,45 @@ + +class MembersFlowWorker + + attr_accessor :member + + def initialize(member) + @member = member + end + + def update(params) + not_complete = self.member.need_completion? + up_result = self.member.update(params) + if up_result + notify_user_profile_complete(not_complete) + end + up_result + end + + def merge_from_sso(user) + merge_result = self.member.merge_from_sso(user) + if merge_result + notify_admin_user_merged + end + merge_result + end + + private + def notify_user_profile_complete(previous_state) + if previous_state and not self.member.need_completion? + NotificationCenter.call type: :notify_user_profile_complete, + receiver: self.member, + attached_object: self.member + NotificationCenter.call type: :notify_admin_profile_complete, + receiver: User.admins, + attached_object: self.member + end + end + + def notify_admin_user_merged + NotificationCenter.call type: :notify_admin_user_merged, + receiver: User.admins, + attached_object: self.member + end + +end \ No newline at end of file diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ec097a951..c504024bc 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,5 +1,73 @@ module ApplicationHelper include Twitter::Autolink + require 'message_format' + ## + # Verify if the provided attribute is in the provided attributes array, whatever it exists or not + # @param attributes {Array|nil} + # @param attribute {String} + ## + def attribute_requested?(attributes, attribute) + attributes.try(:include?, attribute) + end + + def bootstrap_class_for flash_type + { flash: 'alert-success', alert: 'alert-danger', notice: 'alert-info' }[flash_type.to_sym] || flash_type.to_s + end + + def flash_messages(opts = {}) + flash.each do |msg_type, message| + concat(content_tag(:div, message, class: "flash-message alert #{bootstrap_class_for(msg_type)} fade in") do + concat content_tag(:button, 'x', class: 'close', data: { dismiss: 'alert' }) + concat message + end) + end + nil + end + + def class_exists?(class_name) + klass = Module.const_get(class_name) + return klass.is_a?(Class) + rescue NameError + return false + end + + ## + # Allow to treat a rails i18n key as a MessageFormat interpolated pattern. Used in ruby views (API/mails) + # @param key {String} Ruby-on-Rails I18n key (from config/locales/xx.yml) + # @param interpolations {Hash} list of variables to interpolate, following ICU MessageFormat syntax + ## + def _t(key, interpolations) + message = MessageFormat.new(I18n.t(scope_key_by_partial(key)), I18n.locale.to_s) + text = message.format(interpolations) + if html_safe_translation_key?(key) + text.html_safe + else + text + end + end + + def bool_to_sym(bool) + if (bool) then return :true else return :false end + end + + + private + ## inspired by gems/actionview-4.2.5/lib/action_view/helpers/translation_helper.rb + def scope_key_by_partial(key) + if key.to_s.first == "." + if @virtual_path + @virtual_path.gsub(%r{/_?}, ".") + key.to_s + else + raise "Cannot use t(#{key.inspect}) shortcut because path is not available" + end + else + key + end + end + + def html_safe_translation_key?(key) + key.to_s =~ /(\b|_|\.)html$/ + end end diff --git a/app/helpers/upload_helper.rb b/app/helpers/upload_helper.rb index 528fbe683..6d9ba45e0 100644 --- a/app/helpers/upload_helper.rb +++ b/app/helpers/upload_helper.rb @@ -12,4 +12,4 @@ module UploadHelper true # nothing, the dir is not empty end -end \ No newline at end of file +end diff --git a/app/mailers/notifications_mailer.rb b/app/mailers/notifications_mailer.rb index 6d51b2f29..e7d51e96a 100644 --- a/app/mailers/notifications_mailer.rb +++ b/app/mailers/notifications_mailer.rb @@ -4,7 +4,36 @@ class NotificationsMailer < NotifyWith::NotificationsMailer helper :application + def send_mail_by(notification) + @notification = notification + @recipient = notification.receiver + @attached_object = notification.attached_object + + if !respond_to?(notification.notification_type) + class_eval %Q{ + def #{notification.notification_type} + mail to: @recipient.email, + subject: t('notifications_mailer.#{notification.notification_type}.subject'), + template_name: '#{notification.notification_type}', + content_type: 'text/html' + end + } + end + + send(notification.notification_type) + end + def helpers ActionController::Base.helpers end + + def notify_user_when_invoice_ready + attachments['facture.pdf'] = File.read(@attached_object.file) + mail(to: @recipient.email, subject: t('notifications_mailer.notify_member_invoice_ready.subject'), template_name: 'notify_member_invoice_ready') + end + + def notify_user_when_avoir_ready + attachments['avoir.pdf'] = File.read(@attached_object.file) + mail(to: @recipient.email, subject: t('notifications_mailer.notify_member_avoir_ready.subject'), template_name: 'notify_member_avoir_ready') + end end diff --git a/app/mailers/users_mailer.rb b/app/mailers/users_mailer.rb index f87a708bc..1c38e2fc0 100644 --- a/app/mailers/users_mailer.rb +++ b/app/mailers/users_mailer.rb @@ -1,7 +1,7 @@ class UsersMailer < BaseMailer - def notify_member_account_is_created(user, generated_password) + def notify_user_account_created(user, generated_password) @user = user @generated_password = generated_password - mail(to: @user.email, subject: "Votre compte Fab Lab a bien été créé.") + mail(to: @user.email, subject: t('users_mailer.notify_user_account_created.subject')) end end diff --git a/app/models/abuse.rb b/app/models/abuse.rb new file mode 100644 index 000000000..b0752611f --- /dev/null +++ b/app/models/abuse.rb @@ -0,0 +1,15 @@ +class Abuse < ActiveRecord::Base + include NotifyWith::NotificationAttachedObject + + belongs_to :signaled, polymorphic: true + + after_create :notify_admins_abuse_reported + + + private + def notify_admins_abuse_reported + NotificationCenter.call type: 'notify_admin_abuse_reported', + receiver: User.admins, + attached_object: self + end +end diff --git a/app/models/auth_provider.rb b/app/models/auth_provider.rb new file mode 100644 index 000000000..c4b9655bf --- /dev/null +++ b/app/models/auth_provider.rb @@ -0,0 +1,82 @@ +class AuthProvider < ActiveRecord::Base + + # this is a simple stub used for database creation & configuration + class SimpleAuthProvider < Object + def providable_type + DatabaseProvider.name + end + end + + PROVIDABLE_TYPES = %w(DatabaseProvider OAuth2Provider) + + belongs_to :providable, :polymorphic => true, dependent: :destroy + accepts_nested_attributes_for :providable + attr_accessible :name, :providable_type, :providable_attributes + + before_create :set_initial_state + + def build_providable(params, assignment_options) + raise "Unknown providable_type: #{providable_type}" unless PROVIDABLE_TYPES.include?(providable_type) + self.providable = providable_type.constantize.new(params) + end + + ## Return the currently active provider + def self.active + local = SimpleAuthProvider.new + + begin + provider = find_by(status: 'active') + if provider.nil? + return local + else + return provider + end + rescue ActiveRecord::StatementInvalid + # we fall here on database creation because the table "active_providers" still does not exists at the moment + return local + end + end + + ## Get the provider matching the omniAuth strategy name + def self.from_strategy_name(strategy_name) + parsed = /^([^-]+)-(.+)$/.match(strategy_name) + ret = nil + all.each do |strategy| + if strategy.provider_type == parsed[1] and strategy.name.downcase.parameterize == parsed[2] + ret = strategy + break + end + end + ret + end + + ## Return the name that should be registered in OmniAuth for the corresponding strategy + def strategy_name + provider_type+'-'+name.downcase.parameterize + end + + ## Return the provider type name without the "Provider" part. + ## eg. DatabaseProvider will return 'database' + def provider_type + providable.class.name[0..-9].downcase + end + + ## Return the user's profile fields that are currently managed from the SSO + ## @return [Array] + def sso_fields + providable.protected_fields + end + + ## Return the link the user have to follow to edit his profile on the SSO + ## @return [String] + def link_to_sso_profile + providable.profile_url + end + + private + def set_initial_state + # the initial state of a new AuthProvider will be 'pending', except if there is currently + # no providers in the database, he we will be 'active' (see seeds.rb) + self.status = 'pending' unless AuthProvider.count == 0 + end +end diff --git a/app/models/availability.rb b/app/models/availability.rb index fb316c0ba..11135cd85 100644 --- a/app/models/availability.rb +++ b/app/models/availability.rb @@ -1,5 +1,77 @@ class Availability < ActiveRecord::Base + has_many :machines_availabilities, dependent: :destroy + has_many :machines, through: :machines_availabilities + accepts_nested_attributes_for :machines, allow_destroy: true + + has_many :trainings_availabilities, dependent: :destroy + has_many :trainings, through: :trainings_availabilities + + has_many :slots + has_many :reservations, through: :slots has_one :event + has_many :availability_tags, dependent: :destroy + has_many :tags, through: :availability_tags + accepts_nested_attributes_for :tags, allow_destroy: true + + scope :machines, -> { where(available_type: 'machines') } + scope :trainings, -> { where(available_type: 'training') } + + attr_accessor :is_reserved, :slot_id, :can_modify + + validate :length_must_be_1h_minimum + validate :should_be_associated + + def safe_destroy + if available_type == 'machines' + reservations = Reservation.where(reservable_type: 'Machine', reservable_id: machine_ids).joins(:slots).where('slots.availability_id = ?', id) + else + reservations = Reservation.where(reservable_type: 'Training', reservable_id: training_ids).joins(:slots).where('slots.availability_id = ?', id) + end + if reservations.size == 0 + # this update may not call any rails callbacks, that's why we use direct SQL update + update_column(:destroying, true) + destroy + else + false + end + end + + def title + if available_type == 'machines' + machines.map(&:name).join(' - ') + else + trainings.map(&:name).join(' - ') + end + end + + # return training reservations is complete? + # if haven't defined a nb_total_places, places are unlimited + def is_completed + return false if nb_total_places.blank? + nb_total_places <= slots.where(canceled_at: nil).size + end + + def nb_total_places + if read_attribute(:nb_total_places).present? + read_attribute(:nb_total_places) + else + trainings.first.nb_total_places unless trainings.empty? + end + end + + private + def length_must_be_1h_minimum + if end_at < (start_at + 1.hour) + errors.add(:end_at, t('availabilities.must_be_at_least_1_hour_after_the_start_date')) + end + end + + def should_be_associated + if available_type == 'machines' and machine_ids.count == 0 + errors.add(:machine_ids, t('availabilities.must_be_associated_with_at_least_1_machine')) + end + end + end diff --git a/app/models/availability_tag.rb b/app/models/availability_tag.rb new file mode 100644 index 000000000..b6a06a962 --- /dev/null +++ b/app/models/availability_tag.rb @@ -0,0 +1,4 @@ +class AvailabilityTag < ActiveRecord::Base + belongs_to :availability + belongs_to :tag +end diff --git a/app/models/avoir.rb b/app/models/avoir.rb new file mode 100644 index 000000000..a24564718 --- /dev/null +++ b/app/models/avoir.rb @@ -0,0 +1,54 @@ +class Avoir < Invoice + belongs_to :invoice + after_create :expire_subscription, if: :subscription_to_expire + + validates :avoir_mode, :inclusion => {:in => %w(stripe cheque transfer none cash)} + + attr_accessor :invoice_items_ids + + def generate_reference + pattern = Setting.find_by({name: 'invoice_reference'}).value + + # invoice number per day (dd..dd) + reference = pattern.gsub(/d+(?![^\[]*\])/) do |match| + pad_and_truncate(number_of_invoices('day'), match.to_s.length) + end + # invoice number per month (mm..mm) + reference.gsub!(/m+(?![^\[]*\])/) do |match| + pad_and_truncate(number_of_invoices('month'), match.to_s.length) + end + # invoice number per year (yy..yy) + reference.gsub!(/y+(?![^\[]*\])/) do |match| + pad_and_truncate(number_of_invoices('year'), match.to_s.length) + end + + # full year (YYYY) + reference.gsub!(/YYYY(?![^\[]*\])/, self.created_at.strftime('%Y')) + # year without century (YY) + reference.gsub!(/YY(?![^\[]*\])/, self.created_at.strftime('%y')) + + # abreviated month name (MMM) + reference.gsub!(/MMM(?![^\[]*\])/, self.created_at.strftime('%^b')) + # month of the year, zero-padded (MM) + reference.gsub!(/MM(?![^\[]*\])/, self.created_at.strftime('%m')) + # month of the year, non zero-padded (M) + reference.gsub!(/M(?![^\[]*\])/, self.created_at.strftime('%-m')) + + # day of the month, zero-padded (DD) + reference.gsub!(/DD(?![^\[]*\])/, self.created_at.strftime('%d')) + # day of the month, non zero-padded (DD) + reference.gsub!(/DD(?![^\[]*\])/, self.created_at.strftime('%-d')) + + # information about refund/avoir (R[text]) + reference.gsub!(/R\[([^\]]+)\]/, '\1') + + # remove information about online selling (X[text]) + reference.gsub!(/X\[([^\]]+)\]/, ''.to_s) + + self.reference = reference + end + + def expire_subscription + user.subscription.expire(Time.now) + end +end diff --git a/app/models/category.rb b/app/models/category.rb index e23925efb..0ec92a142 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -1,3 +1,3 @@ class Category < ActiveRecord::Base - has_and_belongs_to_many :events, join_table: 'events_categories' + has_and_belongs_to_many :events, join_table: :events_categories end diff --git a/app/models/component.rb b/app/models/component.rb index fde0f0899..6a504ba13 100644 --- a/app/models/component.rb +++ b/app/models/component.rb @@ -1,4 +1,4 @@ class Component < ActiveRecord::Base - has_and_belongs_to_many :projects, join_table: 'projects_components' + has_and_belongs_to_many :projects, join_table: :projects_components validates :name, presence: true, length: { maximum: 50 } end \ No newline at end of file diff --git a/app/models/concerns/stat_concern.rb b/app/models/concerns/stat_concern.rb new file mode 100644 index 000000000..6fb3bc224 --- /dev/null +++ b/app/models/concerns/stat_concern.rb @@ -0,0 +1,18 @@ +module StatConcern + extend ActiveSupport::Concern + + included do + attribute :type, String + attribute :subType, String + attribute :date, String + attribute :stat, Integer + attribute :userId, Integer + attribute :gender, String + attribute :age, Integer + attribute :group, String + + # has include Elasticsearch::Persistence::Model + index_name "stats" + document_type self.to_s.demodulize.underscore + end +end diff --git a/app/models/concerns/stat_reservation_concern.rb b/app/models/concerns/stat_reservation_concern.rb new file mode 100644 index 000000000..d4981bacf --- /dev/null +++ b/app/models/concerns/stat_reservation_concern.rb @@ -0,0 +1,9 @@ +module StatReservationConcern + extend ActiveSupport::Concern + + included do + attribute :reservationId, Integer + attribute :ca, Float + attribute :name, String + end +end diff --git a/app/models/credit.rb b/app/models/credit.rb new file mode 100644 index 000000000..e0410c543 --- /dev/null +++ b/app/models/credit.rb @@ -0,0 +1,7 @@ +class Credit < ActiveRecord::Base + belongs_to :creditable, polymorphic: true + belongs_to :plan + has_many :users_credits, dependent: :destroy + + validates :creditable_id, uniqueness: { scope: [:creditable_type, :plan_id] } +end diff --git a/app/models/custom_asset.rb b/app/models/custom_asset.rb new file mode 100644 index 000000000..ec6f71be0 --- /dev/null +++ b/app/models/custom_asset.rb @@ -0,0 +1,10 @@ +class CustomAsset < ActiveRecord::Base + has_one :custom_asset_file, as: :viewable, dependent: :destroy + accepts_nested_attributes_for :custom_asset_file, allow_destroy: true + + # static method to retrieve the attachement URL of the custom asset + def self.get_url(name) + asset = CustomAsset.find_by(name: name) + asset.custom_asset_file.attachment_url if asset and asset.custom_asset_file + end +end diff --git a/app/models/custom_asset_file.rb b/app/models/custom_asset_file.rb new file mode 100644 index 000000000..34127283b --- /dev/null +++ b/app/models/custom_asset_file.rb @@ -0,0 +1,3 @@ +class CustomAssetFile < Asset + mount_uploader :attachment, CustomAssetsUploader +end diff --git a/app/models/database_provider.rb b/app/models/database_provider.rb new file mode 100644 index 000000000..94ac52563 --- /dev/null +++ b/app/models/database_provider.rb @@ -0,0 +1,15 @@ +class DatabaseProvider < ActiveRecord::Base + has_one :auth_provider, as: :providable, dependent: :destroy + + def protected_fields + [] + end + + def profile_url + '/#!/dashboard/profile' + end + + def omniauth_authorize_path + 'users/sign_in' + end +end diff --git a/app/models/event.rb b/app/models/event.rb index 4fb5a4f59..df0a8a8ad 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -3,11 +3,9 @@ class Event < ActiveRecord::Base has_one :event_image, as: :viewable, dependent: :destroy accepts_nested_attributes_for :event_image, allow_destroy: true - has_many :event_files, as: :viewable, dependent: :destroy accepts_nested_attributes_for :event_files, allow_destroy: true - - has_and_belongs_to_many :categories, join_table: 'events_categories' + has_and_belongs_to_many :categories, join_table: :events_categories belongs_to :availability, dependent: :destroy accepts_nested_attributes_for :availability @@ -15,6 +13,8 @@ class Event < ActiveRecord::Base attr_accessor :recurrence, :recurrence_end_at after_create :event_recurrence + before_save :set_nb_free_places + before_update :update_nb_free_places, if: :nb_total_places_changed? def name title @@ -24,6 +24,15 @@ class Event < ActiveRecord::Base Event.includes(:availability).where('events.recurrence_id = ? AND events.id != ? AND availabilities.start_at >= ?', recurrence_id, id, Time.now).references(:availabilities) end + def safe_destroy + reservations = Reservation.where(reservable_type: 'Event', reservable_id: id) + if reservations.size == 0 + destroy + else + false + end + end + private def event_recurrence if recurrence.present? and recurrence != 'none' @@ -70,4 +79,14 @@ class Event < ActiveRecord::Base end end + def set_nb_free_places + if nb_free_places.nil? + self.nb_free_places = nb_total_places + end + end + + def update_nb_free_places + diff = nb_total_places - nb_total_places_was + self.nb_free_places += diff + end end diff --git a/app/models/event_image.rb b/app/models/event_image.rb index 4cdafc2b3..b76f3ba0d 100644 --- a/app/models/event_image.rb +++ b/app/models/event_image.rb @@ -1,5 +1,5 @@ class EventImage < Asset - mount_uploader :attachment, ProjectImageUploader + mount_uploader :attachment, EventImageUploader validates :attachment, file_size: { maximum: 2.megabytes.to_i } end diff --git a/app/models/group.rb b/app/models/group.rb index 093999848..6167bd327 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1,7 +1,49 @@ class Group < ActiveRecord::Base has_many :plans - + has_many :users has_many :trainings_pricings, dependent: :destroy + has_many :machines_prices, ->{ where(priceable_type: 'Machine') }, class_name: 'Price', dependent: :destroy - has_many :machines_pricings, dependent: :destroy + extend FriendlyId + friendly_id :name, use: :slugged + + validates :name, :slug, presence: true + + after_create :create_prices + after_create :create_statistic_subtype + after_update :update_statistic_subtype, if: :name_changed? + + def destroyable? + users.empty? and plans.empty? + end + + private + def create_prices + create_trainings_pricings + create_machines_prices + end + + def create_trainings_pricings + Training.all.each do |training| + TrainingsPricing.create(group: self, training: training, amount: 0) + end + end + + def create_machines_prices + Machine.all.each do |machine| + Price.create(priceable: machine, group: self, amount: 0) + end + end + + def create_statistic_subtype + user_index = StatisticIndex.find_by(es_type_key: 'user') + StatisticSubType.create!({statistic_types: user_index.statistic_types, key: self.slug, label: self.name}) + end + + def update_statistic_subtype + user_index = StatisticIndex.find_by(es_type_key: 'user') + subtype = StatisticSubType.joins(statistic_type_sub_types: :statistic_type).where(key: self.slug, statistic_types: { statistic_index_id: user_index.id }).first + subtype.label = self.name + subtype.save! + end end diff --git a/app/models/invoice.rb b/app/models/invoice.rb new file mode 100644 index 000000000..6c0c252eb --- /dev/null +++ b/app/models/invoice.rb @@ -0,0 +1,219 @@ +class Invoice < ActiveRecord::Base + include NotifyWith::NotificationAttachedObject + require 'fileutils' + scope :only_invoice, -> { where(type: nil) } + belongs_to :invoiced, polymorphic: true + + has_many :invoice_items, dependent: :destroy + accepts_nested_attributes_for :invoice_items + belongs_to :user + + has_one :avoir, class_name: 'Invoice', foreign_key: :invoice_id, dependent: :destroy + + after_create :update_reference + after_commit :generate_and_send_invoice, on: [:create] + + def file + dir = "invoices/#{user.id}" + + # create directories if they doesn't exists (invoice & user_id) + FileUtils::mkdir_p dir + "#{dir}/#{ENV['INVOICE_PREFIX']}-#{self.id}_#{self.created_at.strftime('%d%m%Y')}.pdf" + end + + + def generate_reference + pattern = Setting.find_by({name: 'invoice_reference'}).value + + # invoice number per day (dd..dd) + reference = pattern.gsub(/d+(?![^\[]*\])/) do |match| + pad_and_truncate(number_of_invoices('day'), match.to_s.length) + end + # invoice number per month (mm..mm) + reference.gsub!(/m+(?![^\[]*\])/) do |match| + pad_and_truncate(number_of_invoices('month'), match.to_s.length) + end + # invoice number per year (yy..yy) + reference.gsub!(/y+(?![^\[]*\])/) do |match| + pad_and_truncate(number_of_invoices('year'), match.to_s.length) + end + + # full year (YYYY) + reference.gsub!(/YYYY(?![^\[]*\])/, Time.now.strftime('%Y')) + # year without century (YY) + reference.gsub!(/YY(?![^\[]*\])/, Time.now.strftime('%y')) + + # abreviated month name (MMM) + reference.gsub!(/MMM(?![^\[]*\])/, Time.now.strftime('%^b')) + # month of the year, zero-padded (MM) + reference.gsub!(/MM(?![^\[]*\])/, Time.now.strftime('%m')) + # month of the year, non zero-padded (M) + reference.gsub!(/M(?![^\[]*\])/, Time.now.strftime('%-m')) + + # day of the month, zero-padded (DD) + reference.gsub!(/DD(?![^\[]*\])/, Time.now.strftime('%d')) + # day of the month, non zero-padded (DD) + reference.gsub!(/DD(?![^\[]*\])/, Time.now.strftime('%-d')) + + # information about online selling (X[text]) + if self.stp_invoice_id + reference.gsub!(/X\[([^\]]+)\]/, '\1') + else + reference.gsub!(/X\[([^\]]+)\]/, ''.to_s) + end + + # remove information about refunds (R[text]) + reference.gsub!(/R\[([^\]]+)\]/, ''.to_s) + + self.reference = reference + end + + def update_reference + generate_reference + save + end + + def order_number + pattern = Setting.find_by({name: 'invoice_order-nb'}).value + + # global invoice number (nn..nn) + reference = pattern.gsub(/n+(?![^\[]*\])/) do |match| + pad_and_truncate(number_of_invoices('global'), match.to_s.length) + end + # invoice number per year (yy..yy) + reference.gsub!(/y+(?![^\[]*\])/) do |match| + pad_and_truncate(number_of_invoices('year'), match.to_s.length) + end + # invoice number per month (mm..mm) + reference.gsub!(/m+(?![^\[]*\])/) do |match| + pad_and_truncate(number_of_invoices('month'), match.to_s.length) + end + # invoice number per day (dd..dd) + reference.gsub!(/d+(?![^\[]*\])/) do |match| + pad_and_truncate(number_of_invoices('day'), match.to_s.length) + end + + # full year (YYYY) + reference.gsub!(/YYYY(?![^\[]*\])/, self.created_at.strftime('%Y')) + # year without century (YY) + reference.gsub!(/YY(?![^\[]*\])/, self.created_at.strftime('%y')) + + # abreviated month name (MMM) + reference.gsub!(/MMM(?![^\[]*\])/, self.created_at.strftime('%^b')) + # month of the year, zero-padded (MM) + reference.gsub!(/MM(?![^\[]*\])/, self.created_at.strftime('%m')) + # month of the year, non zero-padded (M) + reference.gsub!(/M(?![^\[]*\])/, self.created_at.strftime('%-m')) + + # day of the month, zero-padded (DD) + reference.gsub!(/DD(?![^\[]*\])/, self.created_at.strftime('%d')) + # day of the month, non zero-padded (DD) + reference.gsub!(/DD(?![^\[]*\])/, self.created_at.strftime('%-d')) + + reference + end + + # only for debug + def regenerate_invoice_pdf + pdf = ::PDF::Invoice.new(self).render + File.binwrite(file, pdf) + end + + def build_avoir(attrs = {}) + raise Exception if has_avoir === true or prevent_refund? + avoir = Avoir.new(self.dup.attributes) + avoir.type = 'Avoir' + avoir.attributes = attrs + avoir.reference = nil + avoir.invoice_id = id + # override created_at to compute CA in stats + avoir.created_at = avoir.avoir_date + avoir.total = 0 + invoice_items.each do |ii| + if attrs[:invoice_items_ids].include? ii.id + raise Exception if ii.invoice_item + avoir_ii = avoir.invoice_items.build(ii.dup.attributes) + avoir_ii.created_at = avoir.avoir_date + avoir_ii.invoice_item_id = ii.id + avoir.total += avoir_ii.amount + end + end + avoir + end + + def is_subscription_invoice? + invoice_items.each do |ii| + return true if ii.subscription and !ii.subscription.is_expired? + end + false + end + + ## + # Test if the current invoice has been refund, totally or partially. + # @return {Boolean|'partial'}, true means fully refund, false means not refunded + ## + def has_avoir + if avoir + invoice_items.each do |item| + return 'partial' unless item.invoice_item + end + true + else + false + end + end + + ## + # Check if the current invoice is about a training that was previously validated for the concerned user. + # In that case refunding the invoice must not be not allowed. + # @return {Boolean} + ## + def prevent_refund? + if invoiced_type == 'Reservation' and invoiced.reservable_type == 'Training' + user.trainings.include?(invoiced.reservable_id) + else + false + end + end + + private + def generate_and_send_invoice + InvoiceWorker.perform_async(id) + end + + ## + # Output the given integer with leading zeros. If the given value is longer than the given + # length, it will be truncated. + # @param value {Integer} the integer to pad + # @param length {Integer} the length of the resulting string. + ## + def pad_and_truncate (value, length) + value.to_s.rjust(length, '0').gsub(/^.*(.{#{length},}?)$/m,'\1') + end + + ## + # Returns the number of current invoices in the given range around the current date. + # If range is invalid or not specified, the total number of invoices is returned. + # @param range {String} 'day', 'month', 'year' + # @return {Integer} + ## + def number_of_invoices(range) + case range.to_s + when 'day' + start = DateTime.current.beginning_of_day + ending = DateTime.current.end_of_day + when 'month' + start = DateTime.current.beginning_of_month + ending = DateTime.current.end_of_month + when 'year' + start = DateTime.current.beginning_of_year + ending = DateTime.current.end_of_year + else + return self.id + end + if defined? start and defined? ending + Invoice.where('created_at >= :start_date AND created_at < :end_date', {start_date: start, end_date: ending}).length + end + end + +end diff --git a/app/models/invoice_item.rb b/app/models/invoice_item.rb new file mode 100644 index 000000000..68c8193f8 --- /dev/null +++ b/app/models/invoice_item.rb @@ -0,0 +1,6 @@ +class InvoiceItem < ActiveRecord::Base + belongs_to :invoice + belongs_to :subscription + + has_one :invoice_item +end diff --git a/app/models/machine.rb b/app/models/machine.rb index cc62fa17f..4b429a0d0 100644 --- a/app/models/machine.rb +++ b/app/models/machine.rb @@ -1,16 +1,70 @@ class Machine < ActiveRecord::Base extend FriendlyId friendly_id :name, use: :slugged - + has_one :machine_image, as: :viewable, dependent: :destroy accepts_nested_attributes_for :machine_image, allow_destroy: true - has_many :machine_files, as: :viewable, dependent: :destroy - accepts_nested_attributes_for :machine_files, allow_destroy: true + accepts_nested_attributes_for :machine_files, allow_destroy: true, reject_if: :all_blank - has_and_belongs_to_many :projects, join_table: 'projects_machines' + has_and_belongs_to_many :projects, join_table: :projects_machines + + has_many :machines_availabilities, dependent: :destroy + has_many :availabilities, through: :machines_availabilities + + has_and_belongs_to_many :trainings, join_table: :trainings_machines validates :name, presence: true, length: { maximum: 50 } validates :description, presence: true + has_many :prices, as: :priceable, dependent: :destroy + + has_many :reservations, as: :reservable, dependent: :destroy + has_many :credits, as: :creditable, dependent: :destroy + has_many :plans, through: :credits + + + after_create :create_statistic_subtype + after_create :create_machine_prices + after_update :update_statistic_subtype, if: :name_changed? + after_destroy :remove_statistic_subtype + + def not_subscribe_price(group_id) + prices.find_by(plan_id: nil, group_id: group_id) + end + + def prices_by_group(group_id, plan_id = nil) + prices.where.not(plan_id: plan_id).where(group_id: group_id) + end + + def create_statistic_subtype + index = StatisticIndex.where(es_type_key: 'machine') + StatisticSubType.create!({statistic_types: index.first.statistic_types, key: self.slug, label: self.name}) + end + + def update_statistic_subtype + index = StatisticIndex.where(es_type_key: 'machine') + subtype = StatisticSubType.joins(statistic_type_sub_types: :statistic_type).where(key: self.slug, statistic_types: { statistic_index_id: index.first.id }).first + subtype.label = self.name + subtype.save! + end + + def remove_statistic_subtype + subtype = StatisticSubType.where(key: self.slug).first + subtype.destroy! + end + + def create_machine_prices + Group.all.each do |group| + Price.create(priceable: self, group: group, amount: 0) + end + + Plan.all.includes(:group).each do |plan| + Price.create(group: plan.group, plan: plan, priceable: self, amount: 0) + end + end + + def destroyable? + reservations.empty? + end end diff --git a/app/models/machines_availability.rb b/app/models/machines_availability.rb new file mode 100644 index 000000000..67c2209f8 --- /dev/null +++ b/app/models/machines_availability.rb @@ -0,0 +1,16 @@ +class MachinesAvailability < ActiveRecord::Base + belongs_to :machine + belongs_to :availability + after_destroy :cleanup_availability + + # when the MachinesAvailability is deleted (from Machine destroy cascade), we delete the corresponding + # availability if the deleted machine was the last is this availability slot and teh availability is not + # currently being destroyed. + def cleanup_availability + unless availability.destroying + if availability.machines_availabilities.size == 0 + availability.safe_destroy + end + end + end +end diff --git a/app/models/machines_pricing.rb b/app/models/machines_pricing.rb index d74538904..f52d5d245 100644 --- a/app/models/machines_pricing.rb +++ b/app/models/machines_pricing.rb @@ -1,14 +1,9 @@ +##DEPRECATED, this class is not used anymore from the migration to the Pricing model +##TODO remove in future update, in conjunction with the following migrations: +# - 20140606133116_create_machines_pricings.rb +# - 20150520133409_migrate_data_from_machines_pricings_to_prices.rb +# - 20150603133050_drop_machines_pricings.rb class MachinesPricing < ActiveRecord::Base belongs_to :machine belongs_to :group - - def amount_by_plan(plan) - return not_subscribe_amount if plan.blank? - plan = Plan.find(plan) - if plan.interval == 'month' - month_amount - else - year_amount - end - end end diff --git a/app/models/notification.rb b/app/models/notification.rb index 1b8242af9..49b6a241b 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -1,3 +1,7 @@ class Notification < ActiveRecord::Base include NotifyWith::Notification + + def get_meta_data(key) + meta_data.try(:[], key.to_s) + end end diff --git a/app/models/notification_type.rb b/app/models/notification_type.rb index 66d3b6400..38e8376f8 100644 --- a/app/models/notification_type.rb +++ b/app/models/notification_type.rb @@ -6,6 +6,36 @@ class NotificationType notify_admin_when_project_published notify_project_collaborator_to_valid notify_project_author_when_collaborator_valid + notify_user_training_valid + notify_member_subscribed_plan + notify_member_create_reservation + notify_member_subscribed_plan_is_changed + notify_admin_member_create_reservation + notify_member_slot_is_modified + notify_admin_slot_is_modified notify_admin_when_user_is_created + notify_admin_subscribed_plan + notify_user_when_invoice_ready + notify_member_subscription_will_expire_in_7_days + notify_member_subscription_is_expired + notify_admin_subscription_will_expire_in_7_days + notify_admin_subscription_is_expired + notify_admin_subscription_canceled + notify_member_subscription_canceled + notify_user_when_avoir_ready + notify_member_slot_is_canceled + notify_admin_slot_is_canceled + notify_partner_subscribed_plan + notify_member_subscription_extended + notify_admin_subscription_extended + notify_admin_user_group_changed + notify_user_user_group_changed + notify_admin_when_user_is_imported + notify_user_profile_complete + notify_user_auth_migration + notify_admin_user_merged + notify_admin_profile_complete + notify_admin_abuse_reported + notify_admin_invoicing_changed ) end diff --git a/app/models/o_auth2_mapping.rb b/app/models/o_auth2_mapping.rb new file mode 100644 index 000000000..933925c2b --- /dev/null +++ b/app/models/o_auth2_mapping.rb @@ -0,0 +1,3 @@ +class OAuth2Mapping < ActiveRecord::Base + belongs_to :o_auth2_provider +end diff --git a/app/models/o_auth2_provider.rb b/app/models/o_auth2_provider.rb new file mode 100644 index 000000000..6e918bfb4 --- /dev/null +++ b/app/models/o_auth2_provider.rb @@ -0,0 +1,17 @@ +class OAuth2Provider < ActiveRecord::Base + has_one :auth_provider, as: :providable + has_many :o_auth2_mappings, dependent: :destroy + accepts_nested_attributes_for :o_auth2_mappings, allow_destroy: true + + def domain + URI(base_url).scheme+'://'+URI(base_url).host + end + + def protected_fields + fields = [] + o_auth2_mappings.each do |mapping| + fields.push(mapping.local_model+'.'+mapping.local_field) + end + fields + end +end diff --git a/app/models/offer_day.rb b/app/models/offer_day.rb new file mode 100644 index 000000000..6f9c18f75 --- /dev/null +++ b/app/models/offer_day.rb @@ -0,0 +1,6 @@ +class OfferDay < ActiveRecord::Base + include NotifyWith::NotificationAttachedObject + + has_many :invoices, as: :invoiced, dependent: :destroy + belongs_to :subscription +end diff --git a/app/models/partner_plan.rb b/app/models/partner_plan.rb new file mode 100644 index 000000000..d6ee3a3f4 --- /dev/null +++ b/app/models/partner_plan.rb @@ -0,0 +1,14 @@ +class PartnerPlan < Plan + resourcify + + before_create :assign_default_values + + def partners + User.joins(:roles).where(roles: { name: 'partner', resource_type: 'PartnerPlan', resource_id: self.id }) + end + + private + def assign_default_values + assign_attributes(is_rolling: false) + end +end diff --git a/app/models/plan.rb b/app/models/plan.rb new file mode 100644 index 000000000..176488348 --- /dev/null +++ b/app/models/plan.rb @@ -0,0 +1,118 @@ +class Plan < ActiveRecord::Base + belongs_to :group + + has_many :credits, dependent: :destroy + has_many :training_credits, -> {where(creditable_type: 'Training')}, class_name: 'Credit' + has_many :machine_credits, -> {where(creditable_type: 'Machine')}, class_name: 'Credit' + has_many :subscriptions + has_one :plan_image, as: :viewable, dependent: :destroy + has_one :plan_file, as: :viewable, dependent: :destroy + has_many :prices, dependent: :destroy + + accepts_nested_attributes_for :prices + accepts_nested_attributes_for :plan_file, allow_destroy: true, reject_if: :all_blank + + after_update :update_stripe_plan, if: :amount_changed? + after_create :create_stripe_plan, unless: :skip_create_stripe_plan + after_create :create_machines_prices + after_create :create_statistic_type + after_destroy :delete_stripe_plan + + attr_accessor :skip_create_stripe_plan + + validates :amount, :group, :base_name, presence: true + validates :interval_count, numericality: { only_integer: true, greater_than_or_equal_to: 1 } + validates :interval, inclusion: { in: %w(year month) } + + def self.create_for_all_groups(plan_params) + plans = [] + Group.all.each do |group| + if plan_params[:type] == 'PartnerPlan' + plan = PartnerPlan.new(plan_params.except(:group_id, :type)) + else + plan = Plan.new(plan_params.except(:group_id, :type)) + end + plan.group = group + if plan.save + plans << plan + else + plans.each(&:destroy) + return false + end + end + return plans + end + + def destroyable? + subscriptions.empty? + end + + def create_machines_prices + Machine.all.each do |machine| + Price.create(priceable: machine, plan: self, group_id: self.group_id, amount: 0) + end + end + + def duration + interval_count.send(interval) + end + + def human_readable_duration + i18n_key = "duration.#{interval}" + "#{I18n.t(i18n_key, count: interval_count)}" + end + + def human_readable_name(opts = {}) + result = "#{base_name}" + result += " - #{group.slug}" if opts[:group] + result + " - #{human_readable_duration}" + end + + # must be publicly accessible for the migration + def create_statistic_type + stat_index = StatisticIndex.where({es_type_key: 'subscription'}) + type = StatisticType.find_by(statistic_index_id: stat_index.first.id, key: self.duration.to_i) + if type == nil + type = StatisticType.create!({statistic_index_id: stat_index.first.id, key: self.duration.to_i, label: 'Durée : '+self.human_readable_duration, graph: true, simple: true}) + end + subtype = create_statistic_subtype + create_statistic_association(type, subtype) + end + + private + def create_stripe_plan + stripe_plan = Stripe::Plan.create( + amount: amount, + interval: interval, + interval_count: interval_count, + name: "#{base_name} - #{group.name} - #{interval}", + currency: Rails.application.secrets.stripe_currency, + id: "#{base_name.parameterize}-#{group.slug}-#{interval}-#{DateTime.now.to_s(:number)}" + ) + update_columns(stp_plan_id: stripe_plan.id, name: stripe_plan.name) + stripe_plan + end + + def create_statistic_subtype + StatisticSubType.create!({key: self.stp_plan_id, label: self.name}) + end + + def create_statistic_association(stat_type, stat_subtype) + if stat_type != nil and stat_subtype != nil + StatisticTypeSubType.create!({statistic_type: stat_type, statistic_sub_type: stat_subtype}) + else + puts 'ERROR: Unable to create the statistics association for the new plan. '+ + 'Possible causes: the type or the subtype were not created successfully.' + end + end + + def update_stripe_plan + old_stripe_plan = Stripe::Plan.retrieve(stp_plan_id) + old_stripe_plan.delete + create_stripe_plan + end + + def delete_stripe_plan + Stripe::Plan.retrieve(stp_plan_id).delete + end +end diff --git a/app/models/plan_file.rb b/app/models/plan_file.rb new file mode 100644 index 000000000..1dc896995 --- /dev/null +++ b/app/models/plan_file.rb @@ -0,0 +1,3 @@ +class PlanFile < Asset + mount_uploader :attachment, PlanFileUploader +end diff --git a/app/models/plan_image.rb b/app/models/plan_image.rb new file mode 100644 index 000000000..ab76a0658 --- /dev/null +++ b/app/models/plan_image.rb @@ -0,0 +1,4 @@ +class PlanImage < Asset + mount_uploader :attachment, PlanImageUploader + validates :attachment, file_size: { maximum: 2.megabytes.to_i } +end diff --git a/app/models/price.rb b/app/models/price.rb new file mode 100644 index 000000000..f2b7c379c --- /dev/null +++ b/app/models/price.rb @@ -0,0 +1,133 @@ +class Price < ActiveRecord::Base + belongs_to :group + belongs_to :plan + belongs_to :priceable, polymorphic: true + + validates :priceable, :group_id, :amount, presence: true + validates :priceable_id, uniqueness: { scope: [:priceable_type, :plan_id, :group_id] } + + ## + # @param admin {Boolean} true if the current user (ie.the user who requests the price) is an admin + # @param user {User} The user who's reserving (or selected if an admin is reserving) + # @param reservable {Machine|Training|Event} what the reservation is targeting + # @param slots {Array} when did the reservation will occur + # @param [plan_id] {Number} if the user is subscribing to a plan at the same time of his reservation, specify the plan's ID here + # @param [nb_places] {Number} for _reservable_ of type Event, pass here the number of booked places + # @param [nb_reduced_places] {Number} for _reservable_ of type Event, pass here the number of booked places at reduced price + # @return {Hash} total and price detail + ## + def self.compute(admin, user, reservable, slots, plan_id = nil, nb_places = nil, nb_reduced_places = nil) + _amount = 0 + _elements = Hash.new + _elements[:slots] = Array.new + + # initialize Plan + if user.subscribed_plan + plan = user.subscribed_plan + new_plan_being_bought = false + elsif plan_id + plan = Plan.find(plan_id) + new_plan_being_bought = true + else + plan = nil + end + + # === compute reservation price === + + case reservable + + # Machine reservation + when Machine + base_amount = reservable.prices.find_by(group_id: user.group_id, plan_id: plan.try(:id)).amount + if plan + machine_credit = plan.machine_credits.select {|credit| credit.creditable_id == reservable.id}.first + if machine_credit + hours_available = machine_credit.hours + if !new_plan_being_bought + user_credit = user.users_credits.find_by_credit_id(machine_credit.id) + if user_credit + hours_available = machine_credit.hours - user_credit.hours_used + end + end + slots.each_with_index do |slot, index| + _amount += get_slot_price(base_amount, slot, admin, _elements, (index < hours_available)) + end + else + slots.each do |slot| + _amount += get_slot_price(base_amount, slot, admin, _elements) + end + end + else + slots.each do |slot| + _amount += get_slot_price(base_amount, slot, admin, _elements) + end + end + + # Training reservation + when Training + amount = reservable.amount_by_group(user.group_id).amount + if plan + # Return True if the subscription link a training credit for training reserved by the user + training_is_creditable = plan.training_credits.select {|credit| credit.creditable_id == reservable.id}.size > 0 + + # Training reserved by the user is free when : + + # |-> the user already has a current subscription and if training_is_creditable is true and has at least one credit available. + if !new_plan_being_bought + if user.training_credits.size < plan.training_credit_nb and training_is_creditable + amount = 0 + end + # |-> the user buys a new subscription and if training_is_creditable is true. + else + if training_is_creditable + amount = 0 + end + end + end + slots.each do |slot| + _amount += get_slot_price(amount, slot, admin, _elements) + end + + # Event reservation + when Event + if reservable.reduced_amount and nb_reduced_places + amount = reservable.amount * nb_places + (reservable.reduced_amount * nb_reduced_places) + else + amount = reservable.amount * nb_places + end + slots.each do |slot| + _amount += get_slot_price(amount, slot, admin, _elements) + end + + # Unknown reservation type + else + raise NotImplementedError + end + + # === compute Plan price if any === + unless plan_id.nil? + _elements[:plan] = plan.amount + _amount += plan.amount + end + + # return result + {elements: _elements, total: _amount} + end + + private + ## + # Compute the price of a single slot, according to the base price and the ability for an admin + # to offer the slot. + # @param base_amount {Number} base price of a slot + # @param slot {Hash} Slot object + # @param is_admin {Boolean} true if the current user has the 'admin' role + # @param [elements] {Array} optional, if provided the resulting price will be append into elements.slots + # @param [has_credits] {Boolean} true if the user still has credits for the given slot + # @return {Number} price of the slot + ## + def self.get_slot_price(base_amount, slot, is_admin, elements = nil, has_credits = false) + ii_amount = (((has_credits) or (slot[:offered] and is_admin)) ? 0 : base_amount) + elements[:slots].push({start_at: slot[:start_at], price: ii_amount, promo: (ii_amount != base_amount)}) unless elements.nil? + ii_amount + end +end diff --git a/app/models/profile.rb b/app/models/profile.rb index dd95011ca..0dcf5b8b5 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -14,7 +14,8 @@ class Profile < ActiveRecord::Base validates_numericality_of :phone, only_integer: true, allow_blank: false def full_name - first_name.humanize.titleize + ' ' + last_name.humanize.titleize + # if first_name or last_name is nil, the empty string will be used as a temporary replacement + (first_name || '').humanize.titleize + ' ' + (last_name || '').humanize.titleize end def to_s diff --git a/app/models/project.rb b/app/models/project.rb index a112deb48..4a0286bbc 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2,6 +2,15 @@ class Project < ActiveRecord::Base include AASM include NotifyWith::NotificationAttachedObject + # elastic initialisations + include Elasticsearch::Model + index_name 'fablab' + document_type 'projects' + + # kaminari + paginates_per 12 # dependency in projects.coffee + + # friendlyId extend FriendlyId friendly_id :name, use: :slugged @@ -37,6 +46,102 @@ class Project < ActiveRecord::Base #scopes scope :published, -> { where("state = 'published'") } + ## elastic + # callbacks + after_save { IndexerWorker.perform_async(:index, self.id) } + after_destroy { IndexerWorker.perform_async(:delete, self.id) } + + # + settings do + mappings dynamic: 'true' do + indexes 'state', analyzer: 'simple' + indexes 'tags', analyzer: Rails.application.secrets.elasticsearch_language_analyzer + indexes 'name', analyzer: Rails.application.secrets.elasticsearch_language_analyzer + indexes 'description', analyzer: Rails.application.secrets.elasticsearch_language_analyzer + indexes 'project_steps' do + indexes 'title', analyzer: Rails.application.secrets.elasticsearch_language_analyzer + indexes 'description', analyzer: Rails.application.secrets.elasticsearch_language_analyzer + end + end + end + + def as_indexed_json + Jbuilder.new do |json| + json.id id + json.state state + json.author_id author_id + json.user_ids user_ids + json.machine_ids machine_ids + json.theme_ids theme_ids + json.component_ids component_ids + json.tags tags + json.name name + json.description description + json.project_steps project_steps do |project_step| + json.title project_step.title + json.description project_step.description + end + json.created_at created_at + json.updated_at updated_at + end.target! + end + + def self.search(params, current_user) + Project.__elasticsearch__.search(build_search_query_from_context(params, current_user)) + end + + def self.build_search_query_from_context(params, current_user) + search = { + query: { + filtered: { + filter: { + bool: { + must: [], + should: [] + } + } + } + } + } + + if params['q'].empty? # we sort by created_at if there isn't a query + search[:sort] = { created_at: { order: :desc } } + else # otherwise we search for the word (q) in various fields + search[:query][:filtered][:query] = { + multi_match: { + query: params['q'], + type: 'most_fields', + fields: %w(tags^4 name^5 description^3 project_steps.title^2 project_steps.description) + } + } + end + + params.each do |name, value| # we filter by themes, components, machines + if name =~ /(.+_id$)/ + search[:query][:filtered][:filter][:bool][:must] << { term: { "#{name}s" => value } } if value + end + end + + if current_user and params.key?('from') # if use select filter 'my project' or 'my collaborations' + if params['from'] == 'mine' + search[:query][:filtered][:filter][:bool][:must] << { term: { author_id: current_user.id } } + end + if params['from'] == 'collaboration' + search[:query][:filtered][:filter][:bool][:must] << { term: { user_ids: current_user.id } } + end + end + + if current_user # if user is connect, also display his draft projects + search[:query][:filtered][:filter][:bool][:should] << { term: { state: 'published' } } + search[:query][:filtered][:filter][:bool][:should] << { term: { author_id: current_user.id } } + search[:query][:filtered][:filter][:bool][:should] << { term: { user_ids: current_user.id } } + else # otherwise display only published projects + search[:query][:filtered][:filter][:bool][:must] << { term: { state: 'published' } } + end + + search + end + private def notify_admin_when_project_published NotificationCenter.call type: 'notify_admin_when_project_published', diff --git a/app/models/project_user.rb b/app/models/project_user.rb index 5e94de4f2..f7dd9f369 100644 --- a/app/models/project_user.rb +++ b/app/models/project_user.rb @@ -5,7 +5,7 @@ class ProjectUser < ActiveRecord::Base belongs_to :user before_create :generate_valid_token - after_create :notify_project_collaborator_to_valid + after_commit :notify_project_collaborator_to_valid, on: :create after_update :notify_project_author_when_collaborator_valid, if: :is_valid_changed? private diff --git a/app/models/reservation.rb b/app/models/reservation.rb new file mode 100644 index 000000000..48b93ad8f --- /dev/null +++ b/app/models/reservation.rb @@ -0,0 +1,400 @@ +class Reservation < ActiveRecord::Base + include NotifyWith::NotificationAttachedObject + + belongs_to :user + has_many :slots, dependent: :destroy + accepts_nested_attributes_for :slots, allow_destroy: true + belongs_to :reservable, polymorphic: true + + has_one :invoice, -> {where(type: nil)}, as: :invoiced, dependent: :destroy + + validates_presence_of :reservable_id, :reservable_type + validate :machine_not_already_reserved, if: -> { self.reservable.is_a?(Machine) } + validate :training_not_fully_reserved, if: -> { self.reservable.is_a?(Training) } + + attr_accessor :card_token, :plan_id, :subscription + + after_commit :notify_member_create_reservation, on: :create + after_commit :notify_admin_member_create_reservation, on: :create + after_save :update_event_nb_free_places, if: Proc.new { |reservation| reservation.reservable_type == 'Event' } + + # + # Generate an array of {Stripe::InvoiceItem} with the elements in the current reservation, price included. + # The training/machine price is depending of the member's group, subscription and credits already used + # @param on_site {Boolean} true if an admin triggered the call + # + def generate_invoice_items(on_site = false) + + # returning array + invoice_items = [] + + # prepare the plan + if user.subscribed_plan + plan = user.subscribed_plan + new_plan_being_bought = false + elsif plan_id + plan = Plan.find(plan_id) + new_plan_being_bought = true + else + plan = nil + end + + + case reservable + + # === Machine reservation === + when Machine + base_amount = reservable.prices.find_by(group_id: user.group_id, plan_id: plan.try(:id)).amount + if plan + machine_credit = plan.machine_credits.select {|credit| credit.creditable_id == reservable_id}.first + if machine_credit + hours_available = machine_credit.hours + if !new_plan_being_bought + user_credit = user.users_credits.find_by_credit_id(machine_credit.id) + if user_credit + hours_available = machine_credit.hours - user_credit.hours_used + end + end + slots.each_with_index do |slot, index| + description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" + ii_amount = (index < hours_available ? 0 : base_amount) + ii_amount = 0 if (slot.offered and on_site) + unless on_site + ii = Stripe::InvoiceItem.create( + customer: user.stp_customer_id, + amount: ii_amount, + currency: Rails.application.secrets.stripe_currency, + description: description + ) + invoice_items << ii + end + self.invoice.invoice_items.push InvoiceItem.new(amount: ii_amount, stp_invoice_item_id: (ii.id if ii), description: description) + end + else + slots.each do |slot| + description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" + ii_amount = base_amount + ii_amount = 0 if (slot.offered and on_site) + unless on_site + ii = Stripe::InvoiceItem.create( + customer: user.stp_customer_id, + amount: ii_amount, + currency: Rails.application.secrets.stripe_currency, + description: description + ) + invoice_items << ii + end + self.invoice.invoice_items.push InvoiceItem.new(amount: ii_amount, stp_invoice_item_id: (ii.id if ii), description: description) + end + end + else + slots.each do |slot| + description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" + ii_amount = base_amount + ii_amount = 0 if (slot.offered and on_site) + unless on_site + ii = Stripe::InvoiceItem.create( + customer: user.stp_customer_id, + amount: ii_amount, + currency: Rails.application.secrets.stripe_currency, + description: description + ) + invoice_items << ii + end + self.invoice.invoice_items.push InvoiceItem.new(amount: ii_amount, stp_invoice_item_id: (ii.id if ii), description: description) + end + end + + # === Training reservation === + when Training + base_amount = reservable.amount_by_group(user.group_id).amount + if plan + # Return True if the subscription link a training credit for training reserved by the user + training_is_creditable = plan.training_credits.select {|credit| credit.creditable_id == reservable.id}.size > 0 + + # Training reserved by the user is free when : + + # |-> the user already has a current subscription and if training_is_creditable is true and has at least one credit available. + if !new_plan_being_bought + if user.training_credits.size < plan.training_credit_nb and training_is_creditable + base_amount = 0 + end + # |-> the user buys a new subscription and if training_is_creditable is true. + else + if training_is_creditable + base_amount = 0 + end + end + + end + slots.each do |slot| + description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" + ii_amount = base_amount + ii_amount = 0 if (slot.offered and on_site) + unless on_site + ii = Stripe::InvoiceItem.create( + customer: user.stp_customer_id, + amount: ii_amount, + currency: Rails.application.secrets.stripe_currency, + description: description + ) + invoice_items << ii + end + self.invoice.invoice_items.push InvoiceItem.new(amount: ii_amount, stp_invoice_item_id: (ii.id if ii), description: description) + end + + # === Event reservation === + when Event + if reservable.reduced_amount and nb_reserve_reduced_places + amount = reservable.amount * nb_reserve_places + (reservable.reduced_amount * nb_reserve_reduced_places) + else + amount = reservable.amount * nb_reserve_places + end + slots.each do |slot| + description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" + ii_amount = amount + ii_amount = 0 if (slot.offered and on_site) + unless on_site + ii = Stripe::InvoiceItem.create( + customer: user.stp_customer_id, + amount: ii_amount, + currency: Rails.application.secrets.stripe_currency, + description: description + ) + invoice_items << ii + end + self.invoice.invoice_items.push InvoiceItem.new(amount: ii_amount, stp_invoice_item_id: (ii.id if ii), description: description) + end + + # === Unknown reservation type === + else + raise NotImplementedError + + end + + # let's return the resulting array of items + invoice_items + end + + def update_users_credits + if user.subscribed_plan + if reservable_type == 'Machine' + machine_credit = user.subscribed_plan.machine_credits.select {|credit| credit.creditable_id == reservable_id}.first + if machine_credit + hours_available = machine_credit.hours + user_credit = user.users_credits.find_or_initialize_by(credit_id: machine_credit.id) + user_credit.hours_used ||= 0 + hours_available = machine_credit.hours - user_credit.hours_used + if hours_available >= slots.size + user_credit.hours_used = user_credit.hours_used + slots.size + else + user_credit.hours_used = machine_credit.hours + end + user_credit.save + end + elsif reservable_type == 'Training' + training_credit = user.subscribed_plan.training_credits.select {|credit| credit.creditable_id == reservable_id}.first + if user.training_credits.size < user.subscribed_plan.training_credit_nb and training_credit + user.credits << training_credit + end + end + end + return self + end + + def save_with_payment + build_invoice(user: user) + invoice_items = generate_invoice_items + if valid? + # TODO: refactoring + customer = Stripe::Customer.retrieve(user.stp_customer_id) + if plan_id + self.subscription = Subscription.find_or_initialize_by(user_id: user.id) + self.subscription.attributes = {plan_id: plan_id, user_id: user.id, card_token: card_token, expired_at: nil} + if subscription.save_with_payment(false) + self.stp_invoice_id = invoice_items.first.refresh.invoice + self.invoice.stp_invoice_id = invoice_items.first.refresh.invoice + self.invoice.invoice_items.push InvoiceItem.new(amount: subscription.plan.amount, stp_invoice_item_id: subscription.stp_subscription_id, description: subscription.plan.name, subscription_id: subscription.id) + total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+) + self.invoice.total = total + save! + else + # error handling + invoice_items.each(&:delete) + errors[:card] << subscription.errors[:card].join + if subscription.errors[:payment] + errors[:payment] << subscription.errors[:payment].join + end + return false + end + + else + begin + if invoice_items.map(&:amount).map(&:to_i).reduce(:+) > 0 + card = customer.sources.create(card: card_token) + if customer.default_source.present? + customer.default_source = card.id + customer.save + end + end + invoice = Stripe::Invoice.create( + customer: user.stp_customer_id, + ) + invoice.pay + card.delete if card + self.stp_invoice_id = invoice.id + self.invoice.stp_invoice_id = invoice.id + self.invoice.total = invoice.total + save! + rescue Stripe::CardError => card_error + clear_payment_info(card, invoice, invoice_items) + logger.info card_error + errors[:card] << card_error.message + return false + rescue Stripe::InvalidRequestError => e + # Invalid parameters were supplied to Stripe's API + clear_payment_info(card, invoice, invoice_items) + logger.error e + errors[:payment] << e.message + return false + rescue Stripe::AuthenticationError => e + # Authentication with Stripe's API failed + # (maybe you changed API keys recently) + clear_payment_info(card, invoice, invoice_items) + logger.error e + errors[:payment] << e.message + return false + rescue Stripe::APIConnectionError => e + # Network communication with Stripe failed + clear_payment_info(card, invoice, invoice_items) + logger.error e + errors[:payment] << e.message + return false + rescue Stripe::StripeError => e + # Display a very generic error to the user, and maybe send + # yourself an email + clear_payment_info(card, invoice, invoice_items) + logger.error e + errors[:payment] << e.message + return false + rescue => e + # Something else happened, completely unrelated to Stripe + clear_payment_info(card, invoice, invoice_items) + logger.error e + errors[:payment] << e.message + return false + end + end + + update_users_credits + end + end + + def clear_payment_info(card, invoice, invoice_items) + begin + card.delete if card + if invoice + invoice.closed = true + invoice.save + end + if invoice_items.size > 0 + invoice_items.each(&:delete) + end + rescue Stripe::InvalidRequestError => e + logger.error e + rescue Stripe::AuthenticationError => e + logger.error e + rescue Stripe::APIConnectionError => e + logger.error e + rescue Stripe::StripeError => e + logger.error e + rescue => e + logger.error e + end + end + + + def save_with_local_payment + if user.invoicing_disabled? + if valid? + save! + update_users_credits + return true + end + else + build_invoice(user: user) + generate_invoice_items(true) + end + + if valid? + if plan_id + self.subscription = Subscription.find_or_initialize_by(user_id: user.id) + self.subscription.attributes = {plan_id: plan_id, user_id: user.id, expired_at: nil} + if subscription.save_with_local_payment(false) + self.invoice.invoice_items.push InvoiceItem.new(amount: subscription.plan.amount, description: subscription.plan.name, subscription_id: subscription.id) + total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+) + self.invoice.total = total + save! + else + errors[:card] << subscription.errors[:card].join + return false + end + else + total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+) + self.invoice.total = total + save! + end + + update_users_credits + end + end + + def machine_not_already_reserved + already_reserved = false + self.slots.each do |slot| + same_hour_slots = Slot.joins(:reservation).where( + reservations: { reservable_type: self.reservable_type, + reservable_id: self.reservable_id + }, + start_at: slot.start_at, + end_at: slot.end_at, + availability_id: slot.availability_id, + canceled_at: nil) + if same_hour_slots.any? + already_reserved = true + break + end + end + errors.add(:machine, "already reserved") if already_reserved + end + + def training_not_fully_reserved + slot = self.slots.first + errors.add(:training, "already fully reserved") if Availability.find(slot.availability_id).is_completed + end + + private + def notify_member_create_reservation + NotificationCenter.call type: 'notify_member_create_reservation', + receiver: user, + attached_object: self + end + + def notify_admin_member_create_reservation + NotificationCenter.call type: 'notify_admin_member_create_reservation', + receiver: User.admins, + attached_object: self + end + + def update_event_nb_free_places + if reservable_id_was.blank? + nb_free_places = reservable.nb_free_places - nb_reserve_places - nb_reserve_reduced_places + else + reservable_was = Event.find(reservable_id_was) + nb_free_places = reservable_was.nb_free_places + nb_reserve_places + nb_reserve_reduced_places + reservable_was.update_columns(nb_free_places: nb_free_places) + nb_free_places = reservable.nb_free_places - nb_reserve_places - nb_reserve_reduced_places + end + reservable.update_columns(nb_free_places: nb_free_places) + end +end diff --git a/app/models/role.rb b/app/models/role.rb index f8583ab97..ce3605cc4 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -2,9 +2,5 @@ class Role < ActiveRecord::Base has_and_belongs_to_many :users, :join_table => :users_roles belongs_to :resource, :polymorphic => true - validates :resource_type, - :inclusion => { :in => Rolify.resource_types }, - :allow_nil => true - scopify end diff --git a/app/models/setting.rb b/app/models/setting.rb new file mode 100644 index 000000000..e156b0a66 --- /dev/null +++ b/app/models/setting.rb @@ -0,0 +1,44 @@ +class Setting < ActiveRecord::Base + validates :name, inclusion: + { in: %w(about_title + about_body + about_contacts + twitter_name + home_blogpost + machine_explications_alert + training_explications_alert + training_information_message + subscription_explications_alert + event_reduced_amount_alert + invoice_logo + invoice_reference + invoice_code-active + invoice_code-value + invoice_order-nb + invoice_VAT-active + invoice_VAT-rate + invoice_text + invoice_legals + booking_window_start + booking_window_end + booking_slot_duration + booking_move_enable + booking_move_delay + booking_cancel_enable + booking_cancel_delay + main_color + secondary_color + fablab_name + name_genre) + } + + after_update :update_stylesheet if :value_changed? + + def update_stylesheet + if %w(main_color secondary_color).include? self.name + Stylesheet.first.rebuild! + end + end + + +end diff --git a/app/models/slot.rb b/app/models/slot.rb new file mode 100644 index 000000000..1c1d14488 --- /dev/null +++ b/app/models/slot.rb @@ -0,0 +1,49 @@ +class Slot < ActiveRecord::Base + include NotifyWith::NotificationAttachedObject + + belongs_to :reservation + belongs_to :availability + + attr_accessor :is_reserved, :machine, :title, :can_modify, :is_reserved_by_current_user + + after_update :set_ex_start_end_dates_attrs, if: :dates_were_modified? + after_update :notify_member_and_admin_slot_is_modified, if: :dates_were_modified? + + after_update :notify_member_and_admin_slot_is_canceled, if: :canceled? + + private + def notify_member_and_admin_slot_is_modified + NotificationCenter.call type: 'notify_member_slot_is_modified', + receiver: reservation.user, + attached_object: self + NotificationCenter.call type: 'notify_admin_slot_is_modified', + receiver: User.admins, + attached_object: self + end + + def notify_member_and_admin_slot_is_canceled + NotificationCenter.call type: 'notify_member_slot_is_canceled', + receiver: reservation.user, + attached_object: self + NotificationCenter.call type: 'notify_admin_slot_is_canceled', + receiver: User.admins, + attached_object: self + end + + def can_be_modified? + return false if (start_at - Time.now) / 1.day < 1 + return true + end + + def dates_were_modified? + start_at_changed? or end_at_changed? + end + + def canceled? + canceled_at_changed? + end + + def set_ex_start_end_dates_attrs + update_columns(ex_start_at: start_at_was, ex_end_at: end_at_was) + end +end diff --git a/app/models/statistic_field.rb b/app/models/statistic_field.rb new file mode 100644 index 000000000..b4c37da4b --- /dev/null +++ b/app/models/statistic_field.rb @@ -0,0 +1,3 @@ +class StatisticField < ActiveRecord::Base + has_one :statistic_index +end diff --git a/app/models/statistic_graph.rb b/app/models/statistic_graph.rb new file mode 100644 index 000000000..0a46996cb --- /dev/null +++ b/app/models/statistic_graph.rb @@ -0,0 +1,3 @@ +class StatisticGraph < ActiveRecord::Base + belongs_to :statistic_index +end diff --git a/app/models/statistic_index.rb b/app/models/statistic_index.rb new file mode 100644 index 000000000..c491b428d --- /dev/null +++ b/app/models/statistic_index.rb @@ -0,0 +1,5 @@ +class StatisticIndex < ActiveRecord::Base + has_many :statistic_types + has_many :statistic_fields + has_one :statistic_graph +end diff --git a/app/models/statistic_sub_type.rb b/app/models/statistic_sub_type.rb new file mode 100644 index 000000000..bed0465cd --- /dev/null +++ b/app/models/statistic_sub_type.rb @@ -0,0 +1,4 @@ +class StatisticSubType < ActiveRecord::Base + has_many :statistic_type_sub_types, dependent: :destroy + has_many :statistic_types, through: :statistic_type_sub_types +end diff --git a/app/models/statistic_type.rb b/app/models/statistic_type.rb new file mode 100644 index 000000000..1a348668b --- /dev/null +++ b/app/models/statistic_type.rb @@ -0,0 +1,5 @@ +class StatisticType < ActiveRecord::Base + has_one :statistic_index + has_many :statistic_type_sub_types + has_many :statistic_sub_types, through: :statistic_type_sub_types +end diff --git a/app/models/statistic_type_sub_type.rb b/app/models/statistic_type_sub_type.rb new file mode 100644 index 000000000..66d1efc60 --- /dev/null +++ b/app/models/statistic_type_sub_type.rb @@ -0,0 +1,4 @@ +class StatisticTypeSubType < ActiveRecord::Base + belongs_to :statistic_type + belongs_to :statistic_sub_type +end diff --git a/app/models/stats/account.rb b/app/models/stats/account.rb new file mode 100644 index 000000000..85f6f5671 --- /dev/null +++ b/app/models/stats/account.rb @@ -0,0 +1,6 @@ +module Stats + class Account + include Elasticsearch::Persistence::Model + include StatConcern + end +end diff --git a/app/models/stats/event.rb b/app/models/stats/event.rb new file mode 100644 index 000000000..e2d77cb1f --- /dev/null +++ b/app/models/stats/event.rb @@ -0,0 +1,10 @@ +module Stats + class Event + include Elasticsearch::Persistence::Model + include StatConcern + include StatReservationConcern + + attribute :eventId, Integer + attribute :eventDate, String + end +end diff --git a/app/models/stats/machine.rb b/app/models/stats/machine.rb new file mode 100644 index 000000000..39c0704e0 --- /dev/null +++ b/app/models/stats/machine.rb @@ -0,0 +1,9 @@ +module Stats + class Machine + include Elasticsearch::Persistence::Model + include StatConcern + include StatReservationConcern + + attribute :machineId, Integer + end +end diff --git a/app/models/stats/project.rb b/app/models/stats/project.rb new file mode 100644 index 000000000..d1ecf9fad --- /dev/null +++ b/app/models/stats/project.rb @@ -0,0 +1,14 @@ +module Stats + class Project + include Elasticsearch::Persistence::Model + include StatConcern + + attribute :projectId, Integer + attribute :name, String + attribute :licence, Hash + attribute :themes, Array + attribute :components, Array + attribute :machines, Array + attribute :users, Integer + end +end diff --git a/app/models/stats/subscription.rb b/app/models/stats/subscription.rb new file mode 100644 index 000000000..7fef5fd2f --- /dev/null +++ b/app/models/stats/subscription.rb @@ -0,0 +1,12 @@ +module Stats + class Subscription + include Elasticsearch::Persistence::Model + include StatConcern + + attribute :ca, Float + attribute :planId, Integer + attribute :subscriptionId, Integer + attribute :invoiceItemId, Integer + attribute :groupName, String + end +end diff --git a/app/models/stats/training.rb b/app/models/stats/training.rb new file mode 100644 index 000000000..79854fecb --- /dev/null +++ b/app/models/stats/training.rb @@ -0,0 +1,10 @@ +module Stats + class Training + include Elasticsearch::Persistence::Model + include StatConcern + include StatReservationConcern + + attribute :trainingId, Integer + attribute :trainingDate, String + end +end diff --git a/app/models/stats/user.rb b/app/models/stats/user.rb new file mode 100644 index 000000000..0720844d0 --- /dev/null +++ b/app/models/stats/user.rb @@ -0,0 +1,6 @@ +module Stats + class User + include Elasticsearch::Persistence::Model + include StatConcern + end +end diff --git a/app/models/stylesheet.rb b/app/models/stylesheet.rb new file mode 100644 index 000000000..9909b9615 --- /dev/null +++ b/app/models/stylesheet.rb @@ -0,0 +1,79 @@ +class Stylesheet < ActiveRecord::Base + validates_presence_of :contents + + def rebuild! + self.update(contents: Stylesheet.css) + end + + def self.build_sheet! + unless Stylesheet.first + Stylesheet.create!(contents: Stylesheet.css) + end + end + + private + def self.primary + Setting.find_by(name: 'main_color').value + end + + def self.secondary + Setting.find_by(name: 'secondary_color').value + end + + def self.primary_light + self.primary.paint.lighten(10) + end + + def self.primary_dark + self.primary.paint.darken(20) + end + + def self.secondary_light + self.secondary.paint.lighten(10) + end + + def self.secondary_dark + self.secondary.paint.darken(20) + end + + def self.primary_with_alpha(alpha) + self.primary.paint.to_rgb.insert(3, 'a').insert(-2, ", #{alpha}") + end + + def self.css + ".bg-red { background-color: #{Stylesheet.primary}; } + .bg-red-dark { background-color: #{Stylesheet.primary}; } + #nav .nav { background-color: #{Stylesheet.primary}; } + #nav .nav > li > a { color: white; } + #nav .nav > li > a:hover, #nav .nav > li > a:focus { background-color: #{Stylesheet.primary_light}; } + #nav .nav > li > a.active { border-left: 3px solid #{Stylesheet.primary_dark}; background-color: #{Stylesheet.primary_light}; } + #nav .nav > li > a.active { border-left: 3px solid #{Stylesheet.primary_dark}; background-color: #{Stylesheet.primary_light}; } + .btn-theme { background-color: #{Stylesheet.primary}; color: white; } + .btn-theme:active, .btn-theme:hover { background-color: #{Stylesheet.primary_dark}; } + .label-theme { background-color: #{Stylesheet.primary} } + .btn-link { color: #{Stylesheet.primary} !important; } + .btn-link:hover { color: #{Stylesheet.primary_dark} !important; } + a { color: #{Stylesheet.primary}; } + a:hover, a:focus { color: #{Stylesheet.primary_dark}; } + h2, h3, h5 { color: #{Stylesheet.primary}; } + h5:after { background-color: #{Stylesheet.primary}; } + .bg-yellow { background-color: #{Stylesheet.secondary} !important; } + .event:hover { background-color: #{Stylesheet.primary}; } + .widget h3 { color: #{Stylesheet.primary}; } + .modal-header h1, .custom-invoice .modal-header h1 { color: #{Stylesheet.primary}; } + .block-link:hover, .fc-toolbar .fc-button:hover, .fc-toolbar .fc-button:active, .fc-toolbar .fc-button.fc-state-active { background-color: #{Stylesheet.secondary}; } + .carousel-control:hover, .carousel-control:focus, .carousel-caption .title a:hover { color: #{Stylesheet.secondary}; } + .well.well-warning { border-color: #{Stylesheet.secondary}; background-color: #{Stylesheet.secondary}; } + .text-yellow { color: #{Stylesheet.secondary} !important; } + .red { color: #{Stylesheet.primary} !important; } + .btn-warning, .editable-buttons button[type=submit].btn-primary { background-color: #{Stylesheet.secondary} !important; border-color: #{Stylesheet.secondary} !important; } + .btn-warning:hover, .editable-buttons button[type=submit].btn-primary:hover, .btn-warning:focus, .editable-buttons button[type=submit].btn-primary:focus, .btn-warning.focus, .editable-buttons button.focus[type=submit].btn-primary, .btn-warning:active, .editable-buttons button[type=submit].btn-primary:active, .btn-warning.active, .editable-buttons button.active[type=submit].btn-primary, .open > .btn-warning.dropdown-toggle, .editable-buttons .open > button.dropdown-toggle[type=submit].btn-primary { background-color: #{Stylesheet.secondary_dark} !important; border-color: #{Stylesheet.secondary_dark} !important; } + .btn-warning-full { border-color: #{Stylesheet.secondary}; background-color: #{Stylesheet.secondary}; } + .heading .heading-btn a:hover { background-color: #{Stylesheet.secondary}; } + .pricing-panel .content .wrap { border-color: #{Stylesheet.secondary}; } + .pricing-panel .cta-button .btn:hover, .pricing-panel .cta-button .custom-invoice .modal-body .elements li:hover, .custom-invoice .modal-body .elements .pricing-panel .cta-button li:hover { background-color: #{Stylesheet.secondary} !important; } + a.label:hover, .form-control.form-control-ui-select .select2-choices a.select2-search-choice:hover, a.label:focus, .form-control.form-control-ui-select .select2-choices a.select2-search-choice:focus { color: #{Stylesheet.primary}; } + .about-picture { background: linear-gradient( rgba(255,255,255,0.12), rgba(255,255,255,0.13) ), linear-gradient( #{Stylesheet.primary_with_alpha(0.78)}, #{Stylesheet.primary_with_alpha(0.82)} ), url('/about-fablab.jpg') no-repeat; }" + + end +end diff --git a/app/models/subscription.rb b/app/models/subscription.rb new file mode 100644 index 000000000..ddb8d5220 --- /dev/null +++ b/app/models/subscription.rb @@ -0,0 +1,232 @@ +class Subscription < ActiveRecord::Base + include NotifyWith::NotificationAttachedObject + + belongs_to :plan + belongs_to :user + + has_many :invoices, as: :invoiced, dependent: :destroy + has_many :offer_days, dependent: :destroy + + validates_presence_of :plan_id + + attr_accessor :card_token + + # creation + after_save :notify_member_subscribed_plan, if: :is_new? + after_save :notify_admin_subscribed_plan, if: :is_new? + after_save :notify_partner_subscribed_plan, if: :of_partner_plan? + + # Stripe subscription payment + def save_with_payment(invoice = true) + if valid? + customer = Stripe::Customer.retrieve(user.stp_customer_id) + begin + new_subscription = customer.subscriptions.create(plan: plan.stp_plan_id, card: card_token) + self.stp_subscription_id = new_subscription.id + self.canceled_at = nil + self.expired_at = Time.at(new_subscription.current_period_end) + save! + + reset_users_credits if expired_date_changed + + # generate directement invoice + stp_invoice = Stripe::Invoice.all(customer: user.stp_customer_id, limit: 1).data.first + generate_invoice(stp_invoice.id).save if invoice + # cancel subscription after create + cancel + return true + rescue Stripe::CardError => card_error + logger.error card_error + errors[:card] << card_error.message + return false + rescue Stripe::InvalidRequestError => e + # Invalid parameters were supplied to Stripe's API + logger.error e + errors[:payment] << e.message + return false + rescue Stripe::AuthenticationError => e + # Authentication with Stripe's API failed + # (maybe you changed API keys recently) + logger.error e + errors[:payment] << e.message + return false + rescue Stripe::APIConnectionError => e + # Network communication with Stripe failed + logger.error e + errors[:payment] << e.message + return false + rescue Stripe::StripeError => e + # Display a very generic error to the user, and maybe send + # yourself an email + logger.error e + errors[:payment] << e.message + return false + rescue => e + # Something else happened, completely unrelated to Stripe + logger.error e + errors[:payment] << e.message + return false + end + end + end + + def save_with_local_payment(invoice = true) + if valid? + self.stp_subscription_id = nil + self.canceled_at = nil + set_expired_at + save! + reset_users_credits if expired_date_changed + generate_invoice.save if invoice + return true + else + return false + end + end + + def generate_invoice(stp_invoice_id = nil) + invoice = Invoice.new(invoiced_id: id, invoiced_type: 'Subscription', user: user, total: plan.amount, stp_invoice_id: stp_invoice_id) + invoice.invoice_items.push InvoiceItem.new(amount: plan.amount, stp_invoice_item_id: stp_subscription_id, description: plan.name, subscription_id: self.id) + invoice + end + + def generate_and_save_invoice(stp_invoice_id = nil) + generate_invoice(stp_invoice_id).save + end + + def generate_and_save_offer_day_invoice(offer_day_start_at) + od = offer_days.create(start_at: offer_day_start_at, end_at: expired_at) + invoice = Invoice.new(invoiced_id: od.id, invoiced_type: 'OfferDay', user: user, total: 0) + invoice.invoice_items.push InvoiceItem.new(amount: 0, description: plan.name, subscription_id: self.id) + invoice.save + end + + def update_expired_date_with_first_training(training_start_at) + if plan.is_rolling? + update_columns(expired_at: training_start_at + plan.duration) + end + end + + def cancel + if stp_subscription_id.present? + stp_subscription = stripe_subscription + stp_subscription.delete(at_period_end: true) + update_columns(canceled_at: Time.now) + end + end + + def stripe_subscription + user.stripe_customer.subscriptions.retrieve(stp_subscription_id) if stp_subscription_id.present? + end + + def expire(time) + if !is_expired? + update_columns(expired_at: time, canceled_at: time) + notify_admin_subscription_canceled + notify_member_subscription_canceled + true + else + false + end + end + + def is_expired? + expired_at <= Time.now + end + + def extend_expired_date(expired_at, free_days = false) + return false if expired_at <= self.expired_at + + self.expired_at = expired_at + if save + reset_users_credits if !free_days + notify_subscription_extended(free_days) + return true + end + return false + end + + private + def update_payment + if stp_subscription_id.present? + stp_subscription = stripe_subscription + stp_subscription.plan = plan.stp_plan_id + stp_subscription.prorate = false + stp_subscription.save + else + # TODO: implement stripe payment if member update subscription + # must have a card + end + end + + def notify_member_subscribed_plan + NotificationCenter.call type: 'notify_member_subscribed_plan', + receiver: user, + attached_object: self + end + + def notify_admin_subscribed_plan + NotificationCenter.call type: 'notify_admin_subscribed_plan', + receiver: User.admins, + attached_object: self + end + + def notify_admin_subscription_canceled + NotificationCenter.call type: 'notify_admin_subscription_canceled', + receiver: User.admins, + attached_object: self + end + + def notify_member_subscription_canceled + NotificationCenter.call type: 'notify_member_subscription_canceled', + receiver: user, + attached_object: self + end + + def notify_partner_subscribed_plan + NotificationCenter.call type: 'notify_partner_subscribed_plan', + receiver: plan.partners, + attached_object: self + end + + def notify_subscription_extended(free_days) + meta_data = {} + meta_data[:free_days] = true if free_days == true + notification = Notification.new(meta_data: meta_data) + notification.send_notification(type: :notify_member_subscription_extended, attached_object: self).to(user).deliver_later + + User.admins.each do |admin| + notification = Notification.new(meta_data: meta_data) + notification.send_notification(type: :notify_admin_subscription_extended, attached_object: self).to(admin).deliver_later + end + end + + # set a expired date by plan + # expired_at will be updated when has a new payment + def set_expired_at + start_at = Time.now + self.expired_at = start_at + plan.duration + end + + def expired_date_changed + return true if expired_at_was.nil? + expired_at_was.to_date != expired_at.to_date and expired_at > expired_at_was + end + + def reset_users_credits + user.users_credits.destroy_all + end + + # def is_being_extended? + # !expired_at_was.nil? and expired_at_changed? + # end + + def is_new? + expired_at_was.nil? + end + + def of_partner_plan? + plan.is_a?(PartnerPlan) + end + +end diff --git a/app/models/tag.rb b/app/models/tag.rb new file mode 100644 index 000000000..b8fbd17a6 --- /dev/null +++ b/app/models/tag.rb @@ -0,0 +1,7 @@ +class Tag < ActiveRecord::Base + has_many :user_tags, dependent: :destroy + has_many :users, through: :user_tags + + has_many :availability_tags, dependent: :destroy + has_many :availabilities, through: :availability_tags +end diff --git a/app/models/training.rb b/app/models/training.rb new file mode 100644 index 000000000..b2fa1463f --- /dev/null +++ b/app/models/training.rb @@ -0,0 +1,59 @@ +class Training < ActiveRecord::Base + extend FriendlyId + friendly_id :name, use: :slugged + + has_and_belongs_to_many :machines, join_table: :trainings_machines + + has_many :trainings_availabilities + has_many :availabilities, through: :trainings_availabilities, dependent: :destroy + + has_many :reservations, as: :reservable, dependent: :destroy + + # members who DID the training + has_many :user_trainings, dependent: :destroy + has_many :users, through: :user_trainings + + has_many :trainings_pricings, dependent: :destroy + + has_many :credits, as: :creditable, dependent: :destroy + has_many :plans, through: :credits + + after_create :create_statistic_subtype + after_create :create_trainings_pricings + after_update :update_statistic_subtype, if: :name_changed? + after_destroy :remove_statistic_subtype + + validates :description, length: { maximum: 255 } + + def amount_by_group(group) + trainings_pricings.where(group_id: group).first + end + + def create_statistic_subtype + index = StatisticIndex.where(es_type_key: 'training') + StatisticSubType.create!({statistic_types: index.first.statistic_types, key: self.slug, label: self.name}) + end + + def update_statistic_subtype + index = StatisticIndex.where(es_type_key: 'training') + subtype = StatisticSubType.joins(statistic_type_sub_types: :statistic_type).where(key: self.slug, statistic_types: { statistic_index_id: index.first.id }).first + subtype.label = self.name + subtype.save! + end + + def remove_statistic_subtype + subtype = StatisticSubType.where(key: self.slug).first + subtype.destroy! + end + + def destroyable? + reservations.empty? + end + + private + def create_trainings_pricings + Group.all.each do |group| + TrainingsPricing.create(training: self, group: group, amount: 0) + end + end +end diff --git a/app/models/trainings_availability.rb b/app/models/trainings_availability.rb new file mode 100644 index 000000000..d027d0577 --- /dev/null +++ b/app/models/trainings_availability.rb @@ -0,0 +1,15 @@ +class TrainingsAvailability < ActiveRecord::Base + belongs_to :training + belongs_to :availability + after_destroy :cleanup_availability + + # when the TrainingsAvailability is deleted (from Training destroy cascade), we delete the corresponding + # availability. We don't use 'dependent: destroy' as we need to prevent conflicts if the destroy came from + # the Availability destroy cascade. + def cleanup_availability + unless availability.destroying + availability.safe_destroy + end + end + +end diff --git a/app/models/trainings_pricing.rb b/app/models/trainings_pricing.rb new file mode 100644 index 000000000..0fc0af8e7 --- /dev/null +++ b/app/models/trainings_pricing.rb @@ -0,0 +1,8 @@ +class TrainingsPricing < ActiveRecord::Base + belongs_to :training + belongs_to :group + + def amount_by_plan(plan) + amount + end +end diff --git a/app/models/user.rb b/app/models/user.rb index fa427b859..752ef5a7a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,34 +2,70 @@ class User < ActiveRecord::Base include NotifyWith::NotificationReceiver include NotifyWith::NotificationAttachedObject # Include default devise modules. Others available are: - # :lockable, :timeoutable and :omniauthable + # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable, :async rolify + # enable OmniAuth authentication only if needed + devise :omniauthable, :omniauth_providers => [AuthProvider.active.strategy_name.to_sym] unless AuthProvider.active.providable_type == DatabaseProvider.name + extend FriendlyId friendly_id :username, use: :slugged has_one :profile, dependent: :destroy accepts_nested_attributes_for :profile - has_many :my_projects, foreign_key: :author_id, class_name: 'Project' + has_many :my_projects, foreign_key: :author_id, class_name: 'Project', dependent: :destroy has_many :project_users, dependent: :destroy has_many :projects, through: :project_users + has_many :reservations, dependent: :destroy + accepts_nested_attributes_for :reservations, allow_destroy: true + + # Les formations sont déjà faites + has_many :user_trainings, dependent: :destroy + has_many :trainings, through: :user_trainings + belongs_to :group + has_many :subscriptions, dependent: :destroy + accepts_nested_attributes_for :subscriptions, allow_destroy: true + + has_many :users_credits, dependent: :destroy + has_many :credits, through: :users_credits + + has_many :training_credits, through: :users_credits, source: :training_credit + has_many :machine_credits, through: :users_credits, source: :machine_credit + + has_many :invoices, dependent: :destroy + + has_many :user_tags, dependent: :destroy + has_many :tags, through: :user_tags + accepts_nested_attributes_for :tags, allow_destroy: true + + # fix for create admin user + before_save do + self.email.downcase! if self.email + end + before_create :assign_default_role - after_create :notify_admin_when_user_is_created + after_commit :create_stripe_customer, on: [:create] + after_commit :notify_admin_when_user_is_created, on: :create + after_update :notify_admin_invoicing_changed, if: :invoicing_disabled_changed? + after_update :notify_group_changed, if: :group_id_changed? attr_accessor :cgu + delegate :first_name, to: :profile + delegate :last_name, to: :profile validate :cgu_must_accept, if: :new_record? - validates_presence_of :group_id validates :username, presence: true, uniqueness: true, length: { maximum: 30 } + scope :active, -> { where(is_active: true) } + def to_builder Jbuilder.new do |json| json.id id @@ -38,6 +74,7 @@ class User < ActiveRecord::Base json.role roles.first.name json.group_id group_id json.name profile.full_name + json.need_completion need_completion? json.profile do json.user_avatar do json.id profile.user_avatar.id @@ -52,6 +89,43 @@ class User < ActiveRecord::Base json.address profile.address.address if profile.address json.phone profile.phone end + json.subscribed_plan do + json.id subscribed_plan.id + json.name subscribed_plan.name + json.base_name subscribed_plan.base_name + json.amount (subscribed_plan.amount / 100.0) + json.interval subscribed_plan.interval + json.interval_count subscribed_plan.interval_count + json.training_credit_nb subscribed_plan.training_credit_nb + json.training_credits subscribed_plan.training_credits do |tc| + json.training_id tc.creditable_id + end + json.machine_credits subscribed_plan.machine_credits do |mc| + json.machine_id mc.creditable_id + json.hours mc.hours + end + end if subscribed_plan + json.subscription do + json.id subscription.id + json.expired_at subscription.expired_at.iso8601 + json.canceled_at subscription.canceled_at.iso8601 if subscription.canceled_at + json.stripe subscription.stp_subscription_id.present? + json.plan do + json.id subscription.plan.id + json.base_name subscription.plan.base_name + json.name subscription.plan.name + json.interval subscription.plan.interval + json.interval_count subscription.plan.interval_count + json.amount subscription.plan.amount ? (subscription.plan.amount / 100.0) : 0 + end + end if subscription + json.training_credits training_credits do |tc| + json.training_id tc.creditable_id + end + json.machine_credits machine_credits do |mc| + json.machine_id mc.creditable_id + json.hours_used mc.users_credits.find_by_user_id(id).hours_used + end json.last_sign_in_at last_sign_in_at.iso8601 if last_sign_in_at end end @@ -64,6 +138,26 @@ class User < ActiveRecord::Base User.with_role(:admin) end + def is_training_machine?(machine) + return true if is_admin? + trainings.map do |t| + t.machines + end.flatten.uniq.include?(machine) + end + + def training_reservation_by_machine(machine) + reservations.where(reservable_type: 'Training', reservable_id: machine.trainings.map(&:id)).first + end + + def subscribed_plan + return nil if subscription.nil? or subscription.expired_at < Time.now + subscription.plan + end + + def subscription + subscriptions.last + end + def is_admin? has_role? :admin end @@ -76,23 +170,200 @@ class User < ActiveRecord::Base my_projects.to_a.concat projects end - private - def assign_default_role - add_role(:member) if self.roles.blank? + def generate_admin_invoice(offer_day = false, offer_day_start_at = nil) + if self.subscription + if offer_day + self.subscription.generate_and_save_offer_day_invoice(offer_day_start_at) unless self.invoicing_disabled? + else + self.subscription.generate_and_save_invoice unless self.invoicing_disabled? + end + end end - def cgu_must_accept - errors.add(:cgu, I18n.t('activerecord.errors.messages.empty')) if cgu == '0' + def stripe_customer + Stripe::Customer.retrieve stp_customer_id end - def notify_admin_when_user_is_created - NotificationCenter.call type: 'notify_admin_when_user_is_created', - receiver: User.admins, - attached_object: self + def soft_destroy + update_attribute(:is_active, false) + uninvolve_from_projects + end + + def uninvolve_from_projects + my_projects.destroy_all + project_users.destroy_all + end + + def active_for_authentication? + super and self.is_active? + end + + def self.from_omniauth(auth) + active_provider = AuthProvider.active + if active_provider.strategy_name != auth.provider + raise SecurityError, 'The identity provider does not match the activated one' + end + + where(provider: auth.provider, uid: auth.uid).first_or_create.tap do |user| + # execute this regardless of whether record exists or not (-> User#tap) + # this will init or update the user thanks to the informations retrieved from the SSO + user.profile ||= Profile.new + auth.info.mapping.each do |key, value| + user.set_data_from_sso_mapping(key, value) + end + user.password = Devise.friendly_token[0,20] + end + end + + def need_completion? + profile.gender.nil? or profile.first_name.blank? or profile.last_name.blank? or username.blank? or + email.blank? or encrypted_password.blank? or group_id.nil? or profile.birthday.blank? or profile.phone.blank? + end + + ## Retrieve the requested data in the User and user's Profile tables + ## @param sso_mapping {String} must be of form 'user._field_' or 'profile._field_'. Eg. 'user.email' + def get_data_from_sso_mapping(sso_mapping) + parsed = /^(user|profile)\.(.+)$/.match(sso_mapping) + if parsed[1] == 'user' + self[parsed[2].to_sym] + elsif parsed[1] == 'profile' + self.profile[parsed[2].to_sym] + end + end + + + ## Set some data on the current user, according to the sso_key given + ## @param sso_mapping {String} must be of form 'user._field_' or 'profile._field_'. Eg. 'user.email' + ## @param data {*} the data to put in the given key. Eg. 'user@example.com' + def set_data_from_sso_mapping(sso_mapping, data) + if sso_mapping.to_s.start_with? 'user.' + self[sso_mapping[5..-1].to_sym] = data unless data.nil? + elsif sso_mapping.to_s.start_with? 'profile.' + if sso_mapping.to_s == 'profile.avatar' + self.profile.user_avatar ||= UserAvatar.new + self.profile.user_avatar.remote_attachment_url = data + else + self.profile[sso_mapping[8..-1].to_sym] = data unless data.nil? + end + end + end + + ## used to allow the migration of existing users between authentication providers + def generate_auth_migration_token + update_attributes(:auth_token => Devise.friendly_token) + end + + ## link the current user to the given provider (omniauth attributes hash) + ## and remove the auth_token to mark his account as "migrated" + def link_with_omniauth_provider(auth) + active_provider = AuthProvider.active + if active_provider.strategy_name != auth.provider + raise SecurityError, 'The identity provider does not match the activated one' + end + + if User.where(provider: auth.provider, uid: auth.uid).size > 0 + raise DuplicateIndexError, "This #{active_provider.name} account is already linked to an existing user" + end + + update_attributes(provider: auth.provider, uid: auth.uid, auth_token: nil) + end + + ## Merge the provided User's SSO details into the current user and drop the provided user to ensure the unity + ## @param sso_user {User} the provided user will be DELETED after the merge was successful + def merge_from_sso(sso_user) + # update the attibutes to link the account to the sso account + self.provider = sso_user.provider + self.uid = sso_user.uid + + # remove the token + self.auth_token = nil + self.merged_at = DateTime.now + + # check that the email duplication was resolved + if sso_user.email.end_with? '-duplicate' + email_addr = sso_user.email.match(/^<([^>]+)>.{20}-duplicate$/)[1] + unless email_addr == self.email + raise DuplicateIndexError, email_addr + end + end + + # update the user's profile to set the data managed by the SSO + auth_provider = AuthProvider.from_strategy_name(sso_user.provider) + auth_provider.sso_fields.each do |field| + value = sso_user.get_data_from_sso_mapping(field) + # we do not merge the email field if its end with the special value '-duplicate' as this means + # that the user is currently merging with the account that have the same email than the sso + unless field == 'user.email' and value.end_with? '-duplicate' + self.set_data_from_sso_mapping(field, value) + end + end + + # run the account transfert in an SQL transaction to ensure data integrity + User.transaction do + # remove the temporary account + sso_user.destroy + # finally, save the new details + self.save! + end end protected def confirmation_required? false end + + + private + def assign_default_role + add_role(:member) if self.roles.blank? + end + + def cached_has_role?(role) + roles = Rails.cache.fetch(roles_for: { object_id: self.object_id }, expires_in: 1.day, race_condition_ttl: 2.seconds) { self.roles.map(&:name) } + roles.include?(role.to_s) + end + + def cgu_must_accept + errors.add(:cgu, I18n.t('activerecord.errors.messages.empty')) if cgu == '0' + end + + def create_stripe_customer + StripeWorker.perform_async(:create_stripe_customer, id) + end + + def notify_admin_when_user_is_created + if need_completion? and not provider.nil? + NotificationCenter.call type: 'notify_admin_when_user_is_imported', + receiver: User.admins, + attached_object: self + else + NotificationCenter.call type: 'notify_admin_when_user_is_created', + receiver: User.admins, + attached_object: self + end + end + + def notify_group_changed + if changes[:group_id].first != nil + ex_group = Group.find(changes[:group_id].first) + meta_data = { ex_group_name: ex_group.name } + + User.admins.each do |admin| + notification = Notification.new(meta_data: meta_data) + notification.send_notification(type: :notify_admin_user_group_changed, attached_object: self).to(admin).deliver_later + end + + NotificationCenter.call type: :notify_user_user_group_changed, + receiver: self, + attached_object: self + end + end + + def notify_admin_invoicing_changed + NotificationCenter.call type: 'notify_admin_invoicing_changed', + receiver: User.admins, + attached_object: self + end + + end diff --git a/app/models/user_tag.rb b/app/models/user_tag.rb new file mode 100644 index 000000000..eebb02295 --- /dev/null +++ b/app/models/user_tag.rb @@ -0,0 +1,4 @@ +class UserTag < ActiveRecord::Base + belongs_to :user + belongs_to :tag +end diff --git a/app/models/user_training.rb b/app/models/user_training.rb new file mode 100644 index 000000000..cbf27131e --- /dev/null +++ b/app/models/user_training.rb @@ -0,0 +1,15 @@ +class UserTraining < ActiveRecord::Base + include NotifyWith::NotificationAttachedObject + + belongs_to :user + belongs_to :training + + after_commit :notify_user_training_valid, on: :create + + private + def notify_user_training_valid + NotificationCenter.call type: 'notify_user_training_valid', + receiver: user, + attached_object: self + end +end diff --git a/app/models/users_credit.rb b/app/models/users_credit.rb new file mode 100644 index 000000000..a0a41381e --- /dev/null +++ b/app/models/users_credit.rb @@ -0,0 +1,7 @@ +class UsersCredit < ActiveRecord::Base + belongs_to :user + belongs_to :credit + + belongs_to :training_credit, ->{ where('credits.creditable_type = ?', 'Training') }, foreign_key: 'credit_id', class_name: 'Credit' + belongs_to :machine_credit, ->{ where('credits.creditable_type = ?', 'Machine') }, foreign_key: 'credit_id', class_name: 'Credit' +end diff --git a/app/pdfs/pdf/invoice.rb b/app/pdfs/pdf/invoice.rb new file mode 100644 index 000000000..9651cca61 --- /dev/null +++ b/app/pdfs/pdf/invoice.rb @@ -0,0 +1,267 @@ +module PDF + + class Invoice < Prawn::Document + require 'stringio' + include ActionView::Helpers::NumberHelper + + def initialize(invoice) + super(:margin => 70) + + # fonts + opensans = Rails.root.join('vendor/assets/fonts/OpenSans-Regular.ttf').to_s + opensans_bold = Rails.root.join('vendor/assets/fonts/OpenSans-Bold.ttf').to_s + opensans_bolditalic = Rails.root.join('vendor/assets/fonts/OpenSans-BoldItalic.ttf').to_s + opensans_italic = Rails.root.join('vendor/assets/fonts/OpenSans-Italic.ttf').to_s + + font_families.update( + 'Open-Sans' => { + :normal => {:file => opensans, :font => 'Open-Sans'}, + :bold => {:file => opensans_bold, :font => 'Open-Sans-Bold'}, + :italic => {:file => opensans_italic, :font => 'Open-Sans-Oblique'}, + :bold_italic => {:file => opensans_bolditalic, :font => 'Open-Sans-BoldOblique'} + } + ) + + # logo + img_b64 = Setting.find_by({name: 'invoice_logo'}) + image StringIO.new( Base64.decode64(img_b64.value) ), :fit => [415,40] + move_down 20 + font('Open-Sans', :size => 10) do + # general informations + if invoice.is_a?(Avoir) + text I18n.t('invoices.refund_invoice_reference', REF:invoice.reference), :leading => 3 + else + text I18n.t('invoices.invoice_reference', REF:invoice.reference), :leading => 3 + end + if Setting.find_by({name: 'invoice_code-active'}).value == 'true' + text I18n.t('invoices.code', CODE:Setting.find_by({name: 'invoice_code-value'}).value), :leading => 3 + end + if invoice.is_a?(Avoir) + text I18n.t('invoices.order_number', NUMBER:invoice.invoice.order_number), :leading => 3 + else + text I18n.t('invoices.order_number', NUMBER:invoice.order_number), :leading => 3 + end + if invoice.is_a?(Avoir) + text I18n.t('invoices.refund_invoice_issued_on_DATE', DATE:I18n.l(invoice.avoir_date.to_date)) + else + text I18n.t('invoices.invoice_issued_on_DATE', DATE:I18n.l(invoice.created_at.to_date)) + end + + # user's informations + if invoice.user.profile.address + address = invoice.user.profile.address.address + else + address = '' + end + text_box "#{invoice.user.profile.full_name}\n#{invoice.user.email}\n#{address}", :at => [bounds.width - 130, bounds.top - 49], :width => 130, :align => :right, :inline_format => true + + # object + move_down 25 + if invoice.is_a?(Avoir) + object = I18n.t('invoices.cancellation_of_invoice_REF', REF: invoice.invoice.reference) + else + case invoice.invoiced_type + when 'Reservation' + object = I18n.t('invoices.reservation_of_USER_on_DATE_at_TIME', USER:invoice.user.profile.full_name, DATE:I18n.l(invoice.invoiced.slots[0].start_at.to_date), TIME:I18n.l(invoice.invoiced.slots[0].start_at, format: :hour_minute)) + invoice.invoice_items.each do |item| + if item.subscription_id + subscription = Subscription.find item.subscription_id + object = "\n- #{object}\n- #{(invoice.is_a?(Avoir) ? I18n.t('invoices.cancellation')+' - ' : '') + subscription_verbose(subscription, invoice.user)}" + break + end + end + when 'Subscription' + object = subscription_verbose(invoice.invoiced, invoice.user) + when 'OfferDay' + object = offer_day_verbose(invoice.invoiced, invoice.user) + end + end + text I18n.t('invoices.object')+' '+object + + # details table of the invoice's elements + move_down 20 + text I18n.t('invoices.order_summary'), :leading => 4 + move_down 2 + data = [ [I18n.t('invoices.details'), I18n.t('invoices.amount')] ] + + total_calc = 0 + # going through invoice_items + invoice.invoice_items.each do |item| + + price = item.amount.to_i / 100.00 + + details = invoice.is_a?(Avoir) ? I18n.t('invoices.cancellation')+' - ' : '' + + if item.subscription_id ### Subscription + subscription = Subscription.find item.subscription_id + if invoice.invoiced_type == 'OfferDay' + details += I18n.t('invoices.subscription_extended_for_free_from_START_to_END', START:I18n.l(invoice.invoiced.start_at.to_date), END:I18n.l(invoice.invoiced.end_at.to_date)) + else + subscription_start_at = subscription.expired_at - subscription.plan.duration + details += I18n.t('invoices.subscription_NAME_from_START_to_END', NAME:item.description, START:I18n.l(subscription_start_at.to_date), END:I18n.l(subscription.expired_at.to_date)) + end + + + else ### Reservation + case invoice.invoiced.reservable_type + ### Machine reservation + when 'Machine' + details += I18n.t('invoices.machine_reservation_DESCRIPTION', DESCRIPTION: item.description) + ### Training reservation + when 'Training' + details += I18n.t('invoices.training_reservation_DESCRIPTION', DESCRIPTION: item.description) + ### courses and workshops reservation + when 'Event' + details += I18n.t('invoices.courses_and_workshops_reservation_DESCRIPTION', DESCRIPTION: item.description) + # details of the number of tickets + details += "\n "+I18n.t('invoices.full_price_ticket', count: invoice.invoiced.nb_reserve_places) if invoice.invoiced.nb_reserve_places > 0 + details += "\n "+I18n.t('invoices.reduced_rate_ticket', count: invoice.invoiced.nb_reserve_reduced_places) if invoice.invoiced.nb_reserve_reduced_places > 0 + + ### Other cases (not expected) + else + details += I18n.t('invoices.reservation_other') + end + end + + data += [ [details, number_to_currency(price)] ] + total_calc += price + end + + # total verification + total = invoice.total / 100.0 + if total_calc != total + puts "ERROR: totals are NOT equals => expected: #{total}, computed: #{total_calc}" + end + + # TVA + if Setting.find_by({name: 'invoice_VAT-active'}).value == 'true' + data += [ [I18n.t('invoices.total_including_all_taxes'), number_to_currency(total)] ] + + vat_rate = Setting.find_by({name: 'invoice_VAT-rate'}).value.to_f + vat = total * vat_rate / 100 + data += [ [I18n.t('invoices.including_VAT_RATE', RATE: vat_rate), number_to_currency(vat)] ] + data += [ [I18n.t('invoices.including_total_excluding_taxes'), number_to_currency(total-vat)] ] + data += [ [I18n.t('invoices.including_amount_payed_on_ordering'), number_to_currency(total)] ] + + # checking the round number + rounded = sprintf('%.2f', vat).to_i + sprintf('%.2f', total-vat).to_i + if rounded != sprintf('%.2f', total_calc).to_i + puts "ERROR: rounding the numbers cause an invoice inconsistency. Total expected: #{sprintf('%.2f', total_calc)}, total computed: #{rounded}" + end + else + data += [ [I18n.t('invoices.total_amount'), number_to_currency(total)] ] + end + + # display table + table(data, :header => true, :column_widths => [400, 72], :cell_style => {:inline_format => true}) do + row(0).font_style = :bold + column(1).style :align => :right + + if Setting.find_by({name: 'invoice_VAT-active'}).value == 'true' + # Total incl. taxes + row(-1).style :align => :right + row(-1).background_color = 'E4E4E4' + row(-1).font_style = :bold + # including VAT xx% + row(-2).style :align => :right + row(-2).background_color = 'E4E4E4' + row(-2).font_style = :italic + # including total excl. taxes + row(-3).style :align => :right + row(-3).background_color = 'E4E4E4' + row(-3).font_style = :italic + # including amount payed on ordering + row(-4).style :align => :right + row(-4).background_color = 'E4E4E4' + row(-4).font_style = :bold + end + end + + # optional description for refunds + move_down 20 + if invoice.is_a?(Avoir) and invoice.description + text invoice.description + end + + # payment details + move_down 20 + if invoice.is_a?(Avoir) + payment_verbose = I18n.t('invoices.refund_on_DATE', DATE:I18n.l(invoice.avoir_date.to_date))+' ' + case invoice.avoir_mode + when 'stripe' + payment_verbose += I18n.t('invoices.by_stripe_online_payment') + when 'cheque' + payment_verbose += I18n.t('invoices.by_cheque') + when 'transfer' + payment_verbose += I18n.t('invoices.by_transfer') + when 'cash' + payment_verbose += I18n.t('invoices.by_cash') + when 'none' + payment_verbose = I18n.t('invoices.no_refund') + else + puts "ERROR : specified refunding method (#{payment_verbose}) is unknown" + end + else + if invoice.stp_invoice_id + payment_verbose = I18n.t('invoices.settlement_by_debit_card') + else + payment_verbose = I18n.t('invoices.settlement_done_at_the_reception') + end + end + unless invoice.is_a?(Avoir) + payment_verbose += ' '+I18n.t('invoices.on_DATE_at_TIME', DATE: I18n.l(invoice.created_at.to_date), TIME:I18n.l(invoice.created_at, format: :hour_minute)) + end + unless invoice.is_a?(Avoir) and invoice.avoir_mode == 'none' + payment_verbose += ' '+I18n.t('invoices.for_an_amount_of_AMOUNT', AMOUNT: number_to_currency(total)) if invoice.avoir_mode != 'none' + end + text payment_verbose + + # important informations + move_down 40 + txt = parse_html(Setting.find_by({name: 'invoice_text'}).value) + txt.each_line do |line| + text line, :style => :bold, :inline_format => true + end + + + # address and legals informations + move_down 40 + txt = parse_html(Setting.find_by({name: 'invoice_legals'}).value) + txt.each_line do |line| + text line, :align => :right, :leading => 4, :inline_format => true + end + end + end + + private + def reservation_dates_verbose(slot) + if slot.start_at.to_date == slot.end_at.to_date + '- '+I18n.t('invoices.on_DATE_from_START_to_END', DATE: I18n.l(slot.start_at.to_date), START: I18n.l(slot.start_at, format: :hour_minute), END: I18n.l(slot.end_at, format: :hour_minute))+"\n" + else + '- '+I18n.t('invoices.from_STARTDATE_to_ENDDATE_from_STARTTIME_to_ENDTIME', STARTDATE: I18n.l(slot.start_at.to_date), ENDDATE: I18n.l(slot.start_at.to_date), STARTTIME: I18n.l(slot.start_at, format: :hour_minute), ENDTIME: I18n.l(slot.end_at, format: :hour_minute))+"\n" + end + end + + def subscription_verbose(subscription, user) + subscription_start_at = subscription.expired_at - subscription.plan.duration + duration_verbose = I18n.t("duration.#{subscription.plan.interval}", count: subscription.plan.interval_count) + I18n.t('invoices.subscription_of_NAME_for_DURATION_starting_from_DATE', NAME: user.profile.full_name, DURATION: duration_verbose, DATE: I18n.l(subscription_start_at.to_date)) + end + + def offer_day_verbose(offer_day, user) + I18n.t('invoices.subscription_of_NAME_extended_starting_from_STARTDATE_until_ENDDATE', NAME: user.profile.full_name, STARTDATE: I18n.l(offer_day.start_at.to_date), ENDDATE: I18n.l(offer_day.end_at.to_date)) + end + + + + ## + # Remove every unsupported html tag from the given html text (like

    , , ...). + # The supported tags are , , and
    . + # @param html [String] single line html text + # @return [String] multi line simplified html text + ## + def parse_html(html) + ActionController::Base.helpers.sanitize(html, tags: %w(b u i br)) + end + end +end diff --git a/app/policies/admin_policy.rb b/app/policies/admin_policy.rb new file mode 100644 index 000000000..b7922fc92 --- /dev/null +++ b/app/policies/admin_policy.rb @@ -0,0 +1,9 @@ +class AdminPolicy < ApplicationPolicy + def index? + user.is_admin? + end + + def create? + user.is_admin? + end +end diff --git a/app/policies/auth_provider_policy.rb b/app/policies/auth_provider_policy.rb new file mode 100644 index 000000000..a12119570 --- /dev/null +++ b/app/policies/auth_provider_policy.rb @@ -0,0 +1,19 @@ +class AuthProviderPolicy < ApplicationPolicy + + class Scope < Scope + def resolve + scope.includes(:providable) + end + end + + %w(index? show? create? update? destroy? mapping_fields?).each do |action| + define_method action do + user.is_admin? + end + end + + def active? + user + end + +end diff --git a/app/policies/avoir_policy.rb b/app/policies/avoir_policy.rb new file mode 100644 index 000000000..bf67609cb --- /dev/null +++ b/app/policies/avoir_policy.rb @@ -0,0 +1,2 @@ +class AvoirPolicy < InvoicePolicy +end diff --git a/app/policies/credit_policy.rb b/app/policies/credit_policy.rb new file mode 100644 index 000000000..8774155cc --- /dev/null +++ b/app/policies/credit_policy.rb @@ -0,0 +1,17 @@ +class CreditPolicy < ApplicationPolicy + def index? + user.is_admin? + end + + def create? + index? + end + + def update? + index? + end + + def destroy? + index? + end +end diff --git a/app/policies/custom_asset_policy.rb b/app/policies/custom_asset_policy.rb new file mode 100644 index 000000000..03b9f3ab2 --- /dev/null +++ b/app/policies/custom_asset_policy.rb @@ -0,0 +1,11 @@ +class CustomAssetPolicy < ApplicationPolicy + + def create? + user.is_admin? + end + + def update? + user.is_admin? + end + +end diff --git a/app/policies/export_policy.rb b/app/policies/export_policy.rb index 4eddd68bd..18cd7c81d 100644 --- a/app/policies/export_policy.rb +++ b/app/policies/export_policy.rb @@ -1,5 +1,5 @@ class ExportPolicy < Struct.new(:user, :export) - %w(export_members).each do |action| + %w(export_reservations export_members export_subscriptions).each do |action| define_method "#{action}?" do user.is_admin? end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb new file mode 100644 index 000000000..4962727a2 --- /dev/null +++ b/app/policies/group_policy.rb @@ -0,0 +1,13 @@ +class GroupPolicy < ApplicationPolicy + def create? + user.is_admin? + end + + def update? + user.is_admin? + end + + def destroy? + user.is_admin? and record.destroyable? + end +end diff --git a/app/policies/invoice_policy.rb b/app/policies/invoice_policy.rb new file mode 100644 index 000000000..e27c314e5 --- /dev/null +++ b/app/policies/invoice_policy.rb @@ -0,0 +1,13 @@ +class InvoicePolicy < ApplicationPolicy + def index? + user.is_admin? + end + + def download? + user.is_admin? or (record.user_id == user.id) + end + + def create? + user.is_admin? + end +end diff --git a/app/policies/machine_policy.rb b/app/policies/machine_policy.rb index 43d02b42e..8b4637e8b 100644 --- a/app/policies/machine_policy.rb +++ b/app/policies/machine_policy.rb @@ -8,6 +8,6 @@ class MachinePolicy < ApplicationPolicy end def destroy? - user.is_admin? + user.is_admin? and record.destroyable? end end diff --git a/app/policies/partner_plan_policy.rb b/app/policies/partner_plan_policy.rb new file mode 100644 index 000000000..84b5670c7 --- /dev/null +++ b/app/policies/partner_plan_policy.rb @@ -0,0 +1,17 @@ +class PartnerPlanPolicy < ApplicationPolicy + def index? + user.is_admin? + end + + def create? + user.is_admin? + end + + def update? + user.is_admin? + end + + def destroy? + user.is_admin? and record.destroyable? + end +end diff --git a/app/policies/plan_policy.rb b/app/policies/plan_policy.rb new file mode 100644 index 000000000..bc83e3e20 --- /dev/null +++ b/app/policies/plan_policy.rb @@ -0,0 +1,13 @@ +class PlanPolicy < ApplicationPolicy + def create? + user.is_admin? + end + + def update? + user.is_admin? + end + + def destroy? + user.is_admin? and record.destroyable? + end +end diff --git a/app/policies/price_policy.rb b/app/policies/price_policy.rb new file mode 100644 index 000000000..d923d874c --- /dev/null +++ b/app/policies/price_policy.rb @@ -0,0 +1,9 @@ +class PricePolicy < ApplicationPolicy + def index? + user.is_admin? + end + + def update? + user.is_admin? + end +end diff --git a/app/policies/pricing_policy.rb b/app/policies/pricing_policy.rb new file mode 100644 index 000000000..8572afeb7 --- /dev/null +++ b/app/policies/pricing_policy.rb @@ -0,0 +1,5 @@ +class PricingPolicy < ApplicationPolicy + def update? + user.is_admin? + end +end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 17a3e3329..06fd93894 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -19,6 +19,6 @@ class ProjectPolicy < ApplicationPolicy end def destroy? - user.is_admin? + user.is_admin? or record.author == user end end diff --git a/app/policies/reservation_policy.rb b/app/policies/reservation_policy.rb new file mode 100644 index 000000000..49c90ec7e --- /dev/null +++ b/app/policies/reservation_policy.rb @@ -0,0 +1,5 @@ +class ReservationPolicy < ApplicationPolicy + def update? + user.is_admin? or record.user == user + end +end diff --git a/app/policies/setting_policy.rb b/app/policies/setting_policy.rb new file mode 100644 index 000000000..2b929d4e0 --- /dev/null +++ b/app/policies/setting_policy.rb @@ -0,0 +1,7 @@ +class SettingPolicy < ApplicationPolicy + %w(update).each do |action| + define_method "#{action}?" do + user.is_admin? + end + end +end diff --git a/app/policies/slot_policy.rb b/app/policies/slot_policy.rb new file mode 100644 index 000000000..e0a70a89c --- /dev/null +++ b/app/policies/slot_policy.rb @@ -0,0 +1,15 @@ +class SlotPolicy < ApplicationPolicy + def update? + # check that the update is allowed and the prevention delay has not expired + delay = Setting.find_by( name: 'booking_move_delay').value.to_i + enabled = (Setting.find_by( name: 'booking_move_enable').value == 'true') + + # these condition does not apply to admins + user.is_admin? or + (record.reservation.user == user and enabled and ((record.start_at - Time.now).to_i / 3600 >= delay)) + end + + def cancel? + user.is_admin? or record.reservation.user == user + end +end diff --git a/app/policies/statistic_policy.rb b/app/policies/statistic_policy.rb new file mode 100644 index 000000000..384e3781b --- /dev/null +++ b/app/policies/statistic_policy.rb @@ -0,0 +1,7 @@ +class StatisticPolicy < ApplicationPolicy + ['index', 'account', 'event', 'machine', 'project', 'subscription', 'training', 'user'].each do |action| + define_method "#{action}?" do + user.is_admin? + end + end +end diff --git a/app/policies/subscription_policy.rb b/app/policies/subscription_policy.rb new file mode 100644 index 000000000..b0ea91a12 --- /dev/null +++ b/app/policies/subscription_policy.rb @@ -0,0 +1,9 @@ +class SubscriptionPolicy < ApplicationPolicy + def show? + user.is_admin? or record.user_id == user.id + end + + def update? + user.is_admin? + end +end diff --git a/app/policies/tag_policy.rb b/app/policies/tag_policy.rb new file mode 100644 index 000000000..7b1e24205 --- /dev/null +++ b/app/policies/tag_policy.rb @@ -0,0 +1,13 @@ +class TagPolicy < ApplicationPolicy + def create? + user.is_admin? + end + + def update? + create? + end + + def destroy? + create? + end +end diff --git a/app/policies/training_policy.rb b/app/policies/training_policy.rb new file mode 100644 index 000000000..cef6cc9c6 --- /dev/null +++ b/app/policies/training_policy.rb @@ -0,0 +1,19 @@ +class TrainingPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.includes(:plans, :machines, :availabilities => [:slots => [:reservation => [:user => [:profile, :trainings]]]]).order('availabilities.start_at DESC') + end + end + + def create? + user.is_admin? + end + + def update? + user.is_admin? + end + + def destroy? + user.is_admin? and record.destroyable? + end +end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 8e89f6e7d..ea482971d 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -2,15 +2,15 @@ class UserPolicy < ApplicationPolicy class Scope < Scope def resolve if user.is_admin? - scope.with_role(:member).includes(:group, :profile => [:user_avatar]).order('created_at desc') + scope.includes(:group, :training_credits, :machine_credits, :subscriptions => [:plan => [:credits]], :profile => [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").order('users.created_at desc') else - scope.with_role(:member).includes(:group, :profile => [:user_avatar]).where(is_allow_contact: true).order('created_at desc') + scope.includes(:group, :training_credits, :machine_credits, :profile => [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").where(is_allow_contact: true).order('users.created_at desc') end end end def show? - user.is_admin? or (record.is_allow_contact and record.has_role?(:member)) or (user.id == record.id) + user.is_admin? or (record.is_allow_contact and record.is_member?) or (user.id == record.id) end def create? @@ -20,4 +20,12 @@ class UserPolicy < ApplicationPolicy def update? user.is_admin? or (user.id == record.id) end + + def destroy? + user.id == record.id + end + + def merge? + user.id == record.id + end end diff --git a/app/services/statistic_service.rb b/app/services/statistic_service.rb new file mode 100644 index 000000000..f6b4239a3 --- /dev/null +++ b/app/services/statistic_service.rb @@ -0,0 +1,440 @@ +class StatisticService + def initialize + # @projects_comment_info = DisqusApi.v3.threads.list(forum: Rails.application.secrets.disqus_shortname).response + end + + def generate_statistic(options = default_options) + # remove data exists + clean_stat(options) + + # subscription month/year list + subscriptions_list(options).each do |s| + Stats::Subscription.create({ + date: format_date(s.date), + type: s.duration, + subType: s.stp_plan_id, + stat: 1, + ca: s.ca, + planId: s.plan_id, + subscriptionId: s.subscription_id, + invoiceItemId: s.invoice_item_id, + groupName: s.plan_group_name + }.merge(user_info_stat(s))) + end + + # machine list + reservations_machine_list(options).each do |r| + %w(booking hour).each do |type| + stat = Stats::Machine.new({ + date: format_date(r.date), + type: type, + subType: r.machine_type, + ca: r.ca, + machineId: r.machine_id, + name: r.machine_name, + reservationId: r.reservation_id + }.merge(user_info_stat(r))) + stat.stat = (type == 'booking' ? 1 : r.nb_hours) + stat.save + end + end + + # training list + reservations_training_list(options).each do |r| + %w(booking hour).each do |type| + stat = Stats::Training.new({ + date: format_date(r.date), + type: type, + subType: r.training_type, + ca: r.ca, + trainingId: r.training_id, + name: r.training_name, + trainingDate: r.training_date, + reservationId: r.reservation_id + }.merge(user_info_stat(r))) + stat.stat = (type == 'booking' ? 1 : r.nb_hours) + stat.save + end + end + + # event list + reservations_event_list(options).each do |r| + %w(booking hour).each do |type| + stat = Stats::Event.new({ + date: format_date(r.date), + type: type, + subType: r.event_type, + ca: r.ca, + eventId: r.event_id, + name: r.event_name, + eventDate: r.event_date, + reservationId: r.reservation_id + }.merge(user_info_stat(r))) + stat.stat = (type == 'booking' ? r.nb_places : r.nb_hours) + stat.save + end + end + + # account list + members_list(options).each do |m| + Stats::Account.create({ + date: format_date(m.date), + type: 'member', + subType: 'created', + stat: 1 + }.merge(user_info_stat(m))) + end + + # project list + projects_list(options).each do |p| + Stats::Project.create({ + date: format_date(p.date), + type: 'project', + subType: 'published', + stat: 1 + }.merge(user_info_stat(p)).merge(project_info_stat(p))) + end + + # member ca list + members_ca_list(options).each do |m| + Stats::User.create({ + date: format_date(m.date), + type: 'revenue', + subType: m.group, + stat: m.ca + }.merge(user_info_stat(m))) + end + + # project comment nb list + # Stats::Project.where(type: "project", subType: "comment").destroy_all + # projects_comment_nb_list.each do |p| + # Stats::Project.create({ + # type: "project", + # subType: "comment", + # stat: p.project_comments + # }.merge(user_info_stat(p)).merge(project_info_stat(p))) + # end + end + + def subscriptions_list(options = default_options) + result = [] + InvoiceItem.where('invoice_items.created_at >= :start_date AND invoice_items.created_at <= :end_date', options) + .eager_load(subscription: [:plan, user: [:profile, :group]]).each do |i| + unless i.invoice.is_a?(Avoir) + sub = i.subscription + if sub + u = sub.user + p = sub.plan + result.push OpenStruct.new({ + date: options[:start_date].to_date, + plan: p.group.slug, + plan_id: p.id, + plan_interval: p.interval, + plan_interval_count: p.interval_count, + plan_group_name: p.group.name, + stp_plan_id: p.stp_plan_id, + duration: p.duration.to_i, + subscription_id: sub.id, + invoice_item_id: i.id, + ca: i.amount.to_i / 100.0 + }.merge(user_info(u))) + end + end + end + result + end + + def reservations_machine_list(options = default_options) + result = [] + Reservation + .where("reservable_type = 'Machine' AND reservations.created_at >= :start_date AND reservations.created_at <= :end_date", options) + .eager_load(:slots, user: [:profile, :group], invoice: [:invoice_items]) + .each do |r| + u = r.user + result.push OpenStruct.new({ + date: options[:start_date].to_date, + reservation_id: r.id, + machine_id: r.reservable.id, + machine_type: r.reservable.friendly_id, + machine_name: r.reservable.name, + nb_hours: r.slots.size, + ca: calcul_ca(r.invoice) + }.merge(user_info(u))) if r.reservable + end + result + end + + def reservations_training_list(options = default_options) + result = [] + Reservation + .where("reservable_type = 'Training' AND reservations.created_at >= :start_date AND reservations.created_at <= :end_date", options) + .eager_load(:slots, user: [:profile, :group], invoice: [:invoice_items]) + .each do |r| + u = r.user + slot = r.slots.first + result.push OpenStruct.new({ + date: options[:start_date].to_date, + reservation_id: r.id, + training_id: r.reservable.id, + training_type: r.reservable.friendly_id, + training_name: r.reservable.name, + training_date: slot.start_at.to_date, + nb_hours: difference_in_hours(slot.start_at, slot.end_at), + ca: calcul_ca(r.invoice) + }.merge(user_info(u))) if r.reservable + end + result + end + + def reservations_event_list(options = default_options) + result = [] + Reservation + .where("reservable_type = 'Event' AND reservations.created_at >= :start_date AND reservations.created_at <= :end_date", options) + .eager_load(:slots, user: [:profile, :group], invoice: [:invoice_items]) + .each do |r| + u = r.user + slot = r.slots.first + result.push OpenStruct.new({ + date: options[:start_date].to_date, + reservation_id: r.id, + event_id: r.reservable.id, + event_type: r.reservable.categories.first.name, + event_name: r.reservable.name, + event_date: slot.start_at.to_date, + nb_places: r.nb_reserve_places + r.nb_reserve_reduced_places, + nb_hours: difference_in_hours(slot.start_at, slot.end_at), + ca: calcul_ca(r.invoice) + }.merge(user_info(u))) if r.reservable + end + result + end + + def members_ca_list(options = default_options) + subscriptions_ca_list = subscriptions_list(options) + reservations_ca_list = [] + avoirs_ca_list = [] + result = [] + Reservation + .where('reservations.created_at >= :start_date AND reservations.created_at <= :end_date', options) + .eager_load(:slots, user: [:profile, :group], invoice: [:invoice_items]) + .each do |r| + if r.reservable + reservations_ca_list.push OpenStruct.new({ + date: options[:start_date].to_date, + ca: calcul_ca(r.invoice) + }.merge(user_info(r.user))) + end + end + Avoir + .where('invoices.created_at >= :start_date AND invoices.created_at <= :end_date', options) + .eager_load(:invoice_items, user: [:profile, :group]) + .each do |i| + avoirs_ca_list.push OpenStruct.new({ + date: options[:start_date].to_date, + ca: calcul_avoir_ca(i) + }.merge(user_info(i.user))) + end + reservations_ca_list.concat(subscriptions_ca_list).concat(avoirs_ca_list).each do |e| + user = User.find(e.user_id) + u = find_or_create_user_info_info_list(user, result) + u.date = options[:start_date].to_date + e.ca = 0 unless e.ca + if u.ca + u.ca = u.ca + e.ca + else + u.ca = 0 + u.ca = u.ca + e.ca + result.push u + end + end + result + end + + def members_list(options = default_options) + result = [] + User.with_role(:member).where('users.created_at >= :start_date AND users.created_at <= :end_date', options).each do |u| + result.push OpenStruct.new({ + date: options[:start_date].to_date + }.merge(user_info(u))) + end + result + end + + def projects_list(options = default_options) + result = [] + Project.where('projects.published_at >= :start_date AND projects.published_at <= :end_date', options) + .eager_load(:licence, :themes, :components, :machines, :project_users, author: [:profile, :group]) + .each do |p| + result.push OpenStruct.new({ + date: options[:start_date].to_date + }.merge(user_info(p.author)).merge(project_info(p))) + end + result + end + + # return always yesterday's sum of comment of each project + def projects_comment_nb_list + result = [] + Project.where(state: 'published') + .eager_load(:licence, :themes, :components, :machines, :project_users, author: [:profile, :group]) + .each do |p| + result.push OpenStruct.new({ + date: 1.day.ago.to_date, + project_comments: get_project_comment_nb(p) + }.merge(user_info(p.author)).merge(project_info(p))) + end + result + end + + def clean_stat(options = default_options) + %w{Account Event Machine Project Subscription Training User}.each do |o| + "Stats::#{o}".constantize.search(query: {match: {date: format_date(options[:start_date])}}).results.each(&:destroy) + end + end + + private + def default_options + yesterday = 1.day.ago + { + start_date: yesterday.beginning_of_day, + end_date: yesterday.end_of_day + } + end + + def format_date(date) + if date.is_a?(String) + Date.strptime(date, '%Y%m%d').strftime('%Y-%m-%d') + else + date.strftime('%Y-%m-%d') + end + end + + def user_info(user) + { + user_id: user.id, + gender: user.profile.str_gender, + age: user.profile.age, + group: user.group.slug, + email: user.email + } + end + + def user_info_stat(s) + { + userId: s.user_id, + gender: s.gender, + age: s.age, + group: s.group + } + end + + def calcul_ca(invoice) + return nil unless invoice + ca = 0 + invoice.invoice_items.each do |ii| + unless ii.subscription_id + if invoice.is_a?(Avoir) + ca = ca - ii.amount.to_i + else + ca = ca + ii.amount.to_i + end + end + end + ca == 0 ? ca : ca / 100.0 + end + + def calcul_avoir_ca(invoice) + ca = 0 + invoice.invoice_items.each do |ii| + ca = ca - ii.amount.to_i + end + ca == 0 ? ca : ca / 100.0 + end + + def difference_in_hours(start_at, end_at) + if start_at.to_date == end_at.to_date + ((end_at - start_at) / 60 / 60).to_i + else + end_at_to_start_date = end_at.change(year: start_at.year, month: start_at.month, day: start_at.day) + hours = ((end_at_to_start_date - start_at) / 60 / 60).to_i + if end_at.to_date > start_at.to_date + hours = ((end_at.to_date - start_at.to_date).to_i + 1) * hours + end + hours + end + end + + def get_project_themes(project) + project.themes.map do |t| + {id: t.id, name: t.name} + end + end + + def get_projects_components(project) + project.components.map do |c| + {id: c.id, name: c.name} + end + end + + def get_projects_machines(project) + project.machines.map do |m| + {id: m.id, name: m.name} + end + end + + def get_project_users(project) + sum = 0 + project.project_users.each do |pu| + sum = sum + 1 if pu.is_valid + end + sum + end + + def get_project_comment_nb(project) + project_comment_info = @projects_comment_info.select do |p| + p['identifiers'].first == "project_#{project.id}" + end.first + project_comment_info ? project_comment_info['posts'] : 0 + end + + def project_info(project) + { + project_id: project.id, + project_name: project.name, + project_created_at: project.created_at, + project_published_at: project.published_at, + #project_licence: {id: project.licence.id, name: project.licence.name}, + project_licence: {}, + project_themes: get_project_themes(project), + project_components: get_projects_components(project), + project_machines: get_projects_machines(project), + project_users: get_project_users(project) + } + end + + def project_info_stat(project) + { + projectId: project.project_id, + name: project.project_name, + licence: project.project_licence, + themes: project.project_themes, + components: project.project_components, + machines: project.project_machines, + users: project.project_users + } + end + + def get_user_subscription_ca(user, subscriptions_ca_list) + user_subscription_ca = subscriptions_ca_list.select do |ca| + ca.user_id == user.id + end + user_subscription_ca.inject {|sum,x| sum.ca + x.ca } || 0 + end + + def find_or_create_user_info_info_list(user, list) + found = list.select do |l| + l.user_id == user.id + end.first + found || OpenStruct.new(user_info(user)) + end +end diff --git a/app/sweepers/stylesheet_sweeper.rb b/app/sweepers/stylesheet_sweeper.rb new file mode 100644 index 000000000..2c05fab8f --- /dev/null +++ b/app/sweepers/stylesheet_sweeper.rb @@ -0,0 +1,9 @@ +class StylesheetSweeper < ActionController::Caching::Sweeper + observe Stylesheet + + def after_update(record) + if record.contents_changed? + expire_page(:controller => 'stylesheets', action: 'show', id: record.id) + end + end +end \ No newline at end of file diff --git a/app/uploaders/custom_assets_uploader.rb b/app/uploaders/custom_assets_uploader.rb new file mode 100644 index 000000000..e81e8a2f4 --- /dev/null +++ b/app/uploaders/custom_assets_uploader.rb @@ -0,0 +1,58 @@ +# encoding: utf-8 + +class CustomAssetsUploader < CarrierWave::Uploader::Base + + # Include RMagick or MiniMagick support: + # include CarrierWave::RMagick + # include CarrierWave::MiniMagick + include UploadHelper + + # Choose what kind of storage to use for this uploader: + storage :file + # storage :fog + + after :remove, :delete_empty_dirs + + # Override the directory where uploaded files will be stored. + # This is a sensible default for uploaders that are meant to be mounted: + def store_dir + "#{base_store_dir}/#{model.id}" + end + + def base_store_dir + "uploads/#{model.class.to_s.underscore}" + end + + # Provide a default URL as a default if there hasn't been a file uploaded: + # def default_url + # # For Rails 3.1+ asset pipeline compatibility: + # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) + # + # "/images/fallback/" + [version_name, "default.png"].compact.join('_') + # end + + # Process files as they are uploaded: + # process :scale => [200, 300] + # + # def scale(width, height) + # # do something + # end + + # Create different versions of your uploaded files: + # version :thumb do + # process :resize_to_fit => [50, 50] + # end + + # Add a white list of extensions which are allowed to be uploaded. + # For images you might use something like this: + def extension_white_list + %w(pdf png jpeg jpg ico) + end + + # Override the filename of the uploaded files: + # Avoid using model.id or version_name here, see uploader/store.rb for details. + # def filename + # "something.jpg" if original_filename + # end + +end diff --git a/app/uploaders/event_image_uploader.rb b/app/uploaders/event_image_uploader.rb new file mode 100644 index 000000000..8ed9e67b2 --- /dev/null +++ b/app/uploaders/event_image_uploader.rb @@ -0,0 +1,66 @@ +class EventImageUploader < CarrierWave::Uploader::Base + # Include RMagick or MiniMagick support: + # include CarrierWave::RMagick + include CarrierWave::MiniMagick + include UploadHelper + + # Choose what kind of storage to use for this uploader: + storage :file + after :remove, :delete_empty_dirs + # storage :fog + + # Override the directory where uploaded files will be stored. + # This is a sensible default for uploaders that are meant to be mounted: + + def store_dir + "#{base_store_dir}/#{model.id}" + end + + def base_store_dir + "uploads/#{model.class.to_s.underscore}" + end + + # Provide a default URL as a default if there hasn't been a file uploaded: + # def default_url + # # For Rails 3.1+ asset pipeline compatibility: + # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) + # + # "/images/fallback/" + [version_name, "default.png"].compact.join('_') + # end + + # Process files as they are uploaded: + # process :scale => [200, 300] + # + # def scale(width, height) + # # do something + # end + + # Create different versions of your uploaded files: + # version :normal do + # process :resize_to_fit => [312, 270] + # end + + version :large do + process :resize_to_fit => [1000, 700] + end + + version :medium do + process :resize_to_fit => [700, 400] + end + + version :small do + process :resize_to_fit => [260, 260] + end + + # Add a white list of extensions which are allowed to be uploaded. + # For images you might use something like this: + def extension_white_list + %w(jpg jpeg gif png) + end + + # Override the filename of the uploaded files: + # Avoid using model.id or version_name here, see uploader/store.rb for details. + def filename + "#{model.class.to_s.underscore}.#{file.extension}" if original_filename + end +end diff --git a/app/uploaders/machine_file_uploader.rb b/app/uploaders/machine_file_uploader.rb index 0402f5702..3c3b46653 100644 --- a/app/uploaders/machine_file_uploader.rb +++ b/app/uploaders/machine_file_uploader.rb @@ -11,7 +11,6 @@ class MachineFileUploader < CarrierWave::Uploader::Base # Override the directory where uploaded files will be stored. # This is a sensible default for uploaders that are meant to be mounted: - def store_dir "#{base_store_dir}/#{model.id}" end @@ -46,5 +45,5 @@ class MachineFileUploader < CarrierWave::Uploader::Base # Avoid using model.id or version_name here, see uploader/store.rb for details. #def filename #"avatar.#{file.extension}" if original_filename - #end + #end end diff --git a/app/uploaders/machine_image_uploader.rb b/app/uploaders/machine_image_uploader.rb index 7bd62865b..e43239d9e 100644 --- a/app/uploaders/machine_image_uploader.rb +++ b/app/uploaders/machine_image_uploader.rb @@ -54,5 +54,5 @@ class MachineImageUploader < CarrierWave::Uploader::Base # Avoid using model.id or version_name here, see uploader/store.rb for details. def filename "machine_image.#{file.extension}" if original_filename - end + end end diff --git a/app/uploaders/plan_file_uploader.rb b/app/uploaders/plan_file_uploader.rb new file mode 100644 index 000000000..de0c8c5d1 --- /dev/null +++ b/app/uploaders/plan_file_uploader.rb @@ -0,0 +1,49 @@ +class PlanFileUploader < CarrierWave::Uploader::Base + # Include RMagick or MiniMagick support: + # include CarrierWave::RMagick + #include CarrierWave::MiniMagick + include UploadHelper + + # Choose what kind of storage to use for this uploader: + storage :file + after :remove, :delete_empty_dirs + # storage :fog + + # Override the directory where uploaded files will be stored. + # This is a sensible default for uploaders that are meant to be mounted: + def store_dir + "#{base_store_dir}/#{model.id}" + end + + def base_store_dir + "uploads/#{model.class.to_s.underscore}" + end + + # Provide a default URL as a default if there hasn't been a file uploaded: + # def default_url + # # For Rails 3.1+ asset pipeline compatibility: + # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) + # + # "/images/fallback/" + [version_name, "default.png"].compact.join('_') + # end + + # Process files as they are uploaded: + # process :scale => [200, 300] + # + # def scale(width, height) + # # do something + # end + + + # Add a white list of extensions which are allowed to be uploaded. + # For images you might use something like this: + def extension_white_list + %w(pdf png jpeg jpg) + end + + # Override the filename of the uploaded files: + # Avoid using model.id or version_name here, see uploader/store.rb for details. + #def filename + #"avatar.#{file.extension}" if original_filename + #end +end diff --git a/app/uploaders/plan_image_uploader.rb b/app/uploaders/plan_image_uploader.rb new file mode 100644 index 000000000..2e283b4f4 --- /dev/null +++ b/app/uploaders/plan_image_uploader.rb @@ -0,0 +1,62 @@ +class PlanImageUploader < CarrierWave::Uploader::Base + # Include RMagick or MiniMagick support: + # include CarrierWave::RMagick + include CarrierWave::MiniMagick + include UploadHelper + + # Choose what kind of storage to use for this uploader: + storage :file + after :remove, :delete_empty_dirs + # storage :fog + + # Override the directory where uploaded files will be stored. + # This is a sensible default for uploaders that are meant to be mounted: + + def store_dir + "#{base_store_dir}/#{model.id}" + end + + def base_store_dir + "uploads/#{model.class.to_s.underscore}" + end + + # Provide a default URL as a default if there hasn't been a file uploaded: + # def default_url + # # For Rails 3.1+ asset pipeline compatibility: + # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) + # + # "/images/fallback/" + [version_name, "default.png"].compact.join('_') + # end + + # Process files as they are uploaded: + # process :scale => [200, 300] + # + # def scale(width, height) + # # do something + # end + + # Create different versions of your uploaded files: + version :normal do + process :resize_to_fit => [140, 140] + end + + version :small do + process :resize_to_fit => [80, 80] + end + + version :tiny do + process :resize_to_fit => [40, 40] + end + + # Add a white list of extensions which are allowed to be uploaded. + # For images you might use something like this: + def extension_white_list + %w(jpg jpeg gif png) + end + + # Override the filename of the uploaded files: + # Avoid using model.id or version_name here, see uploader/store.rb for details. + def filename + "#{model.class.to_s.underscore}.#{file.extension}" if original_filename + end +end diff --git a/app/uploaders/profil_image_uploader.rb b/app/uploaders/profil_image_uploader.rb index ae889d194..78d9e5af5 100644 --- a/app/uploaders/profil_image_uploader.rb +++ b/app/uploaders/profil_image_uploader.rb @@ -58,5 +58,5 @@ class ProfilImageUploader < CarrierWave::Uploader::Base # Avoid using model.id or version_name here, see uploader/store.rb for details. def filename "#{model.class.to_s.underscore}.#{file.extension}" if original_filename - end + end end diff --git a/app/uploaders/project_cao_uploader.rb b/app/uploaders/project_cao_uploader.rb index 897713cf7..e97577878 100644 --- a/app/uploaders/project_cao_uploader.rb +++ b/app/uploaders/project_cao_uploader.rb @@ -46,5 +46,5 @@ class ProjectCaoUploader < CarrierWave::Uploader::Base # Avoid using model.id or version_name here, see uploader/store.rb for details. #def filename #"avatar.#{file.extension}" if original_filename - #end + #end end diff --git a/app/uploaders/project_image_uploader.rb b/app/uploaders/project_image_uploader.rb index b442e9668..7a5f9e10c 100644 --- a/app/uploaders/project_image_uploader.rb +++ b/app/uploaders/project_image_uploader.rb @@ -58,5 +58,5 @@ class ProjectImageUploader < CarrierWave::Uploader::Base # Avoid using model.id or version_name here, see uploader/store.rb for details. def filename "#{model.class.to_s.underscore}.#{file.extension}" if original_filename - end + end end diff --git a/app/views/api/abuses/create.json.jbuilder b/app/views/api/abuses/create.json.jbuilder new file mode 100644 index 000000000..c45f25a2a --- /dev/null +++ b/app/views/api/abuses/create.json.jbuilder @@ -0,0 +1,3 @@ +json.admin do + json.extract! @abuse, :id, :signaled_id, :signaled_type +end diff --git a/app/views/api/admins/_admin.json.jbuilder b/app/views/api/admins/_admin.json.jbuilder new file mode 100644 index 000000000..203f97f62 --- /dev/null +++ b/app/views/api/admins/_admin.json.jbuilder @@ -0,0 +1,13 @@ +json.extract! admin, :id, :username, :email, :group_id, :slug +json.profile_attributes do + json.id admin.profile.id + json.first_name admin.profile.first_name + json.last_name admin.profile.last_name + json.gender admin.profile.gender + json.birthday admin.profile.birthday if admin.profile.birthday + json.phone admin.profile.phone + json.user_avatar do + json.id admin.profile.user_avatar.id + json.attachment_url admin.profile.user_avatar.attachment_url + end if admin.profile.user_avatar +end diff --git a/app/views/api/admins/create.json.jbuilder b/app/views/api/admins/create.json.jbuilder new file mode 100644 index 000000000..095eb0323 --- /dev/null +++ b/app/views/api/admins/create.json.jbuilder @@ -0,0 +1,3 @@ +json.admin do + json.partial! 'api/admins/admin', admin: @admin +end diff --git a/app/views/api/admins/index.json.jbuilder b/app/views/api/admins/index.json.jbuilder new file mode 100644 index 000000000..0af243ad7 --- /dev/null +++ b/app/views/api/admins/index.json.jbuilder @@ -0,0 +1 @@ +json.admins @admins, partial: 'api/admins/admin', as: :admin diff --git a/app/views/api/auth_providers/_auth_provider.json.jbuilder b/app/views/api/auth_providers/_auth_provider.json.jbuilder new file mode 100644 index 000000000..6ff652c0b --- /dev/null +++ b/app/views/api/auth_providers/_auth_provider.json.jbuilder @@ -0,0 +1 @@ +json.extract! auth_provider, :id, :name, :status, :providable_type \ No newline at end of file diff --git a/app/views/api/auth_providers/active.json.jbuilder b/app/views/api/auth_providers/active.json.jbuilder new file mode 100644 index 000000000..fc0a96351 --- /dev/null +++ b/app/views/api/auth_providers/active.json.jbuilder @@ -0,0 +1,12 @@ +json.partial! 'api/auth_providers/auth_provider', auth_provider: @provider +json.mapping @provider.sso_fields +json.link_to_sso_profile @provider.link_to_sso_profile +if @provider.providable_type == DatabaseProvider.name + json.link_to_sso_connect '/#' +else + json.link_to_sso_connect user_omniauth_authorize_path(@provider.strategy_name.to_sym) +end + +if @provider.providable_type == OAuth2Provider.name + json.domain @provider.providable.domain +end \ No newline at end of file diff --git a/app/views/api/auth_providers/index.json.jbuilder b/app/views/api/auth_providers/index.json.jbuilder new file mode 100644 index 000000000..41e186556 --- /dev/null +++ b/app/views/api/auth_providers/index.json.jbuilder @@ -0,0 +1,3 @@ +json.array!(@providers) do |provider| + json.partial! 'api/auth_providers/auth_provider', auth_provider: provider +end diff --git a/app/views/api/auth_providers/mapping_fields.json.jbuilder b/app/views/api/auth_providers/mapping_fields.json.jbuilder new file mode 100644 index 000000000..0ba57b6d4 --- /dev/null +++ b/app/views/api/auth_providers/mapping_fields.json.jbuilder @@ -0,0 +1,9 @@ + +# we protect some fields are they are designed to be managed by the system and must not be updated externally + +json.user User.column_names - %w(id encrypted_password reset_password_token reset_password_sent_at remember_created_at +sign_in_count current_sign_in_at last_sign_in_at current_sign_in_ip last_sign_in_ip confirmation_token confirmed_at +confirmation_sent_at unconfirmed_email failed_attempts unlock_token locked_at created_at updated_at stp_customer_id slug +provider auth_token) + +json.profile Profile.column_names - %w(id user_id created_at updated_at) + %w(avatar) \ No newline at end of file diff --git a/app/views/api/auth_providers/show.json.jbuilder b/app/views/api/auth_providers/show.json.jbuilder new file mode 100644 index 000000000..665f76237 --- /dev/null +++ b/app/views/api/auth_providers/show.json.jbuilder @@ -0,0 +1,12 @@ +json.partial! 'api/auth_providers/auth_provider', auth_provider: @provider + +# OAuth 2.0 + +if @provider.providable_type == OAuth2Provider.name + json.providable_attributes do + json.extract! @provider.providable, :id, :base_url, :token_endpoint, :authorization_endpoint, :profile_url, :client_id, :client_secret + json.o_auth2_mappings_attributes @provider.providable.o_auth2_mappings do |m| + json.extract! m, :id, :local_model, :local_field, :api_field, :api_endpoint, :api_data_type + end + end +end \ No newline at end of file diff --git a/app/views/api/availabilities/index.json.jbuilder b/app/views/api/availabilities/index.json.jbuilder new file mode 100644 index 000000000..eaeda36e6 --- /dev/null +++ b/app/views/api/availabilities/index.json.jbuilder @@ -0,0 +1,12 @@ +json.array!(@availabilities) do |availability| + json.id availability.id + json.title availability.title + json.start availability.start_at.iso8601 + json.end availability.end_at.iso8601 + json.available_type availability.available_type + json.machine_ids availability.machine_ids + json.training_ids availability.training_ids + json.backgroundColor 'white' + json.borderColor availability.available_type == 'machines' ? '#e4cd78' : '#bd7ae9' + json.tag_ids availability.tag_ids +end diff --git a/app/views/api/availabilities/machine.json.jbuilder b/app/views/api/availabilities/machine.json.jbuilder new file mode 100644 index 000000000..c1811d812 --- /dev/null +++ b/app/views/api/availabilities/machine.json.jbuilder @@ -0,0 +1,26 @@ +json.array!(@slots) do |slot| + json.id slot.id if slot.id + json.can_modify slot.can_modify + json.title slot.title + json.start slot.start_at.iso8601 + json.end slot.end_at.iso8601 + json.is_reserved slot.is_reserved + json.backgroundColor 'white' + json.borderColor slot.is_reserved ? (slot.is_reserved_by_current_user ? '#b2e774' : '#1d98ec') : '#e4cd78' + + json.availability_id slot.availability_id + json.machine do + json.id slot.machine.id + json.name slot.machine.name + end + # the user who booked the slot ... + json.user do + json.id slot.reservation.user.id + json.name slot.reservation.user.profile.full_name + end if slot.reservation # ... if the slot was reserved + json.tag_ids slot.availability.tag_ids + json.tags slot.availability.tags do |t| + json.id t.id + json.name t.name + end +end diff --git a/app/views/api/availabilities/reservations.json.jbuilder b/app/views/api/availabilities/reservations.json.jbuilder new file mode 100644 index 000000000..86b3e56b8 --- /dev/null +++ b/app/views/api/availabilities/reservations.json.jbuilder @@ -0,0 +1,14 @@ +json.array!(@reservation_slots) do |slot| + json.slot_id slot.id + json.start_at slot.start_at.iso8601 + json.end_at slot.end_at.iso8601 + json.message slot.reservation.message + json.reservable slot.reservation.reservable + json.reservable_id slot.reservation.reservable_id + json.reservable_type slot.reservation.reservable_type + json.user do + json.id slot.reservation.user.id + json.name slot.reservation.user.profile.full_name + end + json.canceled_at slot.canceled_at +end diff --git a/app/views/api/availabilities/show.json.jbuilder b/app/views/api/availabilities/show.json.jbuilder new file mode 100644 index 000000000..279d43a37 --- /dev/null +++ b/app/views/api/availabilities/show.json.jbuilder @@ -0,0 +1,13 @@ +json.id @availability.id +json.start_at @availability.start_at.iso8601 +json.end_at @availability.end_at.iso8601 +json.available_type @availability.available_type +json.machine_ids @availability.machine_ids +json.backgroundColor 'white' +json.borderColor @availability.available_type == 'machines' ? '#e4cd78' : '#bd7ae9' +json.title @availability.title +json.tag_ids @availability.tag_ids +json.tags @availability.tags do |t| + json.id t.id + json.name t.name +end diff --git a/app/views/api/availabilities/trainings.json.jbuilder b/app/views/api/availabilities/trainings.json.jbuilder new file mode 100644 index 000000000..b180a6248 --- /dev/null +++ b/app/views/api/availabilities/trainings.json.jbuilder @@ -0,0 +1,42 @@ +json.array!(@availabilities) do |a| + json.id a.id + json.slot_id a.slot_id if a.slot_id + if a.is_reserved + json.title "#{a.trainings[0].name}' - #{t('trainings.i_ve_reserved')}" + elsif a.is_completed + json.title "#{a.trainings[0].name} - #{t('trainings.completed')}" + else + json.title a.trainings[0].name + end + json.start a.start_at.iso8601 + json.end a.end_at.iso8601 + json.is_reserved a.is_reserved + json.backgroundColor 'white' + json.borderColor a.is_reserved ? '#b2e774' : '#bd7ae9' + if a.is_reserved + json.borderColor '#b2e774' + elsif a.is_completed + json.borderColor '#eeeeee' + else + json.borderColor '#bd7ae9' + end + json.can_modify a.can_modify + json.is_completed a.is_completed + json.nb_total_places a.nb_total_places + + json.training do + json.id a.trainings.first.id + json.name a.trainings.first.name + json.description a.trainings.first.description + json.machines a.trainings.first.machines do |m| + json.id m.id + json.name m.name + end + json.amount a.trainings.first.amount_by_group(@user.group_id).amount_by_plan(nil)/100.0 if @user + end + json.tag_ids a.tag_ids + json.tags a.tags do |t| + json.id t.id + json.name t.name + end +end diff --git a/app/views/api/credits/index.json.jbuilder b/app/views/api/credits/index.json.jbuilder new file mode 100644 index 000000000..2216ae3f1 --- /dev/null +++ b/app/views/api/credits/index.json.jbuilder @@ -0,0 +1,8 @@ +json.array!(@credits) do |credit| + json.extract! credit, :id, :creditable_id, :creditable_type, :plan_id, :hours + json.creditable do + json.id credit.creditable.id + json.name credit.creditable.name + end if credit.creditable.present? + json.url credit_url(credit, format: :json) +end diff --git a/app/views/api/credits/show.json.jbuilder b/app/views/api/credits/show.json.jbuilder new file mode 100644 index 000000000..7bca4b42c --- /dev/null +++ b/app/views/api/credits/show.json.jbuilder @@ -0,0 +1,5 @@ +json.extract! @credit, :id, :creditable_id, :creditable_type, :created_at, :updated_at, :plan_id, :hours +json.creditable do + json.id @credit.creditable.id + json.name @credit.creditable.name +end if @credit.creditable diff --git a/app/views/api/custom_assets/show.json.jbuilder b/app/views/api/custom_assets/show.json.jbuilder new file mode 100644 index 000000000..37012a046 --- /dev/null +++ b/app/views/api/custom_assets/show.json.jbuilder @@ -0,0 +1,12 @@ +json.custom_asset do + if @custom_asset + json.extract! @custom_asset, :id, :name + json.custom_asset_file_attributes do + json.id @custom_asset.custom_asset_file.id + json.attachment @custom_asset.custom_asset_file.attachment_identifier + json.attachment_url @custom_asset.custom_asset_file.attachment_url + end if @custom_asset.custom_asset_file + else + json.nil! + end +end \ No newline at end of file diff --git a/app/views/api/events/_event.json.jbuilder b/app/views/api/events/_event.json.jbuilder index 45605d371..e630941b6 100644 --- a/app/views/api/events/_event.json.jbuilder +++ b/app/views/api/events/_event.json.jbuilder @@ -14,7 +14,7 @@ json.start_date event.availability.start_at json.start_time event.availability.start_at json.end_date event.availability.end_at json.end_time event.availability.end_at -json.month t("date.month_names")[event.availability.start_at.month] +json.month t('date.month_names')[event.availability.start_at.month] json.month_id event.availability.start_at.month json.year event.availability.start_at.year json.all_day event.availability.start_at.hour == 0 ? 'true' : 'false' @@ -27,3 +27,4 @@ json.availability_id event.availability_id json.amount (event.amount / 100.0) if event.amount json.reduced_amount (event.reduced_amount / 100.0) if event.reduced_amount json.nb_total_places event.nb_total_places +json.nb_free_places event.nb_free_places || event.nb_total_places diff --git a/app/views/api/events/index.json.jbuilder b/app/views/api/events/index.json.jbuilder index 7c5af6b94..4a24caedc 100644 --- a/app/views/api/events/index.json.jbuilder +++ b/app/views/api/events/index.json.jbuilder @@ -1,5 +1,6 @@ json.array!(@events) do |event| json.partial! 'api/events/event', event: event + json.event_image_small event.event_image.attachment.small.url if event.event_image json.url event_url(event, format: :json) json.nb_total_events @total end diff --git a/app/views/api/events/show.json.jbuilder b/app/views/api/events/show.json.jbuilder index 0f779f081..46c0fd5e4 100644 --- a/app/views/api/events/show.json.jbuilder +++ b/app/views/api/events/show.json.jbuilder @@ -5,5 +5,6 @@ json.recurrence_events @event.recurrence_events do |e| json.start_time e.availability.start_at json.end_date e.availability.end_at json.end_time e.availability.end_at + json.nb_free_places e.nb_free_places json.availability_id e.availability_id end diff --git a/app/views/api/events/upcoming.json.jbuilder b/app/views/api/events/upcoming.json.jbuilder index b870eafc5..97d32bc8c 100644 --- a/app/views/api/events/upcoming.json.jbuilder +++ b/app/views/api/events/upcoming.json.jbuilder @@ -1,5 +1,6 @@ json.array!(@events) do |event| json.partial! 'api/events/event', event: event + json.event_image_medium event.event_image.attachment.medium.url if event.event_image json.url event_url(event, format: :json) end diff --git a/app/views/api/groups/_group.json.jbuilder b/app/views/api/groups/_group.json.jbuilder new file mode 100644 index 000000000..c127b752d --- /dev/null +++ b/app/views/api/groups/_group.json.jbuilder @@ -0,0 +1 @@ +json.extract! group, :id, :slug, :name diff --git a/app/views/api/groups/create.json.jbuilder b/app/views/api/groups/create.json.jbuilder new file mode 100644 index 000000000..eeb783bf2 --- /dev/null +++ b/app/views/api/groups/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/groups/group', group: @group diff --git a/app/views/api/groups/index.json.jbuilder b/app/views/api/groups/index.json.jbuilder index aada10ccd..544b158ea 100644 --- a/app/views/api/groups/index.json.jbuilder +++ b/app/views/api/groups/index.json.jbuilder @@ -1,4 +1 @@ -json.array!(@groups) do |group| - json.id group.id - json.name group.name -end +json.partial! 'api/groups/group', collection: @groups, as: :group diff --git a/app/views/api/groups/update.json.jbuilder b/app/views/api/groups/update.json.jbuilder new file mode 100644 index 000000000..eeb783bf2 --- /dev/null +++ b/app/views/api/groups/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/groups/group', group: @group diff --git a/app/views/api/invoices/avoir.json.jbuilder b/app/views/api/invoices/avoir.json.jbuilder new file mode 100644 index 000000000..2f43bb928 --- /dev/null +++ b/app/views/api/invoices/avoir.json.jbuilder @@ -0,0 +1,13 @@ +json.extract! @avoir, :id, :created_at, :reference, :invoiced_type, :user_id, :avoir_date, :avoir_mode, :invoice_id +json.total (@avoir.total / 100.00) +json.name @avoir.user.profile.full_name +json.has_avoir false +json.is_avoir true +json.date @avoir.avoir_date +json.items @avoir.invoice_items do |item| + json.id item.id + json.stp_invoice_item_id item.stp_invoice_item_id + json.amount (item.amount / 100.0) + json.description item.description + json.invoice_item_id item.invoice_item_id +end \ No newline at end of file diff --git a/app/views/api/invoices/index.json.jbuilder b/app/views/api/invoices/index.json.jbuilder new file mode 100644 index 000000000..09cef1451 --- /dev/null +++ b/app/views/api/invoices/index.json.jbuilder @@ -0,0 +1,12 @@ +json.array!(@invoices) do |invoice| + json.extract! invoice, :id, :created_at, :reference, :invoiced_type, :user_id, :avoir_date + json.total (invoice.total / 100.00) + json.url invoice_url(invoice, format: :json) + json.name invoice.user.profile.full_name + json.has_avoir invoice.has_avoir + json.is_avoir invoice.is_a?(Avoir) + json.is_subscription_invoice invoice.is_subscription_invoice? + json.stripe invoice.stp_invoice_id? + json.date invoice.is_a?(Avoir) ? invoice.avoir_date : invoice.created_at + json.prevent_refund invoice.prevent_refund? +end diff --git a/app/views/api/invoices/show.json.jbuilder b/app/views/api/invoices/show.json.jbuilder new file mode 100644 index 000000000..16196b319 --- /dev/null +++ b/app/views/api/invoices/show.json.jbuilder @@ -0,0 +1,15 @@ +json.extract! @invoice, :id, :created_at, :reference, :invoiced_type, :user_id, :avoir_date, :description +json.total (@invoice.total / 100.00) +json.name @invoice.user.profile.full_name +json.has_avoir @invoice.has_avoir +json.is_avoir @invoice.is_a?(Avoir) +json.is_subscription_invoice @invoice.is_subscription_invoice? +json.stripe @invoice.stp_invoice_id? +json.date @invoice.is_a?(Avoir) ? @invoice.avoir_date : @invoice.created_at +json.items @invoice.invoice_items do |item| + json.id item.id + json.stp_invoice_item_id item.stp_invoice_item_id + json.amount (item.amount / 100.0) + json.description item.description + json.avoir_item_id item.invoice_item.id if item.invoice_item +end diff --git a/app/views/api/machines/index.json.jbuilder b/app/views/api/machines/index.json.jbuilder index 5f642ebe2..25c92a1b3 100644 --- a/app/views/api/machines/index.json.jbuilder +++ b/app/views/api/machines/index.json.jbuilder @@ -1,5 +1,13 @@ +user_is_admin = (current_user and current_user.is_admin?) + json.array!(@machines) do |machine| json.extract! machine, :id, :name, :description, :spec, :slug json.url machine_url(machine, format: :json) json.machine_image machine.machine_image.attachment.medium.url if machine.machine_image + json.current_user_is_training current_user.is_training_machine?(machine) if current_user + json.current_user_training_reservation do + json.partial! 'api/reservations/reservation', reservation: current_user.training_reservation_by_machine(machine) + end if current_user and !current_user.is_training_machine?(machine) and current_user.training_reservation_by_machine(machine) + + json.plan_ids machine.plan_ids if user_is_admin end diff --git a/app/views/api/machines/show.json.jbuilder b/app/views/api/machines/show.json.jbuilder index c16d4616c..1e6d8760a 100644 --- a/app/views/api/machines/show.json.jbuilder +++ b/app/views/api/machines/show.json.jbuilder @@ -5,7 +5,22 @@ json.machine_files_attributes @machine.machine_files do |f| json.attachment f.attachment_identifier json.attachment_url f.attachment_url end +json.trainings @machine.trainings.each, :id, :name +json.current_user_is_training current_user.is_training_machine?(@machine) if current_user +json.current_user_training_reservation do + json.partial! 'api/reservations/reservation', reservation: current_user.training_reservation_by_machine(@machine) +end if current_user and !current_user.is_training_machine?(@machine) and current_user.training_reservation_by_machine(@machine) +json.amount_by_group Group.all do |g| + json.id g.id + json.name g.name + json.not_subscribe_amount @machine.not_subscribe_price(g.id).amount/100.0 + + json.amount_by_plan @machine.prices_by_group(g.id) do |price| + json.plan_id price.plan_id + json.amount price.amount/100.0 + end +end json.machine_projects @machine.projects.published.last(10) do |p| json.id p.id json.name p.name diff --git a/app/views/api/members/export_members.xls.erb b/app/views/api/members/export_members.xls.erb index cce8965de..3f1735f9f 100644 --- a/app/views/api/members/export_members.xls.erb +++ b/app/views/api/members/export_members.xls.erb @@ -1,13 +1,16 @@ - - - - - - - - + + + + + + + + + + + <% @datas.each do |data| %> @@ -15,10 +18,13 @@ - + + + + <% end %>
    IDNomPrénomEmailGenreAgeTel.Type utilisateur<%=t('export_members.id')%><%=t('export_members.surname')%><%=t('export_members.first_name')%><%=t('export_members.email')%><%=t('export_members.gender')%><%=t('export_members.age')%><%=t('export_members.phone')%><%=t('export_members.group')%><%=t('export_members.subscription')%><%=t('export_members.subscription_end_date')%><%=t('export_members.validated_trainings')%>
    <%= data.profile.last_name %> <%= data.profile.first_name %> <%= data.email %><%= data.profile.gender ? 'Homme' : 'Femme' %><%= data.profile.gender ? t('export_members.man') : t('export_members.woman') %> <%= data.profile.age %> <%= data.profile.phone %> <%= data.group.name %><%= (data.subscription and data.subscription.expired_at > Time.now) ? data.subscription.plan.name : t('export_members.without_subscriptions') %><%= (data.subscription and data.subscription.expired_at > Time.now) ? data.subscription.expired_at : '' %><%= raw data.trainings.map(&:name).join(' ') %>
    diff --git a/app/views/api/members/export_reservations.xls.erb b/app/views/api/members/export_reservations.xls.erb new file mode 100644 index 000000000..e1a69517f --- /dev/null +++ b/app/views/api/members/export_reservations.xls.erb @@ -0,0 +1,22 @@ + + + + + + + + + + + <% @datas.each do |d| %> + + + + + + + + + + <% end %> +
    <%=t('export_reservations.customer_id')%><%=t('export_reservations.customer')%><%=t('export_reservations.reservation_date')%><%=t('export_reservations.reservation_type')%><%=t('export_reservations.reservation_object')%><%=t('export_reservations.slots_number_hours_tickets')%>
    <%= d.user.id %><%= d.user.profile.full_name %><%= d.created_at %><%= d.reservable_type %><%= d.reservable.name if !d.reservable.nil? %><%= d.slots.count %><%= (d.stp_invoice_id.nil?)? t('export_reservations.local_payment') : t('export_reservations.online_payment') %>
    \ No newline at end of file diff --git a/app/views/api/members/export_subscriptions.xls.erb b/app/views/api/members/export_subscriptions.xls.erb new file mode 100644 index 000000000..64805d1fb --- /dev/null +++ b/app/views/api/members/export_subscriptions.xls.erb @@ -0,0 +1,26 @@ + + + + + + + + + + + + + <% @datas.each do |data| %> + + + + + + + + + + + + <% end %> +
    <%=t('export_subscriptions.id')%><%=t('export_subscriptions.customer')%><%=t('export_subscriptions.email')%><%=t('export_subscriptions.subscription')%><%=t('export_subscriptions.period')%><%=t('export_subscriptions.start_date')%><%=t('export_subscriptions.expiration_date')%><%=t('export_subscriptions.amount')%><%=t('export_subscriptions.payment_method')%>
    <%= data.user.id %><%= data.user.profile.full_name %><%= data.user.email %><%= data.plan.human_readable_name(group: true) %><%= t("duration.#{data.plan.interval}", count: data.plan.interval_count) %><%= data.created_at %><%= data.expired_at %><%= number_to_currency(data.plan.amount / 100) %><%= (data.stp_subscription_id.nil?)? t('export_subscriptions.local_payment') : t('export_subscriptions.online_payment') %>
    diff --git a/app/views/api/members/index.json.jbuilder b/app/views/api/members/index.json.jbuilder index 7eabfb318..e72b1e569 100644 --- a/app/views/api/members/index.json.jbuilder +++ b/app/views/api/members/index.json.jbuilder @@ -1,3 +1,5 @@ +user_is_admin = (current_user and current_user.is_admin?) + json.array!(@members) do |member| json.id member.id json.username member.username @@ -14,14 +16,46 @@ json.array!(@members) do |member| json.first_name member.profile.first_name json.last_name member.profile.last_name json.gender member.profile.gender.to_s - if current_user and current_user.is_admin? + if user_is_admin json.phone member.profile.phone json.birthday member.profile.birthday.iso8601 if member.profile.birthday end - end + end if attribute_requested?(@requested_attributes, 'profile') json.group_id member.group_id json.group do json.id member.group.id json.name member.group.name - end + end if attribute_requested?(@requested_attributes, 'group') and member.group + + if user_is_admin + json.subscribed_plan do + json.partial! 'api/shared/plan', plan: member.subscribed_plan + end if member.subscribed_plan + json.subscription do + json.id member.subscription.id + json.expired_at member.subscription.expired_at.iso8601 + json.canceled_at member.subscription.canceled_at.iso8601 if member.subscription.canceled_at + json.stripe member.subscription.stp_subscription_id.present? + json.plan do + json.id member.subscription.plan.id + json.name member.subscription.plan.name + json.interval member.subscription.plan.interval + json.amount member.subscription.plan.amount ? (member.subscription.plan.amount / 100.0) : 0 + end + end if member.subscription + end if attribute_requested?(@requested_attributes, 'subscription') + + json.training_credits member.training_credits do |tc| + json.training_id tc.creditable_id + end if attribute_requested?(@requested_attributes, 'credits') or attribute_requested?(@requested_attributes, 'training_credits') + + json.machine_credits member.machine_credits do |mc| + json.machine_id mc.creditable_id + json.hours_used mc.users_credits.find_by_user_id(member.id).hours_used + end if attribute_requested?(@requested_attributes, 'credits') or attribute_requested?(@requested_attributes, 'machine_credits') + + json.tags member.tags do |t| + json.id t.id + json.name t.name + end if attribute_requested?(@requested_attributes, 'tags') end diff --git a/app/views/api/members/show.json.jbuilder b/app/views/api/members/show.json.jbuilder index 512b7d7bb..d9eeca2b2 100644 --- a/app/views/api/members/show.json.jbuilder +++ b/app/views/api/members/show.json.jbuilder @@ -1,6 +1,7 @@ -json.extract! @member, :id, :username, :email, :group_id, :slug +json.extract! @member, :id, :username, :email, :group_id, :slug, :invoicing_disabled, :is_allow_contact json.role @member.roles.first.name json.name @member.profile.full_name +json.need_completion @member.need_completion? json.profile do json.id @member.profile.id json.user_avatar do @@ -19,6 +20,43 @@ json.profile do end if @member.profile.address json.phone @member.profile.phone end +json.subscribed_plan do + json.partial! 'api/shared/plan', plan: @member.subscribed_plan +end if @member.subscribed_plan +json.subscription do + json.id @member.subscription.id + json.expired_at @member.subscription.expired_at.iso8601 + json.canceled_at @member.subscription.canceled_at.iso8601 if @member.subscription.canceled_at + json.stripe @member.subscription.stp_subscription_id.present? + json.plan do + json.id @member.subscription.plan.id + json.base_name @member.subscription.plan.base_name + json.name @member.subscription.plan.name + json.interval @member.subscription.plan.interval + json.interval_count @member.subscription.plan.interval_count + json.amount @member.subscription.plan.amount ? (@member.subscription.plan.amount / 100.0) : 0 + end +end if @member.subscription +json.training_ids @member.training_ids +json.trainings @member.trainings do |t| + json.id t.id + json.name t.name +end +json.training_reservations @member.reservations.where(reservable_type: 'Training') do |r| + json.id r.id + json.start_at r.slots.first.start_at + json.end_at r.slots.first.end_at + json.reservable r.reservable + json.is_valid @member.training_ids.include?(r.reservable.id) + json.canceled_at r.slots.first.canceled_at +end +json.training_credits @member.training_credits do |tc| + json.training_id tc.creditable_id +end +json.machine_credits @member.machine_credits do |mc| + json.machine_id mc.creditable_id + json.hours_used mc.users_credits.find_by_user_id(@member.id).hours_used +end json.last_sign_in_at @member.last_sign_in_at.iso8601 if @member.last_sign_in_at json.all_projects @member.all_projects do |project| json.extract! project, :id, :name, :description, :author_id, :licence_id, :slug @@ -50,3 +88,26 @@ json.all_projects @member.all_projects do |project| json.valid_token pu.valid_token if !pu.is_valid and @member == pu.user end end +json.events_reservations @member.reservations.where(reservable_type: 'Event').joins(:slots).order('slots.start_at asc') do |r| + json.id r.id + json.start_at r.slots.first.start_at + json.end_at r.slots.first.end_at + json.nb_reserve_places r.nb_reserve_places + json.nb_reserve_reduced_places r.nb_reserve_reduced_places + json.reservable r.reservable +end +json.invoices @member.invoices.order('reference DESC') do |i| + json.id i.id + json.reference i.reference + json.type i.invoiced_type + json.invoiced_id i.invoiced_id + json.total (i.total / 100.00) + json.is_avoir i.is_a?(Avoir) + json.date i.is_a?(Avoir) ? i.avoir_date : i.created_at +end +json.tag_ids @member.tag_ids +json.tags @member.tags do |t| + json.id t.id + json.name t.name +end +json.merged_at @member.merged_at diff --git a/app/views/api/notifications/_notify_admin_abuse_reported.json.jbuilder b/app/views/api/notifications/_notify_admin_abuse_reported.json.jbuilder new file mode 100644 index 000000000..61be992b8 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_abuse_reported.json.jbuilder @@ -0,0 +1,6 @@ +json.title notification.notification_type +json.description t('.an_abuse_was_reported_on_TYPE_ID_NAME_html', + TYPE: notification.attached_object.signaled_type, + ID: notification.attached_object.signaled_id, + NAME: (notification.attached_object.signaled.name ? notification.attached_object.signaled.name : '')) +json.url notification_url(notification, format: :json) \ No newline at end of file diff --git a/app/views/api/notifications/_notify_admin_invoicing_changed.json.jbuilder b/app/views/api/notifications/_notify_admin_invoicing_changed.json.jbuilder new file mode 100644 index 000000000..2d9608fa7 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_invoicing_changed.json.jbuilder @@ -0,0 +1,7 @@ +json.title notification.notification_type +json.description _t('.invoices_generation_was_STATUS_for_user_NAME_html', + { + STATUS: notification.attached_object.invoicing_disabled.to_s, + NAME: notification.attached_object.profile.full_name + }) #messageFormat +json.url notification_url(notification, format: :json) \ No newline at end of file diff --git a/app/views/api/notifications/_notify_admin_member_create_reservation.json.jbuilder b/app/views/api/notifications/_notify_admin_member_create_reservation.json.jbuilder new file mode 100644 index 000000000..13ec07a04 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_member_create_reservation.json.jbuilder @@ -0,0 +1,5 @@ +json.title notification.notification_type +json.description t('.a_RESERVABLE_reservation_was_made_by_USER_html', + RESERVABLE: notification.attached_object.reservable.name, + USER: notification.attached_object.user.profile.full_name) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_profile_complete.json.jbuilder b/app/views/api/notifications/_notify_admin_profile_complete.json.jbuilder new file mode 100644 index 000000000..203ffe3a6 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_profile_complete.json.jbuilder @@ -0,0 +1,5 @@ +json.title notification.notification_type +json.description t('.account_imported_from_PROVIDER_(UID)_has_completed_its_informations_html', + PROVIDER: notification.attached_object.provider, + UID: notification.attached_object.uid) +json.url notification_url(notification, format: :json) \ No newline at end of file diff --git a/app/views/api/notifications/_notify_admin_slot_is_canceled.json.jbuilder b/app/views/api/notifications/_notify_admin_slot_is_canceled.json.jbuilder new file mode 100644 index 000000000..e90614b13 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_slot_is_canceled.json.jbuilder @@ -0,0 +1,5 @@ +json.title notification.notification_type +json.description t('.USER_s_reservation_on_the_DATE_was_cancelled_remember_to_generate_a_refund_invoice_if_applicable_html', + USER: notification.attached_object.reservation.user.profile.full_name, + DATE: I18n.l(notification.attached_object.start_at, format: :long)) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_slot_is_modified.json.jbuilder b/app/views/api/notifications/_notify_admin_slot_is_modified.json.jbuilder new file mode 100644 index 000000000..1a68f054f --- /dev/null +++ b/app/views/api/notifications/_notify_admin_slot_is_modified.json.jbuilder @@ -0,0 +1,3 @@ +json.title notification.notification_type +json.description t('.a_booking_slot_was_modified') +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_subscribed_plan.json.jbuilder b/app/views/api/notifications/_notify_admin_subscribed_plan.json.jbuilder new file mode 100644 index 000000000..14cf26c78 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_subscribed_plan.json.jbuilder @@ -0,0 +1,5 @@ +json.title notification.notification_type +json.description t('.subscription_PLAN_has_been_subscribed_by_USER_html', + PLAN: notification.attached_object.plan.name, + USER: notification.attached_object.user.profile.full_name) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_subscription_canceled.json.jbuilder b/app/views/api/notifications/_notify_admin_subscription_canceled.json.jbuilder new file mode 100644 index 000000000..85bafc10c --- /dev/null +++ b/app/views/api/notifications/_notify_admin_subscription_canceled.json.jbuilder @@ -0,0 +1,4 @@ +json.title notification.notification_type +json.description t('.USER_s_subscription_has_been_cancelled', + USER: notification.attached_object.user.profile.full_name) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_subscription_extended.json.jbuilder b/app/views/api/notifications/_notify_admin_subscription_extended.json.jbuilder new file mode 100644 index 000000000..930d87cb8 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_subscription_extended.json.jbuilder @@ -0,0 +1,9 @@ +json.title notification.notification_type +json.description _t('.subscription_PLAN_of_the_member_USER_has_been_extended_FREE_until_DATE_html', + { + PLAN: notification.attached_object.plan.name, + USER: notification.attached_object.user.profile.full_name, + FREE: notification.get_meta_data(:free_days).to_s, + DATE: I18n.l(notification.attached_object.expired_at.to_date) + }) # messageFormat +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_subscription_is_expired.json.jbuilder b/app/views/api/notifications/_notify_admin_subscription_is_expired.json.jbuilder new file mode 100644 index 000000000..293cf4a91 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_subscription_is_expired.json.jbuilder @@ -0,0 +1,4 @@ +json.title notification.notification_type +json.description t('.USER_s_subscription_has_expired', + USER:notification.attached_object.user.profile.full_name) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_subscription_will_expire_in_7_days.json.jbuilder b/app/views/api/notifications/_notify_admin_subscription_will_expire_in_7_days.json.jbuilder new file mode 100644 index 000000000..746f21055 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_subscription_will_expire_in_7_days.json.jbuilder @@ -0,0 +1,4 @@ +json.title notification.notification_type +json.description t('.USER_s_subscription_will_expire_in_7_days', + USER: notification.attached_object.user.profile.full_name) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_user_group_changed.json.jbuilder b/app/views/api/notifications/_notify_admin_user_group_changed.json.jbuilder new file mode 100644 index 000000000..179eaeae4 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_user_group_changed.json.jbuilder @@ -0,0 +1,7 @@ +json.title notification.notification_type +json.description _t('.user_NAME_changed_his_group_html', + { + NAME: notification.attached_object.profile.full_name, + GENDER: bool_to_sym(notification.attached_object.profile.gender) + }) # messageFormat +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_user_merged.json.jbuilder b/app/views/api/notifications/_notify_admin_user_merged.json.jbuilder new file mode 100644 index 000000000..670fae07d --- /dev/null +++ b/app/views/api/notifications/_notify_admin_user_merged.json.jbuilder @@ -0,0 +1,9 @@ +json.title notification.notification_type +json.description _t('.user_NAME_has_merged_his_account_with_the_one_imported_from_PROVIDER_(UID)_html', + { + NAME: notification.attached_object.profile.full_name, + GENDER: notification.attached_object.profile.gender, + PROVIDER: notification.attached_object.provider, + UID: notification.attached_object.uid + }) # messageFormat +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_when_project_published.json.jbuilder b/app/views/api/notifications/_notify_admin_when_project_published.json.jbuilder index 94c819fdc..e90c6daf0 100644 --- a/app/views/api/notifications/_notify_admin_when_project_published.json.jbuilder +++ b/app/views/api/notifications/_notify_admin_when_project_published.json.jbuilder @@ -1,3 +1,5 @@ json.title notification.notification_type -json.description "Le projet #{notification.attached_object.name} vient d'être publié." +json.description t('.project_NAME_has_been_published_html', + ID: notification.attached_object.id, + NAME: notification.attached_object.name) json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_when_user_is_created.json.jbuilder b/app/views/api/notifications/_notify_admin_when_user_is_created.json.jbuilder index 362430b6e..caeeefbbf 100644 --- a/app/views/api/notifications/_notify_admin_when_user_is_created.json.jbuilder +++ b/app/views/api/notifications/_notify_admin_when_user_is_created.json.jbuilder @@ -1,3 +1,5 @@ json.title notification.notification_type -json.description "Un nouveau compte utilisateur vient d'être créé : #{ notification.attached_object.profile.full_name } <#{ notification.attached_object.email}>." +json.description t('.a_new_user_account_has_been_created_NAME_EMAIL_html', + NAME: notification.attached_object.profile.full_name, + EMAIL: notification.attached_object.email) json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_admin_when_user_is_imported.json.jbuilder b/app/views/api/notifications/_notify_admin_when_user_is_imported.json.jbuilder new file mode 100644 index 000000000..6d4cec254 --- /dev/null +++ b/app/views/api/notifications/_notify_admin_when_user_is_imported.json.jbuilder @@ -0,0 +1,5 @@ +json.title notification.notification_type +json.description t('.a_new_user_account_has_been_imported_from_PROVIDER_(UID)_html', + PROVIDER: notification.attached_object.provider, + UID: notification.attached_object.uid) +json.url notification_url(notification, format: :json) \ No newline at end of file diff --git a/app/views/api/notifications/_notify_member_create_reservation.json.jbuilder b/app/views/api/notifications/_notify_member_create_reservation.json.jbuilder new file mode 100644 index 000000000..23ca76840 --- /dev/null +++ b/app/views/api/notifications/_notify_member_create_reservation.json.jbuilder @@ -0,0 +1,4 @@ +json.title notification.notification_type +json.description t('.your_reservation_RESERVABLE_was_successfully_saved_html', + RESERVABLE: notification.attached_object.reservable.name) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_member_slot_is_canceled.json.jbuilder b/app/views/api/notifications/_notify_member_slot_is_canceled.json.jbuilder new file mode 100644 index 000000000..9572765a5 --- /dev/null +++ b/app/views/api/notifications/_notify_member_slot_is_canceled.json.jbuilder @@ -0,0 +1,5 @@ +json.title notification.notification_type +json.description t('.your_reservation_RESERVABLE_of_DATE_was_successfully_cancelled', + RESERVABLE: notification.attached_object.reservation.reservable.name, + DATE: I18n.l(notification.attached_object.start_at, format: :long)) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_member_slot_is_modified.json.jbuilder b/app/views/api/notifications/_notify_member_slot_is_modified.json.jbuilder new file mode 100644 index 000000000..076e36e55 --- /dev/null +++ b/app/views/api/notifications/_notify_member_slot_is_modified.json.jbuilder @@ -0,0 +1,3 @@ +json.title notification.notification_type +json.description t('.your_reservation_slot_was_successfully_changed') +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_member_subscribed_plan.json.jbuilder b/app/views/api/notifications/_notify_member_subscribed_plan.json.jbuilder new file mode 100644 index 000000000..2c51ad989 --- /dev/null +++ b/app/views/api/notifications/_notify_member_subscribed_plan.json.jbuilder @@ -0,0 +1,4 @@ +json.title notification.notification_type +json.description t('.you_have_subscribed_to_PLAN_html', + PLAN: notification.attached_object.plan.name) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_member_subscribed_plan_is_changed.json.jbuilder b/app/views/api/notifications/_notify_member_subscribed_plan_is_changed.json.jbuilder new file mode 100644 index 000000000..bb5af8c65 --- /dev/null +++ b/app/views/api/notifications/_notify_member_subscribed_plan_is_changed.json.jbuilder @@ -0,0 +1,4 @@ +json.title notification.notification_type +json.description t('.you_have_changed_your_subscription_to_PLAN_html', + PLAN: notification.attached_object.plan.name) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_member_subscription_canceled.json.jbuilder b/app/views/api/notifications/_notify_member_subscription_canceled.json.jbuilder new file mode 100644 index 000000000..ff6700965 --- /dev/null +++ b/app/views/api/notifications/_notify_member_subscription_canceled.json.jbuilder @@ -0,0 +1,4 @@ +json.title notification.notification_type +json.description t('.your_subscription_PLAN_was_successfully_cancelled_html', + PLAN: notification.attached_object.plan.name) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_member_subscription_extended.json.jbuilder b/app/views/api/notifications/_notify_member_subscription_extended.json.jbuilder new file mode 100644 index 000000000..bb9ce437f --- /dev/null +++ b/app/views/api/notifications/_notify_member_subscription_extended.json.jbuilder @@ -0,0 +1,8 @@ +json.title notification.notification_type +json.description _t('.your_subscription_PLAN_has_been_extended_FREE_until_DATE_html', + { + PLAN: notification.attached_object.plan.name, + FREE: notification.get_meta_data(:free_days).to_s, + DATE: I18n.l(notification.attached_object.expired_at.to_date) + }) # messageFormat +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_member_subscription_is_expired.json.jbuilder b/app/views/api/notifications/_notify_member_subscription_is_expired.json.jbuilder new file mode 100644 index 000000000..3e6be1f42 --- /dev/null +++ b/app/views/api/notifications/_notify_member_subscription_is_expired.json.jbuilder @@ -0,0 +1,3 @@ +json.title notification.notification_type +json.description t('.your_subscription_has_expired') +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_member_subscription_will_expire_in_7_days.json.jbuilder b/app/views/api/notifications/_notify_member_subscription_will_expire_in_7_days.json.jbuilder new file mode 100644 index 000000000..873bab323 --- /dev/null +++ b/app/views/api/notifications/_notify_member_subscription_will_expire_in_7_days.json.jbuilder @@ -0,0 +1,3 @@ +json.title notification.notification_type +json.description t('.your_subscription_will_expire_in_7_days') +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_partner_subscribed_plan.json.jbuilder b/app/views/api/notifications/_notify_partner_subscribed_plan.json.jbuilder new file mode 100644 index 000000000..90fadd561 --- /dev/null +++ b/app/views/api/notifications/_notify_partner_subscribed_plan.json.jbuilder @@ -0,0 +1,7 @@ +json.title notification.notification_type +json.description t('.subscription_partner_PLAN_has_been_subscribed_by_USER_html', + { + PLAN: notification.attached_object.plan.name, + USER: notification.attached_object.user.profile.full_name + }) # messageFormat +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_project_author_when_collaborator_valid.json.jbuilder b/app/views/api/notifications/_notify_project_author_when_collaborator_valid.json.jbuilder index 8eef540d0..c0b254eb8 100644 --- a/app/views/api/notifications/_notify_project_author_when_collaborator_valid.json.jbuilder +++ b/app/views/api/notifications/_notify_project_author_when_collaborator_valid.json.jbuilder @@ -1,3 +1,5 @@ json.title notification.notification_type -json.description "Le membre #{notification.attached_object.user.profile.full_name} est devenu un collaborateur de votre projet #{notification.attached_object.project.name}." +json.description t('.USER_became_collaborator_of_your_project', + USER: notification.attached_object.user.profile.full_name) + + "#{notification.attached_object.project.name}." json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_project_collaborator_to_valid.json.jbuilder b/app/views/api/notifications/_notify_project_collaborator_to_valid.json.jbuilder index 13c65ecca..f656f668d 100644 --- a/app/views/api/notifications/_notify_project_collaborator_to_valid.json.jbuilder +++ b/app/views/api/notifications/_notify_project_collaborator_to_valid.json.jbuilder @@ -1,3 +1,4 @@ json.title notification.notification_type -json.description "Vous êtes invité à collaborer sur le projet suivant : #{notification.attached_object.project.name}." +json.description t('.you_are_invited_to_collaborate_on_the_project') + + "#{notification.attached_object.project.name}." json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_user_auth_migration.json.jbuilder b/app/views/api/notifications/_notify_user_auth_migration.json.jbuilder new file mode 100644 index 000000000..d1fb253d9 --- /dev/null +++ b/app/views/api/notifications/_notify_user_auth_migration.json.jbuilder @@ -0,0 +1,3 @@ +json.title notification.notification_type +json.description t('.your_account_was_migrated') +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_user_profile_complete.json.jbuilder b/app/views/api/notifications/_notify_user_profile_complete.json.jbuilder new file mode 100644 index 000000000..1eba5b35d --- /dev/null +++ b/app/views/api/notifications/_notify_user_profile_complete.json.jbuilder @@ -0,0 +1,3 @@ +json.title notification.notification_type +json.description t('.your_profile_was_completed') +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_user_training_valid.json.jbuilder b/app/views/api/notifications/_notify_user_training_valid.json.jbuilder new file mode 100644 index 000000000..af7fc64b3 --- /dev/null +++ b/app/views/api/notifications/_notify_user_training_valid.json.jbuilder @@ -0,0 +1,4 @@ +json.title notification.notification_type +json.description t('.your_TRAINING_was_validated_html', + TRAINING: notification.attached_object.training.name) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_user_user_group_changed.json.jbuilder b/app/views/api/notifications/_notify_user_user_group_changed.json.jbuilder new file mode 100644 index 000000000..6da7e4efa --- /dev/null +++ b/app/views/api/notifications/_notify_user_user_group_changed.json.jbuilder @@ -0,0 +1,3 @@ +json.title notification.notification_type +json.description t('.your_group_has_changed') +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_user_when_avoir_ready.json.jbuilder b/app/views/api/notifications/_notify_user_when_avoir_ready.json.jbuilder new file mode 100644 index 000000000..48e3de107 --- /dev/null +++ b/app/views/api/notifications/_notify_user_when_avoir_ready.json.jbuilder @@ -0,0 +1,7 @@ +json.title notification.notification_type +amount = notification.attached_object.total / 100.0 +json.description t('.your_avoir_is_ready_html', + REFERENCE: notification.attached_object.reference, + AMOUNT: number_to_currency(amount), + INVOICE_ID: notification.attached_object.id) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_notify_user_when_invoice_ready.json.jbuilder b/app/views/api/notifications/_notify_user_when_invoice_ready.json.jbuilder new file mode 100644 index 000000000..8367110c3 --- /dev/null +++ b/app/views/api/notifications/_notify_user_when_invoice_ready.json.jbuilder @@ -0,0 +1,7 @@ +json.title notification.notification_type +amount = notification.attached_object.total / 100.0 +json.description t('.your_invoice_is_ready_html', + REFERENCE: notification.attached_object.reference, + AMOUNT: number_to_currency(amount), + INVOICE_ID: notification.attached_object.id) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/_undefined_notification.json.jbuilder b/app/views/api/notifications/_undefined_notification.json.jbuilder new file mode 100644 index 000000000..a469ac4f8 --- /dev/null +++ b/app/views/api/notifications/_undefined_notification.json.jbuilder @@ -0,0 +1,5 @@ +json.title t('.unknown_notification') +json.description t('.notification_ID_wrong_type_TYPE_unknown', + ID: notification.id, + TYPE: notification.notification_type_id) +json.url notification_url(notification, format: :json) diff --git a/app/views/api/notifications/index.json.jbuilder b/app/views/api/notifications/index.json.jbuilder index a68082679..8a1380a64 100644 --- a/app/views/api/notifications/index.json.jbuilder +++ b/app/views/api/notifications/index.json.jbuilder @@ -1,9 +1,13 @@ json.array!(@notifications) do |notification| - if notification.attached_object + if Module.const_get(notification.attached_object_type) and notification.attached_object # WHY WERE WE DOING Object.const_defined?(notification.attached_object_type) ??? Why not just deleting obsolete notifications ?! Object.const_defined? was introducing a bug! Module.const_get is a TEMPORARY fix, NOT a solution json.extract! notification, :id, :notification_type_id, :notification_type, :created_at, :is_read json.attached_object notification.attached_object json.message do - json.partial! "/api/notifications/#{notification.notification_type}", notification: notification + if notification.notification_type.nil? + json.partial! 'api/notifications/undefined_notification', notification: notification + else + json.partial! "/api/notifications/#{notification.notification_type}", notification: notification + end end end end.delete_if {|n| n['id'] == nil } diff --git a/app/views/api/plans/_plan.json.jbuilder b/app/views/api/plans/_plan.json.jbuilder new file mode 100644 index 000000000..bda984fbe --- /dev/null +++ b/app/views/api/plans/_plan.json.jbuilder @@ -0,0 +1,28 @@ +json.extract! plan, :id, :base_name, :name, :interval, :interval_count, :group_id, :training_credit_nb, :is_rolling, :description, :type, :ui_weight +json.amount (plan.amount / 100.00) +json.prices plan.prices, partial: 'api/prices/price', as: :price +json.plan_file_attributes do + json.id plan.plan_file.id + json.attachment_identifier plan.plan_file.attachment_identifier +end if plan.plan_file + +json.prices plan.prices do |price| + json.extract! price, :id, :group_id, :plan_id, :priceable_type, :priceable_id + json.amount price.amount / 100.0 + json.priceable_name price.priceable.name +end + +json.partners plan.partners do |partner| + json.first_name partner.first_name + json.last_name partner.last_name + json.email partner.email +end if plan.respond_to?(:partners) + +json.training_credits plan.training_credits do |tc| + json.training_id tc.creditable_id +end if attribute_requested?(@attributes_requested, 'trainings_credits') + +json.machine_credits plan.machine_credits do |mc| + json.machine_id mc.creditable_id + json.hours mc.hours +end if attribute_requested?(@attributes_requested, 'machines_credits') diff --git a/app/views/api/plans/index.json.jbuilder b/app/views/api/plans/index.json.jbuilder new file mode 100644 index 000000000..a63916500 --- /dev/null +++ b/app/views/api/plans/index.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/plans/plan', collection: @plans, as: :plan diff --git a/app/views/api/plans/shallow_index.json.jbuilder b/app/views/api/plans/shallow_index.json.jbuilder new file mode 100644 index 000000000..525e74926 --- /dev/null +++ b/app/views/api/plans/shallow_index.json.jbuilder @@ -0,0 +1,11 @@ +json.array!(@plans) do |plan| + json.id plan.id + json.ui_weight plan.ui_weight + json.group_id plan.group_id + json.base_name plan.base_name + json.amount plan.amount / 100.0 + json.interval plan.interval + json.interval_count plan.interval_count + json.type plan.type + json.plan_file_url plan.plan_file.attachment_url if plan.plan_file +end diff --git a/app/views/api/plans/show.json.jbuilder b/app/views/api/plans/show.json.jbuilder new file mode 100644 index 000000000..799730b4d --- /dev/null +++ b/app/views/api/plans/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/plans/plan', plan: @plan diff --git a/app/views/api/prices/_price.json.jbuilder b/app/views/api/prices/_price.json.jbuilder new file mode 100644 index 000000000..19a0ffc06 --- /dev/null +++ b/app/views/api/prices/_price.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! price, :id, :group_id, :plan_id, :priceable_type, :priceable_id +json.amount price.amount / 100.0 diff --git a/app/views/api/prices/compute.json.jbuilder b/app/views/api/prices/compute.json.jbuilder new file mode 100644 index 000000000..752ac43ac --- /dev/null +++ b/app/views/api/prices/compute.json.jbuilder @@ -0,0 +1,9 @@ +json.price @amount[:total] / 100.00 +json.details do + json.slots @amount[:elements][:slots] do |slot| + json.start_at slot[:start_at] + json.price slot[:price] / 100.00 + json.promo slot[:promo] + end + json.plan @amount[:elements][:plan] / 100.00 if @amount[:elements][:plan] +end if @amount[:elements] diff --git a/app/views/api/prices/index.json.jbuilder b/app/views/api/prices/index.json.jbuilder new file mode 100644 index 000000000..5cd43fe7b --- /dev/null +++ b/app/views/api/prices/index.json.jbuilder @@ -0,0 +1 @@ +json.prices @prices, partial: 'api/prices/price', as: :price diff --git a/app/views/api/prices/update.json.jbuilder b/app/views/api/prices/update.json.jbuilder new file mode 100644 index 000000000..a59de38a1 --- /dev/null +++ b/app/views/api/prices/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/prices/price', price: @price diff --git a/app/views/api/pricing/index.json.jbuilder b/app/views/api/pricing/index.json.jbuilder new file mode 100644 index 000000000..e27c5fa7e --- /dev/null +++ b/app/views/api/pricing/index.json.jbuilder @@ -0,0 +1,20 @@ +json.array!(@group_pricing) do |group| + json.extract! group, :id, :name + json.plans group.plans do |p| + json.partial! 'api/shared/plan', plan: p + end + json.trainings_pricings group.trainings_pricings do |p| + json.amount p.amount ? (p.amount / 100.0) : 0 + json.training_id p.training_id + end + json.machines_prices do + group.machines_prices.group_by(&:priceable_id).each do |machine_id, prices| + json.machine_id machine_id + json.not_subscribe_amount prices.find { |price| price.plan_id.nil? }.amount / 100.0 + json.amount_by_plan prices.select { |price| price.plan_id } do |price| + json.plan_id price.plan_id + json.amount price.amount / 100.0 + end + end + end +end diff --git a/app/views/api/projects/show.json.jbuilder b/app/views/api/projects/show.json.jbuilder index 692aabad5..495a9c42c 100644 --- a/app/views/api/projects/show.json.jbuilder +++ b/app/views/api/projects/show.json.jbuilder @@ -51,9 +51,12 @@ json.project_steps_attributes @project.project_steps.order('project_steps.create json.description s.description json.title s.title json.project_step_image s.project_step_image.attachment_identifier if s.project_step_image - json.project_step_image_url s.project_step_image.attachment_url if s.project_step_image + json.project_step_image_url s.project_step_image.attachment.medium.url if s.project_step_image end json.state @project.state json.licence do json.name @project.licence.name end if @project.licence.present? +#json.project_steps_attributes @project.project_steps do |s| + #json.set! s.id, {id: s.id, description: s.description} +#end diff --git a/app/views/api/reservations/_reservation.json.jbuilder b/app/views/api/reservations/_reservation.json.jbuilder new file mode 100644 index 000000000..fc724eb87 --- /dev/null +++ b/app/views/api/reservations/_reservation.json.jbuilder @@ -0,0 +1,14 @@ +json.id reservation.id +json.user_id reservation.user_id +json.user_full_name reservation.user.profile.full_name +json.message reservation.message +json.slots reservation.slots do |s| + json.id s.id + json.start_at s.start_at.iso8601 + json.end_at s.end_at.iso8601 +end +json.nb_reserve_places reservation.nb_reserve_places +json.nb_reserve_reduced_places reservation.nb_reserve_reduced_places +json.created_at reservation.created_at.iso8601 +json.reservable_id reservation.reservable_id +json.reservable_type reservation.reservable_type diff --git a/app/views/api/reservations/index.json.jbuilder b/app/views/api/reservations/index.json.jbuilder new file mode 100644 index 000000000..c3f05fa56 --- /dev/null +++ b/app/views/api/reservations/index.json.jbuilder @@ -0,0 +1,4 @@ +json.array!(@reservations) do |r| + json.partial! 'api/reservations/reservation', reservation: r + json.url reservation_url(r, format: :json) +end diff --git a/app/views/api/reservations/show.json.jbuilder b/app/views/api/reservations/show.json.jbuilder new file mode 100644 index 000000000..e3c9c832d --- /dev/null +++ b/app/views/api/reservations/show.json.jbuilder @@ -0,0 +1,29 @@ +json.id @reservation.id +json.user_id @reservation.user_id +json.user do + json.id @reservation.user.id + json.subscribed_plan do + json.partial! 'api/shared/plan', plan: @reservation.user.subscribed_plan + end if @reservation.user.subscribed_plan + json.training_credits @reservation.user.training_credits do |tc| + json.training_id tc.creditable_id + end + json.machine_credits @reservation.user.machine_credits do |mc| + json.machine_id mc.creditable_id + json.hours_used mc.users_credits.find_by_user_id(@reservation.user_id).hours_used + end +end +json.message @reservation.message +json.slots @reservation.slots do |s| + json.id s.id + json.start_at s.start_at.iso8601 + json.end_at s.end_at.iso8601 + json.is_reserved true +end +json.reservable do + json.id @reservation.reservable.id + json.name @reservation.reservable.name +end +json.nb_reserve_places @reservation.nb_reserve_places +json.nb_reserve_reduced_places @reservation.nb_reserve_reduced_places +json.created_at @reservation.created_at.iso8601 diff --git a/app/views/api/settings/_setting.json.jbuilder b/app/views/api/settings/_setting.json.jbuilder new file mode 100644 index 000000000..f98501c28 --- /dev/null +++ b/app/views/api/settings/_setting.json.jbuilder @@ -0,0 +1 @@ +json.extract! setting, :name, :value diff --git a/app/views/api/settings/index.json.jbuilder b/app/views/api/settings/index.json.jbuilder new file mode 100644 index 000000000..eef731fd6 --- /dev/null +++ b/app/views/api/settings/index.json.jbuilder @@ -0,0 +1,3 @@ +@settings.each do |setting| + json.set! setting.name, setting.value +end diff --git a/app/views/api/settings/show.json.jbuilder b/app/views/api/settings/show.json.jbuilder new file mode 100644 index 000000000..c0c489dbb --- /dev/null +++ b/app/views/api/settings/show.json.jbuilder @@ -0,0 +1,3 @@ +json.setting do + json.partial! 'api/settings/setting', setting: @setting +end diff --git a/app/views/api/settings/update.json.jbuilder b/app/views/api/settings/update.json.jbuilder new file mode 100644 index 000000000..c0c489dbb --- /dev/null +++ b/app/views/api/settings/update.json.jbuilder @@ -0,0 +1,3 @@ +json.setting do + json.partial! 'api/settings/setting', setting: @setting +end diff --git a/app/views/api/shared/_plan.json.jbuilder b/app/views/api/shared/_plan.json.jbuilder new file mode 100644 index 000000000..07159feea --- /dev/null +++ b/app/views/api/shared/_plan.json.jbuilder @@ -0,0 +1,14 @@ +json.id plan.id +json.base_name plan.base_name +json.name plan.name +json.amount plan.amount ? (plan.amount / 100.0) : 0 +json.interval plan.interval +json.interval_count plan.interval_count +json.training_credit_nb plan.training_credit_nb +json.training_credits plan.training_credits do |tc| + json.training_id tc.creditable_id +end +json.machine_credits plan.machine_credits do |mc| + json.machine_id mc.creditable_id + json.hours mc.hours +end diff --git a/app/views/api/slots/cancel.json.jbuilder b/app/views/api/slots/cancel.json.jbuilder new file mode 100644 index 000000000..de61e70c2 --- /dev/null +++ b/app/views/api/slots/cancel.json.jbuilder @@ -0,0 +1,2 @@ +json.id @slot.id +json.canceled_at @slot.canceled_at diff --git a/app/views/api/slots/show.json.jbuilder b/app/views/api/slots/show.json.jbuilder new file mode 100644 index 000000000..5937bfc73 --- /dev/null +++ b/app/views/api/slots/show.json.jbuilder @@ -0,0 +1 @@ +json.id @slot.id diff --git a/app/views/api/statistics/index.json.jbuilder b/app/views/api/statistics/index.json.jbuilder new file mode 100644 index 000000000..e5f78eedf --- /dev/null +++ b/app/views/api/statistics/index.json.jbuilder @@ -0,0 +1,16 @@ +json.array!(@statistics) do |s| + json.extract! s, :id, :es_type_key, :label, :table, :ca + json.additional_fields s.statistic_fields do |f| + json.extract! f, :key, :label, :data_type + end + json.types s.statistic_types do |t| + json.extract! t, :id, :key, :label, :graph, :simple + json.subtypes t.statistic_sub_types do |st| + json.extract! st, :id, :key, :label + end + end + json.graph do + json.chart_type s.statistic_graph.chart_type + json.limit s.statistic_graph.limit + end if s.statistic_graph +end \ No newline at end of file diff --git a/app/views/api/subscriptions/_subscription.json.jbuilder b/app/views/api/subscriptions/_subscription.json.jbuilder new file mode 100644 index 000000000..f2311a818 --- /dev/null +++ b/app/views/api/subscriptions/_subscription.json.jbuilder @@ -0,0 +1,7 @@ +json.extract! subscription, :id, :plan_id +json.expired_at subscription.expired_at.iso8601 +json.canceled_at subscription.canceled_at.iso8601 if subscription.canceled_at +json.stripe subscription.stp_subscription_id.present? +json.plan do + json.partial! 'api/shared/plan', plan: subscription.plan +end diff --git a/app/views/api/subscriptions/show.json.jbuilder b/app/views/api/subscriptions/show.json.jbuilder new file mode 100644 index 000000000..f48db8b44 --- /dev/null +++ b/app/views/api/subscriptions/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/subscriptions/subscription', subscription: @subscription diff --git a/app/views/api/subscriptions/update.json.jbuilder b/app/views/api/subscriptions/update.json.jbuilder new file mode 100644 index 000000000..f48db8b44 --- /dev/null +++ b/app/views/api/subscriptions/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/subscriptions/subscription', subscription: @subscription diff --git a/app/views/api/tags/index.json.jbuilder b/app/views/api/tags/index.json.jbuilder new file mode 100644 index 000000000..fe81d7ada --- /dev/null +++ b/app/views/api/tags/index.json.jbuilder @@ -0,0 +1,3 @@ +json.array!(@tags) do |tag| + json.extract! tag, :id, :name +end diff --git a/app/views/api/tags/show.json.jbuilder b/app/views/api/tags/show.json.jbuilder new file mode 100644 index 000000000..422c5f97d --- /dev/null +++ b/app/views/api/tags/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @tag, :id, :name \ No newline at end of file diff --git a/app/views/api/trainings/index.json.jbuilder b/app/views/api/trainings/index.json.jbuilder new file mode 100644 index 000000000..95f0b6e27 --- /dev/null +++ b/app/views/api/trainings/index.json.jbuilder @@ -0,0 +1,19 @@ +json.array!(@trainings) do |training| + json.id training.id + json.name training.name + json.description training.description + json.machine_ids training.machine_ids + json.availabilities training.availabilities do |a| + json.id a.id + json.start_at a.start_at.iso8601 + json.end_at a.end_at.iso8601 + json.reservation_users a.slots.map do |slot| + json.id slot.reservation.user.id + json.full_name slot.reservation.user.profile.full_name + json.is_valid slot.reservation.user.trainings.include?(training) + end + end + json.nb_total_places training.nb_total_places + + json.plan_ids training.plan_ids if current_user and current_user.is_admin? +end diff --git a/app/views/api/trainings/show.json.jbuilder b/app/views/api/trainings/show.json.jbuilder new file mode 100644 index 000000000..9e9991c63 --- /dev/null +++ b/app/views/api/trainings/show.json.jbuilder @@ -0,0 +1,11 @@ +json.extract! @training, :id, :name, :machine_ids, :nb_total_places +json.availabilities @training.availabilities.order('start_at DESC') do |a| + json.id a.id + json.start_at a.start_at.iso8601 + json.end_at a.end_at.iso8601 + json.reservation_users a.slots.map do |slot| + json.id slot.reservation.user.id + json.full_name slot.reservation.user.profile.full_name + json.is_valid slot.reservation.user.trainings.include?(training) + end +end diff --git a/app/views/api/trainings_pricings/_trainings_pricing.json.jbuilder b/app/views/api/trainings_pricings/_trainings_pricing.json.jbuilder new file mode 100644 index 000000000..221217ac2 --- /dev/null +++ b/app/views/api/trainings_pricings/_trainings_pricing.json.jbuilder @@ -0,0 +1,5 @@ +json.extract! trainings_pricing, :id, :group_id, :training_id +json.amount trainings_pricing.amount / 100.0 +json.training do + json.name trainings_pricing.training.name +end diff --git a/app/views/api/trainings_pricings/index.json.jbuilder b/app/views/api/trainings_pricings/index.json.jbuilder new file mode 100644 index 000000000..a091263a6 --- /dev/null +++ b/app/views/api/trainings_pricings/index.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/trainings_pricings/trainings_pricing', collection: @trainings_pricings, as: :trainings_pricing diff --git a/app/views/api/trainings_pricings/update.json.jbuilder b/app/views/api/trainings_pricings/update.json.jbuilder new file mode 100644 index 000000000..c9f02d23c --- /dev/null +++ b/app/views/api/trainings_pricings/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/trainings_pricings/trainings_pricing', trainings_pricing: @trainings_pricing diff --git a/app/views/api/users/create.json.jbuilder b/app/views/api/users/create.json.jbuilder new file mode 100644 index 000000000..be7c585e1 --- /dev/null +++ b/app/views/api/users/create.json.jbuilder @@ -0,0 +1,2 @@ + json.extract! @user, :id, :email, :first_name, :last_name + json.name "#{@user.first_name} #{@user.last_name}" diff --git a/app/views/api/users/index.json.jbuilder b/app/views/api/users/index.json.jbuilder new file mode 100644 index 000000000..76cb8c963 --- /dev/null +++ b/app/views/api/users/index.json.jbuilder @@ -0,0 +1,4 @@ +json.users @users do |user| + json.extract! user, :id, :email, :first_name, :last_name + json.name "#{user.first_name} #{user.last_name}" +end diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb index c32fb2f10..a8026f029 100644 --- a/app/views/application/index.html.erb +++ b/app/views/application/index.html.erb @@ -5,20 +5,60 @@ - + - Fab-Manager + <%=Setting.find_by(name: 'fablab_name').value%> - - - + <% if ENV['DEFAULT_HOST'] == 'fablab.lacasemate.fr' %> + + + <% else %> + + + + <% end %> + + + + <%= stylesheet_link_tag 'application', media: 'all' %> + <%= stylesheet_link_tag 'app.printer', media: 'print' %> + <% if !Stylesheet.first.nil? %> + + <% end %> - - + + @@ -29,34 +69,49 @@ -

    +
    -
    + <%= flash_messages %> - +
    -
    -
    + - +
    +
    + -
    -
    -
    +
    +
    +
    +
    -
    -
    -
    +
    +
    -
    +
    + +
    Powered by Fab Manager
    <%= javascript_include_tag 'application' %> + + + + +<% if Rails.application.secrets.moment_locale != "en" %> + +<% end %> +<% if Rails.application.secrets.summernote_locale != "en" %> + +<% end %> +<% if Rails.application.secrets.fullcalendar_locale != "en" %> + +<% end %> diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb index 4cd66e420..552e05628 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.erb +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -1,7 +1,5 @@ -

    Bonjour <%= @user.profile.full_name %> !

    +<%= render 'notifications_mailer/shared/hello', recipient: @user %> -

    Vous pouvez finaliser votre inscription en confirmant votre adresse mail en cliquant sur le lien suivant :

    +

    <%= t('.instruction') %>

    -

    <%= link_to 'Confirmer mon e-mail !', confirmation_url(@resource, confirmation_token: @token) %>

    - -

    L'équipe Fab Lab.

    \ No newline at end of file +

    <%= link_to t('.action'), confirmation_url(@resource, confirmation_token: @token) %>

    diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb index f2aa6655c..53d290869 100644 --- a/app/views/devise/mailer/reset_password_instructions.html.erb +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -1,7 +1,7 @@ -

    Bonjour,

    +<%= render 'notifications_mailer/shared/hello', recipient: @user %> -

    Quelqu'un a demandé un lien pour changer votre mot de passe. Vous pouvez le faire via le lien ci-dessous.

    +

    <%= t('.instruction')%>

    -

    <%= link_to 'Changer mon mot de passe', "#{root_url}#!/?reset_password_token=#{@token}" %>

    +

    <%= link_to t('.action'), "#{root_url}#!/?reset_password_token=#{@token}" %>

    -

    Si vous n'avez pas demandé cela, merci d'ignorer ce message.

    +

    <%= t('.ignore_otherwise') %>

    diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb deleted file mode 100644 index d298d8b02..000000000 --- a/app/views/devise/registrations/new.html.erb +++ /dev/null @@ -1,88 +0,0 @@ -
    -
    - <%= devise_error_messages! %> - <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => {:novalidate => ''}) do |f| %> - - <% resource.build_profile if resource.profile.nil? %> - -
    - <%= f.label :username %> - <%= f.text_field :username, autofocus: true, class: 'form-control' %> -
    - -
    - <%= f.label :email %> - <%= f.email_field :email, class: 'form-control' %> -
    - - <%= f.fields_for :profile do |p| %> -
    - <%= p.label :phone %> - <%= p.text_field :phone, class: 'form-control' %> -
    - -
    - <%= p.label :gender %> -
    - -
    -
    - -
    -
    - -
    - <%= p.label :last_name %> - <%= p.text_field :last_name, class: 'form-control' %> -
    - -
    - <%= p.label :first_name %> - <%= p.text_field :first_name, class: 'form-control' %> -
    - -
    - <%= p.label :birthday %> - <%= p.date_field :birthday, class: 'form-control' %> -
    - -
    - <%= p.label :interest %> - <%= p.text_area :interest, class: 'form-control' %> -
    - -
    - <%= p.label :software_mastered %> - <%= p.text_area :software_mastered, class: 'form-control' %> -
    - <% end %> - -
    - <%= f.label :password %> - <%= f.password_field :password, class: 'form-control' %> -
    - -
    - <%= f.label :password_confirmation %> - <%= f.password_field :password_confirmation, class: 'form-control' %> -
    - -
    - -
    - -
    - -
    - - <% end %> -
    -
    diff --git a/app/views/layouts/notifications_mailer.html.erb b/app/views/layouts/notifications_mailer.html.erb index 3a8b921e8..9779839d8 100644 --- a/app/views/layouts/notifications_mailer.html.erb +++ b/app/views/layouts/notifications_mailer.html.erb @@ -1,3 +1,6 @@ +<% fablab_name = Setting.find_by(name: 'fablab_name').value %> +<% primary_color = Stylesheet.primary %> + @@ -6,7 +9,7 @@ @@ -18,11 +21,15 @@ - + - +
    - Fab Lab + <% if CustomAsset.get_url('logo-file') %> + <%=fablab_name%> + <% else %> + <%=fablab_name%> + <% end %>
    <%= message.subject %><%= message.subject %>
    @@ -31,12 +38,15 @@
    -

    A très bientôt sur le Fab Lab.

    -

    Cordialement,
    L'équipe du Fab Lab.

    +

    + <%= _t(".see_you_later", {GENDER:Setting.find_by(name: 'name_genre').value}) + # messageFormat + %> <%=fablab_name%>.

    +

    <%= t(".sincerely") %>
    <%= t(".signature") %>

    Merci de ne pas répondre directement à cet email.<%= t('.do_not_reply') %>

     

    @@ -45,4 +55,4 @@ - \ No newline at end of file + diff --git a/app/views/notifications_mailer/notify_admin_abuse_reported.html.erb b/app/views/notifications_mailer/notify_admin_abuse_reported.html.erb new file mode 100644 index 000000000..1593a17d9 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_abuse_reported.html.erb @@ -0,0 +1,9 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.intro') %>

    +
      +
    • <%= t('.body.signaled_content') %> <%= link_to "#{@attached_object.signaled_type} #{@attached_object.signaled_id} (#{@attached_object.signaled.name if @attached_object.signaled.name})", "#{root_url}#!/projects/#{@attached_object.signaled_id}" %>
    • +
    • <%= t('.body.signaled_by') %> <%= @attached_object.first_name %> <%= @attached_object.last_name %> (<%= @attached_object.email %>)
    • +
    • <%= t('.body.signaled_on') %> <%= @attached_object.created_at.strftime('%e %B %Y, %H:%M') %>
    • +
    • <%= t('.body.message') %>
      <%= @attached_object.message %>
    • +
    diff --git a/app/views/notifications_mailer/notify_admin_invoicing_changed.html.erb b/app/views/notifications_mailer/notify_admin_invoicing_changed.html.erb new file mode 100644 index 000000000..b21bae087 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_invoicing_changed.html.erb @@ -0,0 +1,17 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    + <%= _t('.body.generation_status_html', + { + STATUS: bool_to_sym(@attached_object.invoicing_disabled), + NAME: @attached_object.profile.full_name + }) + # messageFormat + %> +

    + +<% if @attached_object.invoicing_disabled %> +

    <%= t('.body.disabled') %>

    +<% else %> +

    <%= t('.body.enabled') %>

    +<% end %> diff --git a/app/views/notifications_mailer/notify_admin_member_create_reservation.html.erb b/app/views/notifications_mailer/notify_admin_member_create_reservation.html.erb new file mode 100644 index 000000000..f16bc570c --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_member_create_reservation.html.erb @@ -0,0 +1,13 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    + <%= t('.body.member_reserved_html', + NAME: @attached_object.user.profile.full_name, + RESERVABLE: @attached_object.reservable.name) %> +

    +

    <%= t('.body.reserved_slots') %>

    +
      +<% @attached_object.slots.each do |slot| %> +
    • <%= "#{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" %>
    • +<% end %> +
    diff --git a/app/views/notifications_mailer/notify_admin_profile_complete.html.erb b/app/views/notifications_mailer/notify_admin_profile_complete.html.erb new file mode 100644 index 000000000..030b8cb17 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_profile_complete.html.erb @@ -0,0 +1,7 @@ +<% provider = AuthProvider.from_strategy_name(@attached_object.provider) %> +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.account_completed', PROVIDER: provider.name) %> + <%= "#{@attached_object.profile.full_name}" %> <<%= @attached_object.email%>>.

    + +

    <%= t('.body.provider_id', UID: @attached_object.uid) %>

    diff --git a/app/views/notifications_mailer/notify_admin_slot_is_canceled.html.erb b/app/views/notifications_mailer/notify_admin_slot_is_canceled.html.erb new file mode 100644 index 000000000..1ea94c689 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_slot_is_canceled.html.erb @@ -0,0 +1,9 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.member_cancelled', NAME: @attached_object.reservation.user.profile.full_name) %>

    +

    <%= t('.body.item_details', + START: I18n.l(@attached_object.start_at, format: :long), + END:(I18n.l @attached_object.end_at, format: :hour_minute), + RESERVABLE: @attached_object.reservation.reservable.name) %> +

    +

    <%= t('.body.generate_refund') %>

    diff --git a/app/views/notifications_mailer/notify_admin_slot_is_modified.html.erb b/app/views/notifications_mailer/notify_admin_slot_is_modified.html.erb new file mode 100644 index 000000000..709ade2d6 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_slot_is_modified.html.erb @@ -0,0 +1,5 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.slot_modified', NAME: @attached_object.reservation.user.profile.full_name) %>

    +

    <%= t('.body.new_date') %> <%= "#{I18n.l @attached_object.start_at, format: :long} - #{I18n.l @attached_object.end_at, format: :hour_minute}" %>

    +

    <%= t('.body.old_date') %> <%= "#{I18n.l @attached_object.ex_start_at, format: :long} - #{I18n.l @attached_object.ex_end_at, format: :hour_minute}" %>

    diff --git a/app/views/notifications_mailer/notify_admin_subscribed_plan.html.erb b/app/views/notifications_mailer/notify_admin_subscribed_plan.html.erb new file mode 100644 index 000000000..e2d99d1e7 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_subscribed_plan.html.erb @@ -0,0 +1,7 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.plan_subscribed_html', + PLAN: @attached_object.plan.human_readable_name, + NAME: @attached_object.user.profile.full_name) %> +

    + diff --git a/app/views/notifications_mailer/notify_admin_subscription_canceled.html.erb b/app/views/notifications_mailer/notify_admin_subscription_canceled.html.erb new file mode 100644 index 000000000..dae0143e0 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_subscription_canceled.html.erb @@ -0,0 +1,6 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.subscription_canceled_html', + PLAN: @attached_object.plan.human_readable_name, + NAME: @attached_object.user.profile.full_name) %> +

    diff --git a/app/views/notifications_mailer/notify_admin_subscription_extended.html.erb b/app/views/notifications_mailer/notify_admin_subscription_extended.html.erb new file mode 100644 index 000000000..46019d671 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_subscription_extended.html.erb @@ -0,0 +1,13 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    + <%= _t('.body.subscription_extended_html', + { + PLAN: @attached_object.plan.human_readable_name, + NAME: @attached_object.user.profile.full_name, + FREE: bool_to_sym(@notification.get_meta_data(:free_days)), + DATE: I18n.l(@attached_object.expired_at.to_date) + }) + # messageFormat + %> +

    diff --git a/app/views/notifications_mailer/notify_admin_subscription_is_expired.html.erb b/app/views/notifications_mailer/notify_admin_subscription_is_expired.html.erb new file mode 100644 index 000000000..bac56e6ed --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_subscription_is_expired.html.erb @@ -0,0 +1,6 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.subscription_expired_html', + NAME:@attached_object.user.profile.full_name, + PLAN: @attached_object.plan.human_readable_name) %> +

    diff --git a/app/views/notifications_mailer/notify_admin_subscription_will_expire_in_7_days.html.erb b/app/views/notifications_mailer/notify_admin_subscription_will_expire_in_7_days.html.erb new file mode 100644 index 000000000..aca550498 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_subscription_will_expire_in_7_days.html.erb @@ -0,0 +1,6 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.subscription_will_expire_html', + NAME:@attached_object.user.profile.full_name, + PLAN: @attached_object.plan.human_readable_name) %> +

    diff --git a/app/views/notifications_mailer/notify_admin_user_group_changed.html.erb b/app/views/notifications_mailer/notify_admin_user_group_changed.html.erb new file mode 100644 index 000000000..4aedec845 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_user_group_changed.html.erb @@ -0,0 +1,8 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.user_changed_group_html', NAME: @attached_object.profile.full_name) %>

    + +

    + <%= t('.body.previous_group') %> <%= "#{@notification.get_meta_data(:ex_group_name)}" %> +
    <%= t('.body.new_group') %> <%= "#{@attached_object.group.name}" %> +

    diff --git a/app/views/notifications_mailer/notify_admin_user_merged.html.erb b/app/views/notifications_mailer/notify_admin_user_merged.html.erb new file mode 100644 index 000000000..1fd3ef247 --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_user_merged.html.erb @@ -0,0 +1,10 @@ +<% provider = AuthProvider.from_strategy_name(@attached_object.provider) %> +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.imported_account_merged', + PROVIDER: provider.name, + NAME: @attached_object.profile.full_name) %> + <<%= @attached_object.email%>>. +

    + +

    <%= t('.body.provider_uid', UID: @attached_object.uid) %>

    diff --git a/app/views/notifications_mailer/notify_admin_when_project_published.html.erb b/app/views/notifications_mailer/notify_admin_when_project_published.html.erb index b596e2cec..0c3486e53 100644 --- a/app/views/notifications_mailer/notify_admin_when_project_published.html.erb +++ b/app/views/notifications_mailer/notify_admin_when_project_published.html.erb @@ -1,3 +1,3 @@ -

    Bonjour,

    +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> -

    Un nouveau projet vient d'être publié : "<%= link_to @attached_object.name, "#{root_url}#!/projects/#{@attached_object.id}" %>"

    +

    <%= t('.body.new_project_published') %> "<%= link_to @attached_object.name, "#{root_url}#!/projects/#{@attached_object.id}" %>"

    diff --git a/app/views/notifications_mailer/notify_admin_when_user_is_created.html.erb b/app/views/notifications_mailer/notify_admin_when_user_is_created.html.erb index 130bb6a0b..cadac8f8c 100644 --- a/app/views/notifications_mailer/notify_admin_when_user_is_created.html.erb +++ b/app/views/notifications_mailer/notify_admin_when_user_is_created.html.erb @@ -1,3 +1,7 @@ -

    Bonjour,

    +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> -

    Un nouveau compte utilisateur vient d'être créé sur la plateforme : "<%= @attached_object.profile.full_name %> <<%= @attached_object.email%>>"

    +

    <%= t('.body.new_account_created') %> "<%= @attached_object.profile.full_name %> <<%= @attached_object.email%>>"

    + +<% if @attached_object.invoicing_disabled? %> +

    <%= t('.body.invoicing_disabled_html') %>

    +<% end %> \ No newline at end of file diff --git a/app/views/notifications_mailer/notify_admin_when_user_is_imported.html.erb b/app/views/notifications_mailer/notify_admin_when_user_is_imported.html.erb new file mode 100644 index 000000000..74e192d4c --- /dev/null +++ b/app/views/notifications_mailer/notify_admin_when_user_is_imported.html.erb @@ -0,0 +1,22 @@ +<% provider = AuthProvider.from_strategy_name(@attached_object.provider) %> +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.new_account_imported', ID: @attached_object.id, PROVIDER: provider.name) %>
    + <%= t('.body.provider_uid', UID:@attached_object.uid) %>

    +<% if provider.sso_fields.size > 1 %> +

    <%= t('.body.known_informations') %>

    +
      + <% for field in provider.sso_fields %> + <% value = @attached_object.get_data_from_sso_mapping(field) %> + <%if field == 'user.email' and value.end_with? '-duplicate' %> +
    • <%= field %> : <%= value.match(/^<([^>]+)>.{20}-duplicate$/)[1] %> + - <%= t('.body.address_already_used') %> +
    • + <% else %> +
    • <%= field %> : <%= value %>
    • + <% end %> + <% end %> +
    +<% end %> + +

    <%= t('.body.no_more_info_available') %>

    diff --git a/app/views/notifications_mailer/notify_member_avoir_ready.html.erb b/app/views/notifications_mailer/notify_member_avoir_ready.html.erb new file mode 100644 index 000000000..9da742e51 --- /dev/null +++ b/app/views/notifications_mailer/notify_member_avoir_ready.html.erb @@ -0,0 +1,20 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    + <%= _t('.body.please_find_attached_html', + { + DATE: I18n.l(@attached_object.avoir_date.to_date), + AMOUNT: number_to_currency(@attached_object.total / 100.0), + TYPE: @attached_object.invoiced_type + }) + # messageFormat + %> +

    + +

    <%= t('.body.invoice_in_your_dashboard_html', + DASHBOARD: link_to( + t('.body.your_dashboard'), + "#{root_url}#!/dashboard/invoices" + ) + ) %> +

    diff --git a/app/views/notifications_mailer/notify_member_create_reservation.html.erb b/app/views/notifications_mailer/notify_member_create_reservation.html.erb new file mode 100644 index 000000000..793fc5a37 --- /dev/null +++ b/app/views/notifications_mailer/notify_member_create_reservation.html.erb @@ -0,0 +1,10 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.reservation_saved_html', RESERVATION: @attached_object.reservable.name) %>

    + +

    <%= t('.body.your_reserved_slots') %>

    +
      +<% @attached_object.slots.each do |slot| %> +
    • <%= "#{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" %>
    • +<% end %> +
    diff --git a/app/views/notifications_mailer/notify_member_invoice_ready.html.erb b/app/views/notifications_mailer/notify_member_invoice_ready.html.erb new file mode 100644 index 000000000..c41a09d4f --- /dev/null +++ b/app/views/notifications_mailer/notify_member_invoice_ready.html.erb @@ -0,0 +1,20 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    + <%= _t('.body.please_find_attached_html', + { + DATE: I18n.l(@attached_object.created_at.to_date), + AMOUNT: number_to_currency(@attached_object.total / 100.0), + TYPE: @attached_object.invoiced_type + }) + # messageFormat + %> +

    + +

    <%= t('.body.invoice_in_your_dashboard_html', + DASHBOARD: link_to( + t('.body.your_dashboard'), + "#{root_url}#!/dashboard/invoices" + ) + ) %> +

    diff --git a/app/views/notifications_mailer/notify_member_slot_is_canceled.html.erb b/app/views/notifications_mailer/notify_member_slot_is_canceled.html.erb new file mode 100644 index 000000000..2170f508e --- /dev/null +++ b/app/views/notifications_mailer/notify_member_slot_is_canceled.html.erb @@ -0,0 +1,4 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.reservation_canceled', RESERVABLE: @attached_object.reservation.reservable.name ) %>

    +

    <%= "#{I18n.l @attached_object.start_at, format: :long} - #{I18n.l @attached_object.end_at, format: :hour_minute}" %>

    diff --git a/app/views/notifications_mailer/notify_member_slot_is_modified.html.erb b/app/views/notifications_mailer/notify_member_slot_is_modified.html.erb new file mode 100644 index 000000000..6e055aecc --- /dev/null +++ b/app/views/notifications_mailer/notify_member_slot_is_modified.html.erb @@ -0,0 +1,5 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.reservation_changed_to') %>

    +

    <%= "#{I18n.l @attached_object.start_at, format: :long} - #{I18n.l @attached_object.end_at, format: :hour_minute}" %>

    +

    <%= t('.body.previous_date') %> <%= "#{I18n.l @attached_object.ex_start_at, format: :long} - #{I18n.l @attached_object.ex_end_at, format: :hour_minute}" %>

    diff --git a/app/views/notifications_mailer/notify_member_subscribed_plan.html.erb b/app/views/notifications_mailer/notify_member_subscribed_plan.html.erb new file mode 100644 index 000000000..327a0e9f2 --- /dev/null +++ b/app/views/notifications_mailer/notify_member_subscribed_plan.html.erb @@ -0,0 +1,5 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.plan_subscribed_html', PLAN: @attached_object.plan.human_readable_name) %>

    + +

    <%= t('.body.subscription_stops_on', DATE: I18n.l(@attached_object.expired_at.to_date)) %>

    diff --git a/app/views/notifications_mailer/notify_member_subscribed_plan_is_changed.html.erb b/app/views/notifications_mailer/notify_member_subscribed_plan_is_changed.html.erb new file mode 100644 index 000000000..b106e53a8 --- /dev/null +++ b/app/views/notifications_mailer/notify_member_subscribed_plan_is_changed.html.erb @@ -0,0 +1,3 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.new_plan_html', PLAN: @attached_object.plan.human_readable_name) %>

    diff --git a/app/views/notifications_mailer/notify_member_subscription_canceled.html.erb b/app/views/notifications_mailer/notify_member_subscription_canceled.html.erb new file mode 100644 index 000000000..d3ad885b5 --- /dev/null +++ b/app/views/notifications_mailer/notify_member_subscription_canceled.html.erb @@ -0,0 +1,3 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.your_plan_was_canceled') %>. (<%= t('.body.your_plan') %> <%= @attached_object.plan.human_readable_name %> <%= t('.body.end_at') %> <%= l @attached_object.expired_at %>.)

    diff --git a/app/views/notifications_mailer/notify_member_subscription_extended.html.erb b/app/views/notifications_mailer/notify_member_subscription_extended.html.erb new file mode 100644 index 000000000..092a1d563 --- /dev/null +++ b/app/views/notifications_mailer/notify_member_subscription_extended.html.erb @@ -0,0 +1,3 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.your_plan') %> <%= @attached_object.plan.human_readable_name %> <%= t('.body.has_been_extended') %> <%= "#{@notification.get_meta_data(:free_days) ? t('.body.free')+' ' : ''}" %><%= t('.body.until') %> <%= l @attached_object.expired_at.to_date %>.

    diff --git a/app/views/notifications_mailer/notify_member_subscription_is_expired.html.erb b/app/views/notifications_mailer/notify_member_subscription_is_expired.html.erb new file mode 100644 index 000000000..0860b1af6 --- /dev/null +++ b/app/views/notifications_mailer/notify_member_subscription_is_expired.html.erb @@ -0,0 +1,5 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.your_plan') %> <%= @attached_object.plan.human_readable_name %> <%= t('.body.has_expired') %>.

    + +

    <%= t('.body.you_can_go_to') %> <%= link_to "Abonnements", "#{root_url}#!/pricing" %>, <%= t('.body.to_renew_your_plan') %>.

    diff --git a/app/views/notifications_mailer/notify_member_subscription_will_expire_in_7_days.html.erb b/app/views/notifications_mailer/notify_member_subscription_will_expire_in_7_days.html.erb new file mode 100644 index 000000000..22d21474e --- /dev/null +++ b/app/views/notifications_mailer/notify_member_subscription_will_expire_in_7_days.html.erb @@ -0,0 +1,6 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.your_plan') %> <%= @attached_object.plan.human_readable_name %> <%= t('.body.expires_in_7_days') %>.

    +<% if @attached_object.stp_subscription_id %> +

    <%= t('.body.to_renew_your_plan_follow_the_link') %> <%= link_to "votre page de profil", "#{root_url}#!/dashboard/profile" %>

    +<% end %> diff --git a/app/views/notifications_mailer/notify_partner_subscribed_plan.html.erb b/app/views/notifications_mailer/notify_partner_subscribed_plan.html.erb new file mode 100644 index 000000000..bb7cb9279 --- /dev/null +++ b/app/views/notifications_mailer/notify_partner_subscribed_plan.html.erb @@ -0,0 +1,3 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t('.body.a_plan') %> <%= @attached_object.plan.human_readable_name %> <%= t('.body.was_purchased_by_member') %> <%= @attached_object.user.profile.full_name %>.

    diff --git a/app/views/notifications_mailer/notify_project_author_when_collaborator_valid.html.erb b/app/views/notifications_mailer/notify_project_author_when_collaborator_valid.html.erb index 24c721645..c5141f2fc 100644 --- a/app/views/notifications_mailer/notify_project_author_when_collaborator_valid.html.erb +++ b/app/views/notifications_mailer/notify_project_author_when_collaborator_valid.html.erb @@ -1,3 +1,3 @@ -

    Bonjour <%= @attached_object.project.author.profile.full_name %>,

    +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> -

    Le membre <%= @attached_object.user.profile.full_name %> a accepté votre demande de collaboration sur votre projet : <%= link_to @attached_object.project.name, "#{root_url}#!/projects/#{@attached_object.project.id}" %>.

    +

    <%= t('.body.the_member') %> <%= @attached_object.user.profile.full_name %> <%= t(".body.accepted_your_invitation_to_take_part_in_the_project" ) %> <%= link_to @attached_object.project.name, "#{root_url}#!/projects/#{@attached_object.project.id}" %>.

    diff --git a/app/views/notifications_mailer/notify_project_collaborator_to_valid.html.erb b/app/views/notifications_mailer/notify_project_collaborator_to_valid.html.erb index 30314a2e1..1bb99907c 100644 --- a/app/views/notifications_mailer/notify_project_collaborator_to_valid.html.erb +++ b/app/views/notifications_mailer/notify_project_collaborator_to_valid.html.erb @@ -1,5 +1,5 @@ -

    Bonjour <%= @attached_object.user.profile.full_name %>,

    +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> -

    Vous êtes invité à collaborer sur ce projet : <%= link_to @attached_object.project.name, "#{root_url}#!/projects/#{@attached_object.project.id}" %>.

    +

    <%= t(".body.your_are_invited_to_take_part_in_a_project") %> <%= link_to @attached_object.project.name, "#{root_url}#!/projects/#{@attached_object.project.id}" %>.

    -

    Pour accepter cette invitation, il vous suffit de cliquer sur le lien suivant: <%= link_to "#{root_url}project_collaborator/#{@attached_object.valid_token}", "#{root_url}project_collaborator/#{@attached_object.valid_token}" %>

    +

    <%= t(".body.to_accept_the_invitation_click_on_following_link") %> <%= link_to "#{root_url}project_collaborator/#{@attached_object.valid_token}", "#{root_url}project_collaborator/#{@attached_object.valid_token}" %>

    diff --git a/app/views/notifications_mailer/notify_user_auth_migration.html.erb b/app/views/notifications_mailer/notify_user_auth_migration.html.erb new file mode 100644 index 000000000..937872e62 --- /dev/null +++ b/app/views/notifications_mailer/notify_user_auth_migration.html.erb @@ -0,0 +1,29 @@ + + +<% active_provider = AuthProvider.active %> +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t(".body.the_platform") %> <%= Setting.find_by(name: 'fablab_name').value %> <%= t(".body.is_changing_its_auth_system_and_will_now_use") %> <%= active_provider.name %> <%= t(".body.instead_of") %> <%= AuthProvider.find_by(status: 'previous').name %>.

    + +

    <%= t('.body.consequence_of_the_modification') %>

    + +

    <%= t('.body.to_use_the_platform_thanks_for') %> + <%= t('.body.create_an_account_on') %> <%= active_provider.name %> <%= t('.body.or_use_an_existing_account_clicking_here') %>

    + +

    <%= t('.body.in_case_of_problem_enter_the_following_code') %>

    + +
    + <%= @attached_object.auth_token %> +
    diff --git a/app/views/notifications_mailer/notify_user_profile_complete.html.erb b/app/views/notifications_mailer/notify_user_profile_complete.html.erb new file mode 100644 index 000000000..0f6a7ed63 --- /dev/null +++ b/app/views/notifications_mailer/notify_user_profile_complete.html.erb @@ -0,0 +1,3 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t(".body.message") %>

    diff --git a/app/views/notifications_mailer/notify_user_training_valid.html.erb b/app/views/notifications_mailer/notify_user_training_valid.html.erb new file mode 100644 index 000000000..88db0c905 --- /dev/null +++ b/app/views/notifications_mailer/notify_user_training_valid.html.erb @@ -0,0 +1,3 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t(".body.your_training") %> <%= @attached_object.training.name %> <%= t(".body.has_been_validated") %>.

    diff --git a/app/views/notifications_mailer/notify_user_user_group_changed.html.erb b/app/views/notifications_mailer/notify_user_user_group_changed.html.erb new file mode 100644 index 000000000..accf48e74 --- /dev/null +++ b/app/views/notifications_mailer/notify_user_user_group_changed.html.erb @@ -0,0 +1,3 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

    <%= t(".body.warning") %>

    diff --git a/app/views/notifications_mailer/shared/_hello.html.erb b/app/views/notifications_mailer/shared/_hello.html.erb new file mode 100644 index 000000000..dc55acf4a --- /dev/null +++ b/app/views/notifications_mailer/shared/_hello.html.erb @@ -0,0 +1 @@ +

    <%= t "notifications_mailer.shared.hello", user_name: recipient.profile.full_name %>,

    diff --git a/app/views/users_mailer/notify_member_account_is_created.html.erb b/app/views/users_mailer/notify_member_account_is_created.html.erb deleted file mode 100644 index 12c85a509..000000000 --- a/app/views/users_mailer/notify_member_account_is_created.html.erb +++ /dev/null @@ -1,10 +0,0 @@ -

    Bonjour <%= @user.profile.full_name %>,

    - -

    L’équipe du Fab Lab vient de vous créer un compte sur <%= link_to 'le site du Fab lab', root_url %>
    Voici vos paramètres de connexion :

    - -

    Nom de compte : <%= @user.email %>

    -

    Mot de passe : <%= @generated_password %>

    - -

    Ce mot de passe est temporaire, vous pourrez le modifier en accédant à l’espace « Mon compte ». Avec ce compte, vous conservez bien entendu tous les avantages liés à votre profil utilisateur Fab Lab (abonnement, formations).

    - -

    L'équipe Fab Lab.

    \ No newline at end of file diff --git a/app/views/users_mailer/notify_user_account_created.html.erb b/app/views/users_mailer/notify_user_account_created.html.erb new file mode 100644 index 000000000..b0377fc14 --- /dev/null +++ b/app/views/users_mailer/notify_user_account_created.html.erb @@ -0,0 +1,51 @@ + + + +

    <%= t('.body.hello', NAME: @user.profile.full_name) %>

    + +

    <%= _t('.body.intro', + { + GENDER: Setting.find_by(name: 'name_genre').value, + FABLAB: Setting.find_by(name: 'fablab_name').value + }) + # messageFormat + %> <%= link_to URI(root_url).host, root_url %>. +

    + +<% active_provider = AuthProvider.active %> +<% if active_provider.providable_type == DatabaseProvider.name %> + +

    <%= t('.body.connection_parameters') %>

    + +

    <%= t('.body.account_name') %> <%= @user.email %>

    +

    <%= t('.body.password') %> <%= @generated_password %>

    + +

    <%= t('.body.temporary_password') %> <%= t('.body.keep_advantages') %>

    + +<% else %> + +

    + <%= t('.body.thanks_to_') %> + + <%= t('body.logon_or_login', PROVIDER: active_provider.name )%> + +

    + +

    <%= t('.body.token_if_link_problem') %>

    + +
    + <%= @user.auth_token %> +
    +<% end %> diff --git a/app/workers/indexer_worker.rb b/app/workers/indexer_worker.rb new file mode 100644 index 000000000..e08e21d3a --- /dev/null +++ b/app/workers/indexer_worker.rb @@ -0,0 +1,21 @@ +class IndexerWorker + include Sidekiq::Worker + sidekiq_options queue: 'elasticsearch', retry: true + + Logger = Sidekiq.logger.level == Logger::DEBUG ? Sidekiq.logger : nil + Client = Elasticsearch::Model.client + + def perform(operation, record_id) + logger.debug [operation, "ID: #{record_id}"] + + case operation.to_s + when /index/ + record = Project.find(record_id) + Client.index index: Project.index_name, type: Project.document_type, id: record.id, body: record.as_indexed_json + #puts record.as_indexed_json + when /delete/ + Client.delete index: 'fablab', type: 'projects', id: record_id + else raise ArgumentError, "Unknown operation '#{operation}'" + end + end +end diff --git a/app/workers/invoice_worker.rb b/app/workers/invoice_worker.rb new file mode 100644 index 000000000..fa8c7ce23 --- /dev/null +++ b/app/workers/invoice_worker.rb @@ -0,0 +1,24 @@ +class InvoiceWorker + include Sidekiq::Worker + + def perform(invoice_id) + # generate a invoice + invoice = Invoice.find invoice_id + pdf = ::PDF::Invoice.new(invoice).render + + # store invoice on drive + File.binwrite(invoice.file, pdf) + + # notify user + send invoice by mail + if invoice.is_a?(Avoir) + NotificationCenter.call type: 'notify_user_when_avoir_ready', + receiver: invoice.user, + attached_object: invoice + else + NotificationCenter.call type: 'notify_user_when_invoice_ready', + receiver: invoice.user, + attached_object: invoice + end + end + +end diff --git a/app/workers/statistic_worker.rb b/app/workers/statistic_worker.rb new file mode 100644 index 000000000..f075aa6c1 --- /dev/null +++ b/app/workers/statistic_worker.rb @@ -0,0 +1,7 @@ +class StatisticWorker + include Sidekiq::Worker + + def perform + StatisticService.new.generate_statistic + end +end diff --git a/app/workers/stripe_worker.rb b/app/workers/stripe_worker.rb new file mode 100644 index 000000000..feff2281e --- /dev/null +++ b/app/workers/stripe_worker.rb @@ -0,0 +1,17 @@ +class StripeWorker + include Sidekiq::Worker + sidekiq_options :queue => :stripe + + def perform(action, *params) + send(action, *params) + end + + def create_stripe_customer(user_id) + user = User.find(user_id) + customer = Stripe::Customer.create( + description: user.profile.full_name, + email: user.email + ) + user.update_columns(stp_customer_id: customer.id) + end +end diff --git a/app/workers/subscription_expire_worker.rb b/app/workers/subscription_expire_worker.rb new file mode 100644 index 000000000..32e8866c8 --- /dev/null +++ b/app/workers/subscription_expire_worker.rb @@ -0,0 +1,25 @@ +class SubscriptionExpireWorker + include Sidekiq::Worker + + def perform(expire_in) + Subscription.where('expired_at >= ?', Time.now.at_beginning_of_day).each do |s| + if (s.expired_at - expire_in.days).to_date == Time.now.to_date + if expire_in != 0 + NotificationCenter.call type: 'notify_member_subscription_will_expire_in_7_days', + receiver: s.user, + attached_object: s + NotificationCenter.call type: 'notify_admin_subscription_will_expire_in_7_days', + receiver: User.admins, + attached_object: s + else + NotificationCenter.call type: 'notify_member_subscription_is_expired', + receiver: s.user, + attached_object: s + NotificationCenter.call type: 'notify_admin_subscription_is_expired', + receiver: User.admins, + attached_object: s + end + end + end + end +end diff --git a/bin/rails b/bin/rails index 4d608edeb..7feb6a30e 100755 --- a/bin/rails +++ b/bin/rails @@ -3,6 +3,6 @@ begin load File.expand_path("../spring", __FILE__) rescue LoadError end -APP_PATH = File.expand_path('../../config/application', __FILE__) +APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands' diff --git a/bin/setup b/bin/setup deleted file mode 100755 index acdb2c138..000000000 --- a/bin/setup +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env ruby -require 'pathname' - -# path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) - -Dir.chdir APP_ROOT do - # This script is a starting point to setup your application. - # Add necessary setup steps to this file: - - puts "== Installing dependencies ==" - system "gem install bundler --conservative" - system "bundle check || bundle install" - - # puts "\n== Copying sample files ==" - # unless File.exist?("config/database.yml") - # system "cp config/database.yml.sample config/database.yml" - # end - - puts "\n== Preparing database ==" - system "bin/rake db:setup" - - puts "\n== Removing old logs and tempfiles ==" - system "rm -f log/*" - system "rm -rf tmp/cache" - - puts "\n== Restarting application server ==" - system "touch tmp/restart.txt" -end diff --git a/bin/spring b/bin/spring index 7b45d374f..e09182495 100755 --- a/bin/spring +++ b/bin/spring @@ -8,7 +8,7 @@ unless defined?(Spring) require "bundler" if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) - Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq } + Gem.paths = { "GEM_PATH" => Bundler.bundle_path.to_s } gem "spring", match[1] require "spring/binstub" end diff --git a/bower.json b/bower.json index ffe018659..9aeb8c634 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "fabmanager", - "version": "1.0.0", + "version": "0.0.0", "authors": [ "sleede " ], @@ -15,14 +15,15 @@ "tests" ], "dependencies": { - "angular-bootstrap": ">=0.11", - "angular-ui-router": ">=0.2.10", - "angular-ui-select2": ">=0.0.5", - "angular-moment": ">=0.7.0", + "angular-bootstrap": ">=0.13.1", + "angular-ui-router": ">=0.2.15", + "fullcalendar": "=2.3.1", + "angular-ui-calendar": "0.9.0-beta.1", + "moment": "=2.10.6", + "angular-moment": ">=0.10.3", "ngUpload": ">=0.5.11", "jasny-bootstrap": ">=3.1.3", "holderjs": "~2.6.0", - "angular-growl": ">=0.4.0", "angular-xeditable": ">=0.1.8", "checklist-model": ">=0.1.3", "angular-unsavedChanges": "~0.2.0", @@ -31,12 +32,31 @@ "angular-google-analytics": "~0.0.3", "underscore": "~1.7.0", "angular-devise": "~1.0.2", - "angular-i18n": "~1.3.15", - "summernote": "~0.6.6", - "angular-summernote": "~0.3.2" + "elasticsearch": "~3.1.1", + "nvd3": "~1.8.1", + "angular-i18n": "=1.3.15", + "angular-medium-editor": "~0.1.0", + "angular-bootstrap-switch": "~0.4.0", + "angular-base64-upload": "~0.0.8", + "summernote": "=0.7.3", + "angular-summernote": "~0.7.1", + "angular-minicolors": "~0.0.5", + "angular-ui-select": "~0.13.2", + "angular-growl-v2": "~0.7.9", + "angular-cookies": "1.3.20", + "angular-resource": "1.3.20", + "angular-sanitize": "1.3.20", + "angular-animate": "1.3.20", + "angular-touch": "1.3.20", + "angular-translate": "~2.8.1", + "angular-translate-loader-partial": "~2.8.1", + "angular-translate-interpolation-messageformat": "~2.8.1", + "messageformat": "=0.1.8", + "moment-timezone": "~0.5.0" }, "resolutions": { "jquery": ">=1.10.2", - "angular": "~1.2.16" + "angular": "1.3.20", + "messageformat": "=0.1.8" } } diff --git a/config.ru b/config.ru index bd83b2541..5bc2a619e 100644 --- a/config.ru +++ b/config.ru @@ -1,4 +1,4 @@ # This file is used by Rack-based servers to start the application. -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) run Rails.application diff --git a/config/application.rb b/config/application.rb index 6c1a4b534..8bc56fb5a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -10,6 +10,8 @@ require File.expand_path('../boot', __FILE__) #require "rails/test_unit/railtie" require 'csv' require "rails/all" +require 'elasticsearch/rails/instrumentation' +require 'elasticsearch/persistence/model' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. @@ -23,11 +25,15 @@ module Fablab # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - config.time_zone = 'Paris' + config.time_zone = Rails.application.secrets.time_zone # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] - config.i18n.default_locale = :fr + # + # /!\ ALL locales above SHOULD be configured accordingly with this locale. /!\ + # + config.i18n.default_locale = Rails.application.secrets.rails_locale + config.assets.paths << Rails.root.join('vendor', 'assets', 'components').to_s @@ -49,5 +55,12 @@ module Fablab config.generators do |g| g.orm :active_record end + + if Rails.env.development? + config.web_console.whitelisted_ips << '192.168.0.0/16' + config.web_console.whitelisted_ips << '192.168.99.0/16' #docker + config.web_console.whitelisted_ips << '10.0.2.2' #vagrant + end + end end diff --git a/config/application.yml.default b/config/application.yml.default new file mode 100644 index 000000000..1281d4e80 --- /dev/null +++ b/config/application.yml.default @@ -0,0 +1,48 @@ +# Add application configuration variables here, as shown below. + +POSTGRES_HOST: localhost +POSTGRES_PASSWORD: +REDIS_HOST: localhost +ELASTICSEARCH_HOST: localhost + +SECRET_KEY_BASE: 83daf5e7b80d990f037407bab78dff9904aaf3c195a50f84fa8695a22287e707dfbd9524b403b1dcf116ae1d8c06844c3d7ed942564e5b46be6ae3ead93a9d30 +STRIPE_API_KEY: +STRIPE_PUBLISHABLE_KEY: +STRIPE_CURRENCY: 'eur' + +INVOICE_PREFIX: Demo-FabLab-facture +FABLAB_WITHOUT_PLANS: 'false' + +DEFAULT_MAIL_FROM: Fab Manager Demo + +# For prod & staging env only +DEFAULT_HOST: fab-manager.com +DEFAULT_PROTOCOL: https +DELIVERY_METHOD: smtp +SMTP_ADDRESS: +SMTP_PORT: 587 +SMTP_USER_NAME: +SMTP_PASSWORD: +GA_ID: '' +## + +DISQUS_SHORTNAME: + +TWITTER_NAME: 'FablabGrenoble' +TWITTER_CONSUMER_KEY: '' +TWITTER_CONSUMER_SECRET: '' +TWITTER_ACCESS_TOKEN: '' +TWITTER_ACCESS_TOKEN_SECRET: '' + +RAILS_LOCALE: 'fr' +MOMENT_LOCALE: 'fr' +SUMMERNOTE_LOCALE: 'fr-FR' +ANGULAR_LOCALE: 'fr-fr' +MESSAGEFORMAT_LOCALE: 'fr' +FULLCALENDAR_LOCALE: 'fr' + +ELASTICSEARCH_LANGUAGE_ANALYZER: 'french' + +TIME_ZONE: 'Paris' +WEEK_STARTING_DAY: 'monday' +D3_DATE_FORMAT: '%d/%m/%y' diff --git a/config/database.yml.default b/config/database.yml.default index def3dad9f..af626499b 100644 --- a/config/database.yml.default +++ b/config/database.yml.default @@ -1,81 +1,39 @@ - -default: &default - adapter: postgresql - encoding: unicode - # For details on connection pooling, see rails configuration guide - # http://guides.rubyonrails.org/configuring.html#database-pooling - pool: 25 - development: - <<: *default - database: fabmanager_development - - # The specified database role being used to connect to postgres. - # To create additional roles in postgres see `$ createuser --help`. - # When left blank, postgres will use the default role. This is - # the same name as the operating system user that initialized the database. - username: sleede - - # The password associated with the postgres role (username). - password: sleede - - # Connect on a TCP socket. Omitted by default since the client uses a - # domain socket that doesn't need configuration. Windows does not have - # domain sockets, so uncomment these lines. + adapter: postgresql host: localhost - - # The TCP port the server listens on. Defaults to 5432. - # If your server runs on a different port number, change accordingly. - port: 5432 - - # Schema search path. The server defaults to $user,public - #schema_search_path: myapp,sharedapp,public - - # Minimum log levels, in increasing order: - # debug5, debug4, debug3, debug2, debug1, - # log, notice, warning, error, fatal, and panic - # Defaults to warning. - #min_messages: notice + encoding: unicode + database: fablab_development + pool: 25 + username: sleede + password: sleede # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - <<: *default - database: fabmanager_test - -# As with config/secrets.yml, you never want to store sensitive information, -# like your database password, in your source code. If your source code is -# ever seen by anyone, they now have access to your database. -# -# Instead, provide the password as a unix environment variable when you boot -# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database -# for a full rundown on how to provide these environment variables in a -# production deployment. -# -# On Heroku and other platform providers, you may have a full connection URL -# available as an environment variable. For example: -# -# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" -# -# You can use this database configuration with: -# -# production: -# url: <%= ENV['DATABASE_URL'] %> -# + adapter: postgresql + host: localhost + encoding: unicode + database: fablab_test + pool: 25 + username: sleede + password: sleede staging: - <<: *default - database: fabmanager_staging - username: <%= ENV['DATABASE_USERNAME'] %> - password: <%= ENV['DATABASE_PASSWORD'] %> - port: <%= ENV['DATABASE_PORT'] %> + adapter: postgresql + host: localhost + encoding: unicode + database: fablab_development + pool: 25 + username: sleede + password: sleede production: - <<: *default - database: fabmanager_production - username: <%= ENV['DATABASE_USERNAME'] %> - password: <%= ENV['DATABASE_PASSWORD'] %> - port: <%= ENV['DATABASE_PORT'] %> - + adapter: postgresql + host: localhost + encoding: unicode + database: fablab_development + pool: 25 + username: sleede + password: sleede diff --git a/config/deploy.rb b/config/deploy.rb index 3c9cb1753..00b46b0a9 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -1,6 +1,7 @@ require "bundler/capistrano" require "rvm/capistrano" require 'capistrano/ext/multistage' +require 'capistrano/maintenance' set :stages, %w(production staging) set :default_stage, "staging" @@ -11,6 +12,8 @@ ssh_options[:forward_agent] = true after "deploy", "deploy:cleanup" # keep only the last 5 releases +# after "deploy:finalize_update", "deploy:assets:precompile" + namespace :deploy do %w[start stop restart].each do |command| desc "#{command} unicorn server" @@ -38,8 +41,9 @@ namespace :deploy do sudo "ln -nfs #{current_path}/config/unicorn_init.sh /etc/init.d/unicorn_#{application}" run "mkdir -p #{shared_path}/config" run "mkdir -p #{shared_path}/uploads" - put File.read("config/database.yml.default"), "#{shared_path}/config/database.yml" - puts "Now edit #{shared_path}/config/database.yml" + run "mkdir -p #{shared_path}/invoices" + put File.read("config/database.yml"), "#{shared_path}/config/database.yml" + puts "Now edit #{shared_path}/config/database.yml and add your username and password" put File.read("config/application.yml"), "#{shared_path}/config/application.yml" puts "Now edit #{shared_path}/config/application.yml and add your ENV vars" @@ -81,6 +85,12 @@ namespace :deploy do end after "deploy:finalize_update", 'deploy:symlink_uploads_dir' + desc "Symlinks the invoices dir" + task :symlink_invoices_dir, :roles => :app do + run "rm -rf #{release_path}/invoices" + run "ln -nfs #{shared_path}/invoices/ #{release_path}/" + end + after "deploy:finalize_update", 'deploy:symlink_invoices_dir' namespace :assets do desc 'Run the precompile task locally and rsync with shared' diff --git a/config/deploy/production.rb b/config/deploy/production.rb index 17ce95568..8a5586167 100644 --- a/config/deploy/production.rb +++ b/config/deploy/production.rb @@ -1,15 +1,15 @@ -server "fab-manager.com", :web, :app, :db, primary: true +server "fablab.lacasemate.fr", :web, :app, :db, primary: true -set :domain, "fab-manager.com" -set :application, "fabmanager" -set :user, "admin" +set :domain, "fablab.lacasemate.fr" +set :application, "fablab" +set :user, "sleede" set :port, 22 set :deploy_to, "/home/#{user}/apps/#{application}" set :deploy_via, :remote_cache set :use_sudo, false set :scm, "git" -set :repository, "git@github.com:LaCasemate/fab-manager.git" +set :repository, "git@git.sleede.com:clients/fablab.git" set :scm_user, "jarod022" set :branch, "master" diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb index 8952eb260..cf0f0645f 100644 --- a/config/deploy/staging.rb +++ b/config/deploy/staging.rb @@ -1,7 +1,6 @@ -server "demo.fab-manager.com", :web, :app, :db, primary: true +server "test.fab-manager.com", :web, :app, :db, primary: true -set :domain, "demo.fab-manager.com" -set :application, "fabmanager_staging" +set :application, "fablab_staging" set :user, "admin" set :port, 22 set :deploy_to, "/home/#{user}/apps/#{application}" @@ -9,7 +8,7 @@ set :deploy_via, :remote_cache set :use_sudo, false set :scm, "git" -set :repository, "git@github.com:LaCasemate/fab-manager.git" +set :repository, "git@git.sleede.com:clients/fablab.git" set :scm_user, "jarod022" set :branch, "dev" @@ -29,8 +28,8 @@ namespace :deploy do sudo "ln -nfs #{current_path}/config/unicorn_init_staging.sh /etc/init.d/unicorn_#{application}" run "mkdir -p #{shared_path}/config" run "mkdir -p #{shared_path}/uploads" - put File.read("config/database.yml.default"), "#{shared_path}/config/database.yml" - puts "Now edit #{shared_path}/config/database.yml" + put File.read("config/database.yml"), "#{shared_path}/config/database.yml" + puts "Now edit #{shared_path}/config/database.yml and add your username and password" put File.read("config/application.yml"), "#{shared_path}/config/application.yml" puts "Now edit #{shared_path}/config/application.yml and add your ENV vars" @@ -49,16 +48,17 @@ namespace :deploy do end after "deploy:create_symlink", "deploy:db_migrate" - desc "load seed to bd" - task :load_seed, :roles => :app do - run "cd #{current_path} && bundle exec rake db:seed RAILS_ENV=staging" - end - namespace :assets do desc 'Run the precompile task locally and rsync with shared' - task :precompile, :roles => :web, :except => { :no_release => true } do + task :precompile, :only => { :primary => true } do + %x{bundle exec rake assets:precompile RAILS_ENV=staging} - %x{rsync --recursive --times --rsh='ssh -p#{port}' --compress --human-readable --progress public/assets #{user}@#{domain}:#{shared_path}} + + servers = find_servers :roles => [:app], :except => { :no_release => true } + servers.each do |server| + %x{rsync --recursive --times --rsh='ssh -p#{port}' --compress --human-readable --progress public/assets #{user}@#{server}:#{shared_path}} + end + %x{bundle exec rake assets:clean} end end diff --git a/config/disqus_api.yml b/config/disqus_api.yml deleted file mode 100644 index 21210753a..000000000 --- a/config/disqus_api.yml +++ /dev/null @@ -1,14 +0,0 @@ -development: - api_secret: YourApiSecretHere - api_key: YourApiKeyHere - access_token: YourAccessTokenHere - -staging: - api_secret: YourApiSecretHere - api_key: YourApiKeyHere - access_token: YourAccessTokenHere - -production: - api_secret: YourApiSecretHere - api_key: YourApiKeyHere - access_token: YourAccessTokenHere diff --git a/config/environments/development.rb b/config/environments/development.rb index 2e0d9cb7b..e24f8414a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -40,5 +40,5 @@ Rails.application.configure do # config.action_view.raise_on_missing_translations = true config.action_mailer.delivery_method = :letter_opener - config.action_mailer.default_url_options = { :host => 'localhost:3000' } + config.action_mailer.default_url_options = { :host => 'localhost:5000' } end diff --git a/config/environments/production.rb b/config/environments/production.rb index 0ac41f565..1db32f4e9 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -81,27 +81,21 @@ Rails.application.configure do # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new - config.serve_static_files = true + # config.serve_static_assets = true - config.action_mailer.default_url_options = { :host => ENV['APPLICATION_ROOT_URL'], :protocol => ENV['APPLICATION_PROTOCOL'] } + config.action_mailer.default_url_options = { :host => Rails.application.secrets.default_host, :protocol => Rails.application.secrets.default_protocol } # config.action_mailer.perform_deliveries = true # config.action_mailer.raise_delivery_errors = false # config.action_mailer.default :charset => "utf-8" - # #mandrillapp - # config.action_mailer.smtp_settings = { - # :port => 587, - # :address => 'smtp.mandrillapp.com', - # :user_name => ENV['MANDRILL_USERNAME'], - # :password => ENV['MANDRILL_APIKEY'], - # :authentication => 'plain', - # :enable_starttls_auto => true - # } + config.action_mailer.smtp_settings = { + :address => Rails.application.secrets.smtp_address, + :port => Rails.application.secrets.smtp_port, + :user_name => Rails.application.secrets.smtp_user_name, + :password => Rails.application.secrets.smtp_password + } - # config.action_mailer.delivery_method = :smtp - - config.action_mailer.delivery_method = :mandrill - - # config.action_mailer.delivery_method = :sendmail + # use :smtp for switch prod + config.action_mailer.delivery_method = Rails.application.secrets.delivery_method.to_sym end diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 501442264..1db32f4e9 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -81,27 +81,21 @@ Rails.application.configure do # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new - config.serve_static_files = true + # config.serve_static_assets = true - config.action_mailer.default_url_options = { :host => ENV['APPLICATION_ROOT_URL'], :protocol => ENV['APPLICATION_PROTOCOL'] } + config.action_mailer.default_url_options = { :host => Rails.application.secrets.default_host, :protocol => Rails.application.secrets.default_protocol } # config.action_mailer.perform_deliveries = true # config.action_mailer.raise_delivery_errors = false # config.action_mailer.default :charset => "utf-8" - # #mandrillapp - # config.action_mailer.smtp_settings = { - # :port => 587, - # :address => 'smtp.mandrillapp.com', - # :user_name => ENV['MANDRILL_USERNAME'], - # :password => ENV['MANDRILL_APIKEY'], - # :authentication => 'plain', - # :enable_starttls_auto => true - # } + config.action_mailer.smtp_settings = { + :address => Rails.application.secrets.smtp_address, + :port => Rails.application.secrets.smtp_port, + :user_name => Rails.application.secrets.smtp_user_name, + :password => Rails.application.secrets.smtp_password + } - # config.action_mailer.delivery_method = :smtp - - config.action_mailer.delivery_method = :mandrill - - # config.action_mailer.delivery_method = :sendmail + # use :smtp for switch prod + config.action_mailer.delivery_method = Rails.application.secrets.delivery_method.to_sym end diff --git a/config/initializers/activerecord.rb b/config/initializers/activerecord.rb new file mode 100644 index 000000000..c1f4bf43b --- /dev/null +++ b/config/initializers/activerecord.rb @@ -0,0 +1,6 @@ +# https://github.com/ruckus/active-record-query-trace +# traces the origin of active record queries, useful for optimisation purposes +if Rails.env.development? + ActiveRecordQueryTrace.enabled = false # set to true to enable + ActiveRecordQueryTrace.level = :app +end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 55ab9e200..d8be92465 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -12,3 +12,5 @@ Rails.application.config.assets.version = '1.0' Rails.application.config.assets.precompile += %w( fontawesome-webfont.eot fontawesome-webfont.woff fontawesome-webfont.svg fontawesome-webfont.ttf ) Rails.application.config.assets.precompile += %w( app.printer.css ) + +Rails.application.config.assets.precompile += %w( angular-i18n/angular-locale_*.js moment/locale/*.js summernote/lang/*.js messageformat/locale/*.js fullcalendar/dist/lang/*.js ) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index c05495be2..687eb1039 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -4,7 +4,10 @@ Devise.setup do |config| # The secret key used by Devise. Devise uses this key to generate # random tokens. Changing this key will render invalid all existing # confirmation, reset password and unlock tokens in the database. - config.secret_key = ENV['DEVISE_KEY'] + # Devise will use the `secret_key_base` on Rails 4+ applications as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = 'f0ad7aadec8086b90c0427e734602262e5d211147f3d93b5b94b5263ffd245e9fd9fcd672dcadea1d9ee2b1bffbf2712cdb013883d66943ef5bed93a263fe11a' + # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class @@ -116,7 +119,7 @@ Devise.setup do |config| # initial account confirmation) to be applied. Requires additional unconfirmed_email # db field (see migrations). Until confirmed new email is stored in # unconfirmed email column, and copied to email column on successful confirmation. - config.reconfirmable = true + config.reconfirmable = false # Defines which key will be used when confirming an account # config.confirmation_keys = [ :email ] @@ -228,6 +231,12 @@ Devise.setup do |config| # up on your models and hooks. # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' + require_relative '../../lib/omni_auth/omni_auth' + active_provider = AuthProvider.active + if active_provider.providable_type == OAuth2Provider.name + config.omniauth OmniAuth::Strategies::SsoOauth2Provider.name.to_sym, active_provider.providable.client_id, active_provider.providable.client_secret + end + # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or # change the failure app, you can configure them inside the config.warden block. diff --git a/config/initializers/elasticsearch.rb b/config/initializers/elasticsearch.rb new file mode 100644 index 000000000..16fdd2ce3 --- /dev/null +++ b/config/initializers/elasticsearch.rb @@ -0,0 +1,3 @@ +client = Elasticsearch::Client.new host: "http://#{ENV["ELASTICSEARCH_HOST"]}:9200", log: true +Elasticsearch::Model.client = client +Elasticsearch::Persistence.client = client diff --git a/config/initializers/mail.rb b/config/initializers/mail.rb new file mode 100644 index 000000000..cb3e16c7a --- /dev/null +++ b/config/initializers/mail.rb @@ -0,0 +1,15 @@ +require 'mail' +# This is trying to workaround an issue in `mail` gem +# Issue: https://github.com/mikel/mail/issues/912 +# +# Since with current version (2.6.3) it is using `autoload` +# And as mentioned by a comment in the issue above +# It might not be thread-safe and +# might have problem in threaded environment like Sidekiq workers +# +# So we try to require the file manually here to avoid +# "uninitialized constant" error +# +# This is merely a workaround since +# it should fixed by not using the `autoload` +require 'mail/parsers/content_type_parser' \ No newline at end of file diff --git a/config/initializers/mandrill.rb b/config/initializers/mandrill.rb deleted file mode 100644 index 909a0e7ef..000000000 --- a/config/initializers/mandrill.rb +++ /dev/null @@ -1,3 +0,0 @@ -MandrillDm.configure do |config| - config.api_key = ENV['MANDRILL_APIKEY'] -end \ No newline at end of file diff --git a/config/initializers/postgresql_database_tasks.rb b/config/initializers/postgresql_database_tasks.rb new file mode 100644 index 000000000..8883701bb --- /dev/null +++ b/config/initializers/postgresql_database_tasks.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module Tasks + class PostgreSQLDatabaseTasks + def drop + establish_master_connection + connection.select_all "select pg_terminate_backend(pg_stat_activity.pid) from pg_stat_activity where datname='#{configuration['database']}' AND state='idle';" + connection.drop_database configuration['database'] + end + end + end +end \ No newline at end of file diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index dd777bf91..a77a0da7a 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Rails.application.config.session_store :cookie_store, key: '_fabmanager_session' +Rails.application.config.session_store :cookie_store, key: '_fablab_session' diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 07839b93f..f27f1b87d 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -1,22 +1,23 @@ -# If ENV['REDIS_URL'] = nil, then url = redis://localhost:6379/0 - if Rails.env.staging? - namespace = "fabmanager_staging" + namespace = "fablab_staging" else - namespace = "fabmanager" + namespace = "fablab" end +redis_host = ENV["REDIS_HOST"] || 'localhost' +redis_url = "redis://#{redis_host}:6379" + Sidekiq.configure_server do |config| - config.redis = { url: ENV['REDIS_URL'], namespace: namespace } + config.redis = { url: redis_url, namespace: namespace } + + # load sidekiq-cron schedule config + schedule_file = "config/schedule.yml" + + if File.exists?(schedule_file) + Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file) + end end Sidekiq.configure_client do |config| - config.redis = { url: ENV['REDIS_URL'], namespace: namespace } -end - -# load sidekiq-cron schedule config -schedule_file = "config/schedule.yml" - -if File.exists?(schedule_file) - Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file) + config.redis = { url: redis_url, namespace: namespace } end diff --git a/config/initializers/stripe.rb b/config/initializers/stripe.rb new file mode 100644 index 000000000..a532c7a81 --- /dev/null +++ b/config/initializers/stripe.rb @@ -0,0 +1,3 @@ +require "stripe" + +Stripe.api_key = Rails.application.secrets.stripe_api_key diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml new file mode 100644 index 000000000..0c4587ad8 --- /dev/null +++ b/config/locales/app.admin.en.yml @@ -0,0 +1,463 @@ +en: + app: + admin: + machines_new: + # add a new machine + declare_a_new_machine: "Declare a new machine" + watch_out_when_creating_a_new_machine_its_prices_are_initialized_at_0_for_all_subscriptions: "Watch out! When creating a new machine, its prices are initialized at 0 for all subscriptions." + consider_changing_them_before_creating_any_reservation_slot: "Consider changing them before creating any reservation slot." + + machines_edit: + # machine edition + machine_edit: "Edit a machine" + + calendar: + # manage the trainings & machines slots + calendar_management: "Calendar management" + ongoing_reservations: "Ongoing reservations" + no_reservations: "No reservations" + do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION: "Do you really want to cancel the {USER}'s reservation, the {DATE} at {TIME}, concerning {RESERVATION}?" # messageFormat interpolation + reservation_cancellation_failed: "Reservation cancellation failed." + unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather: "Unable to remove the last machine of the slot. Delete the slot rather." + do_you_really_want_to_remove_MACHINE_from_this_slot: "Do you really want to remove \"{MACHINE}\" from this slot?" # messageFormat interpolation + this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing: "This will prevent any new reservation on this slot but won't cancel those existing." + beware_this_cannot_be_reverted: "Beware: this cannot be reverted." + the_machine_was_successfully_removed_from_the_slot: "The machine was successfully removed from the slot." + deletion_failed: "Deletion failed." + DATE_slot: "{{DATE}} slot:" # angular interpolation + you_can_define_a_training_on_that_slot: "You can define a training on that slot:" + link_a_training: "Link a training" + or_: "Or" + _select_some_machines: "select some machines" + number_of_tickets: "Number of tickets: " + adjust_the_opening_hours: "Adjust the opening hours" + restrict_this_slot_with_labels_(optional): "Restrict this slot with labels (optional)" + the_slot_START-END_has_been_successfully_deleted: "The slot {{START}} - {{END}} has been successfully deleted" # angular interpolation + unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Unable to delete the slot {{START}} - {{END}} because it's already reserved by a member" # angular interpolation + you_should_link_a_training_or_a_machine_to_this_slot: "You should link a training or a machine to this slot." + + project_elements: + # management of the projects' components + projects_elements_management: "Projects elements management" + add_a_material: "Add a material" + add_a_new_theme: "Add a new theme" + licences: "Licences" + add_a_new_licence: "Add a new licence" + + trainings: + # track and monitor the trainings + add_a_new_training: "Add a new training" + beware_when_creating_a_training_its_reservation_prices_are_initialized_to_zero: "Beware, when creating a training, its reservation prices are initialized at zero." + dont_forget_to_change_them_before_creating_slots_for_this_training: "Don't forget to change them before creating slots for this training." + associated_machines: "Associated machines" + number_of_tickets: "Number of tickets" + training: "Training" + year_NUMBER: "Year {{NUMBER}}" # angular interpolation + month_of_NAME: "Month of {{NAME}}" # angular interpolation + NUMBER_reservation: "{NUMBER} {NUMBER, plural, one{reservation} other{reservations}}" # messageFormat interpolation + none: "None" + training_validation: "Training validation" + training_of_the_ : "Training of the " # context: date. eg. "training of the september 1st 2012" + you_can_validate_the_training_of_the_following_members: "You can validate the training of the following members:" + no_reservation: "No reservation" + validate_the_trainings: "Validate the trainings" + edition_of_the_description_tooltip: "Edition of the description tooltip" + describe_the_training_in_a_few_words: "Describe the training in a few words." + description_is_limited_to_255_characters: "Description is limited to 255 characters." + description_was_successfully_saved: "Description was successfully saved." + training_successfully_deleted: "Training successfully deleted." + unable_to_delete_the_training_because_some_users_alredy_booked_it: "Unable to delete the training because some users already booked it." + + events: + # courses and workshops tracking and management + fablab_courses_and_workshops: "Fablab courses and workshops" + all_events: "All events" + passed_events: "Passed events" + events_to_come: "Events to come" + from_DATE: "From {{DATE}}" # angular interpolation + from_TIME: "From {{TIME}}" # angular interpolation + view_reservations: "View reservations" + + events_new: + # add a new workshop/course + none: "None" + every_days: "Every days" + every_week: "Every week" + every_month: "Every month" + every_year: "Every year" + + events_edit: + # edit an existing event + edit_the_event: "Edit the event" + + event_reservations: + # event reservations list + reservations: "Reservations :" + payment_date: "Payment date" + reserved_tickets: "Reserved tickets" + show_the_event: "Show the event" + no_reservations_for_now: "No reservation for now." + back_to_monitoring: "Back to monitoring" + + pricing: + # subscriptions, prices and credits management + pricing_management: "Pricing management" + list_of_the_subscription_plans: "List of the subscription plans" + beware_the_subscriptions_are_disabled_on_this_application: "Beware, the subscriptions are disabled on this application." + you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager: "You can create some but they won't be available until the project is redeployed by the server manager." + for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later: "For safety reasons, please don't create subscriptions if you don't intend to use them later." + add_a_new_subscription_plan: "Add a new subscription plan" + duration: "Duration" + prominence: "Prominence" + machine_hours: "Machine hours" + these_prices_match_machine_hours_rates_: "These prices match machine hours rates" + _without_subscriptions: "without subscriptions" + credits: "Credits" + related_trainings: "Related trainings" + add_a_machine_credit: "Add a machine credit" + machine: "Machine" + hours: "Hours" + related_subscriptions: "Related subscriptions" + please_specify_a_number: "Please specify a number." + none: "None" # grammar note: concordance with "training". + an_error_occurred_while_saving_the_number_of_credits: "An error occurred while saving the number of credits." + an_error_occurred_while_deleting_credit_with_the_TRAINING: "An error occurred while deleting credit with the {{TRAINING}}." # angular interpolation + an_error_occurred_unable_to_find_the_credit_to_revoke: "An error occurred : unable to find the credit to revoke." + an_error_occurred_while_creating_credit_with_the_TRAINING: "An error occurred while creating credit with the {{TRAINING}}." # angular interpolation + not_set: "Not set" + error_a_credit_linking_this_machine_with_that_subscription_already_exists: "Error : a credit linking this machine with that subscription already exists." + changes_have_been_successfully_saved: "Changes have been successfully saved." + credit_was_successfully_saved: "Credit was successfully saved." + do_you_really_want_to_delete_this_subscription_plan: "Do you really want to delete this subscription plan?" + subscription_plan_was_successfully_deleted: "Subscription plan was successfully delete." + unable_to_delete_the_specified_subscription_an_error_occurred: "Unable to delete the specified subscription, an error occurred." + + plans: + new: + # add a subscription plan on the platform + add_a_subscription_plan: "Add a subscription plan" + unable_to_create_the_subscription_please_try_again: "Unable to create the subscription plan. Please try again." + successfully_created_subscription(s)_dont_forget_to_redefine_prices: "Subscription(s) successfully created. Don't forget to redefine prices." + unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name: "Unable to save this user. Check that there isn't an already defined user with the same name." + edit: + # edit a subscription plan / machine hours prices + subscription_plan: "Subscription plan:" + prices: "Prices" + copy_prices_from: "Copy prices from" + machine: "Machine" + hourly_rate: "Hourly rate" + unable_to_save_subscription_changes_please_try_again: "Unable to save subscription changes. Please try again." + subscription_successfully_changed: "Subscription successfully changed." + + invoices: + # list of all invoices & invoicing parameters + invoices: "Invoices" + invoices_list: "Invoices list" + filter_invoices: "Filter invoices" + invoice_#_: "Invoice #:" + customer_: "Customer:" + date_: "Date:" + invoice_#: "Invoice #" + customer: "Customer" + credit_note: "Credit note" + invoicing_settings: "Invoicing settings" + change_logo: "Change logo" + john_smith: "John Smith" + john_smith@example_com: "jean.smith@example.com" + invoice_reference_: "Invoice reference:" + code_: "Code:" + code_disabled: "Code disabled" + order_#: "Order #:" + invoice_issued_on_DATE_at_TIME: "Invoice issued on {{DATE}} at {{TIME}}" # angular interpolation + object_reservation_of_john_smith_on_DATE_at_TIME: "Object: Reservation of John Smith on {{DATE}} at {{TIME}}" # angular interpolation + order_summary: "Order summary:" + details: "Details" + amount: "Amount" + machine_booking-3D_printer: "Machine booking - 3D printer" + total_amount: "Total amount" + total_including_all_taxes: "Total incl. all taxes" + VAT_disabled: "VAT disabled" + including_VAT: "Including VAT" + including_total_excluding_taxes: "Including Total excl. taxes" + including_amount_payed_on_ordering: "Including Amount payed on ordering" + settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT: "Settlement by debit card on {{DATE}} at {{TIME}}, for an amount of {{AMOUNT}}" + important_notes: "Important notes" + address_and_legal_information: "Address and legal information" + invoice_reference: "Invoice reference" + day: "Day" + "#_of_invoice": "# of invoice" + online_sales: "Online sales" + refund: "Refund" + documentation: "Documentation" + 2_digits_year_(eg_70): "2 digits year (eg. 70)" + 4_digits_year_(eg_1970): "4 digits year (eg. 1970)" + month_number_(eg_1): "Month number (eg. 1)" + 2_digits_month_number_(eg_01): "2 digits month number (eg. 01)" + 3_characters_month_name_(eg_JAN): "3 characters month name (eg. JAN)" + day_in_the_month_(eg_1): "Day in the month (eg. 1)" + 2_digits_day_in_the_month_(eg_01): "2 digits in the month (eg. 01)" + (n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day): "(n) digits, daily count of invoices (eg. ddd => 002 : 2nd invoice of the day)" + (n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month): "(n) digits, monthly count of invoices (eg. mmmm => 0012 : 12th invoice of the month)" + (n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year): "(n) digits, annual count of invoices (ex. yyyyyy => 000008 : 8th invoice of this year)" + beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left: "Beware: if the number exceed the specified length, it will be truncated by the left." + (n)_digits_count_of_orders_(eg_nnnn_0327_327th_order): "(n) digits, count of invoices (eg. nnnn => 0327 : 327th order)" + (n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day): "(n) digits, daily count of orders (eg. ddd => 002 : 2nd order of the day)" + (n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month): "(n) digits, monthly count of orders (eg. mmmm => 0012 : 12th order of the month)" + (n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year): "(n) digits, annual count of orders (ex. yyyyyy => 000008 : 8th order of this year)" + add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Add a notice regarding the online sales, only if the invoice is concerned." + this_will_never_be_added_when_a_refund_notice_is_present: "This will never be added when a refund notice is present." + (eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe): '(eg. X[/VL] will add "/VL" to the invoices settled with stripe)' + add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Add a notice regarding refunds, only if the invoice is concerned." + this_will_never_be_added_when_an_online_sales_notice_is_present: "This will never be added when an online sales notice is present." + (eg_R[/A]_will_add_/A_to_the_refund_invoices): '(ed. R[/A] will add "/A" to the refund invoices)' + code: "Code" + enable_the_code: "Enable the code" + enabled: "Enabled" + disabled: "Disabled" + order_number: "Order number" + elements: "Elements" + VAT: "VAT" + enable_VAT: "Enable VAT" + VAT_rate: "VAT rate" + refund_invoice_successfully_created: "Refund invoice successfully created." + create_a_refund_on_this_invoice: "Create a refund on this invoice" + creation_date_for_the_refund: "Creation date for the refund" + creation_date_is_required: "Creation date is required." + refund_mode: "Refund mode:" + do_you_want_to_disable_the_user_s_subscription: "Do you want to disabled the user's subscription:" + elements_to_refund: "Elements to refund" + description_(optional): "Description (optional):" + will_appear_on_the_refund_invoice: "Will appear on the refund invoice." + none: "None" # grammar note: concordance with "payment mean" + by_cash: "By cash" + by_cheque: "By cheque" + by_transfer: "By transfer" + you_must_select_at_least_one_element_to_create_a_refund: "You must select at least one element, to create a refund." + unable_to_create_the_refund: "Unable to create the refund" + invoice_reference_successfully_saved: "Invoice reference successfully saved." + an_error_occurred_while_saving_invoice_reference: "An error occurred while saving invoice reference." + invoicing_code_succesfully_saved: "Invoicing code successfully saved." + an_error_occurred_while_saving_the_invoicing_code: "An error occurred while saving the invoicing code." + code_successfully_activated: "Code successfully activated." + code_successfully_disabled: "Code successfully disabled." + an_error_occurred_while_activating_the_invoicing_code: "An error occurred while activating the invoicing code." + order_number_successfully_saved: "Order number successfully saved." + an_error_occurred_while_saving_the_order_number: "An error occurred while saving the order number." + VAT_rate_successfully_saved: "VAT rate successfully saved." + an_error_occurred_while_saving_the_VAT_rate: "An error occurred while saving the VAT rate." + VAT_successfully_activated: "VAT successfully activated." + VAT_successfully_disabled: "VAT successfully disabled." + an_error_occurred_while_activating_the_VAT: "An error occurred while activating the VAT." + text_successfully_saved: "Text successfully saved." + an_error_occurred_while_saving_the_text: "An error occurred while saving the text." + address_and_legal_information_successfully_saved: "Address and legal information successfully saved." + an_error_occurred_while_saving_the_address_and_the_legal_information: "An error occurred while saving the address and the legal information." + logo_successfully_saved: "Logo successfully saved." + an_error_occurred_while_saving_the_logo: "An error occurred while saving the logo." + + members: + # management of users, labels, groups, and so on + users_management: "Users management" + members: "Members" + search_for_an_user: "Search for an user" + add_a_new_member: "Add a new member" + reservations: "Reservations" + email: "Email" + phone: "Phone" + user_type: "User type" + administrators: "Administrators" + search_for_an_administrator: "Search for an administrator" + add_a_new_administrator: "Add a new administrator" + groups: "Groups" + authentication: "Authentication" + do_you_really_want_to_delete_this_administrator_this_cannot_be_undone: "Do you really want to delete this administrator? This cannot be undone." + administrator_successfully_deleted: "Administrator successfully deleted." + unable_to_delete_the_administrator: "Unable to delete the administrator." + add_a_group: "Add a group" + group_name: "Group name" + changes_successfully_saved: "Changes successfully saved." + an_error_occurred_while_saving_changes: "An error occurred when saving changes." + new_group_successfully_saved: "New group successfully saved." + an_error_occurred_when_saving_the_new_group: "An error occurred when saving the new group." + group_successfully_deleted: "Group successfully deleted." + unable_to_delete_group_because_some_users_and_or_groups_are_still_linked_to_it: "Unable to delete group because some users and/or groups are still linked to it." + add_a_tag: "Add a tag" + tag_name: "Tag name" + new_tag_successfully_saved: "New tag successfully saved." + an_error_occurred_while_saving_the_new_tag: "An error occurred while saving the new tag." + tag_successfully_deleted: "Tag successfully deleted." + an_error_occurred_and_the_tag_deletion_failed: "An error occurred and the tag deletion failed." + search_for_an_authentication_provider: "Search for an authentication provider" + add_a_new_authentication_provider: "Add a new authentication provider" + state: "State" + unknown: "Unknown: " + active: "Active" + pending: "Pending" + previous_provider: "Previous provider" + do_you_really_want_to_delete_the_TYPE_authentication_provider_NAME: "Do you really want to delete the {{TYPE}} authentication provider: {{NAME}}?" # angular interpolation + authentication_provider_successfully_deleted: "Authentication provider successfully deleted." + an_error_occurred_unable_to_delete_the_specified_provider: "An error occurred: unable to delete the specified provider." + + members_new: + # add a member + add_a_member: "Add a member" + + members_edit: + # edit a member + duration: "Duration:" + expires_at: "Expires at:" + price_: "Price:" + offer_free_days: "Offer free days" + extend_subscription: "Extend subscription" + user_has_no_current_subscription: "User has no current subscription." + subscribe_to_a_plan: "Subscribe to a plan" + next_trainings: "Next trainings" + passed_trainings: "Passed trainings" + validated_trainings: "Validated trainings" + courses_and_workshops: "Courses and workshops" + next_courses_and_workshops: "Next courses and workshops" + no_upcomning_courses_or_workshops: "No upcoming courses and workshops" + NUMBER_full_price_tickets_reserved: "{NUMBER, plural, =0{} one{1 full price ticket reserved} other{{NUMBER} full price tickets reserved}}" # messageFormat interpolation + NUMBER_reduced_rate_tickets_reserved: "{NUMBER, plural, =0{} one{1 reduced rate ticket reserved} other{{NUMBER} reduced rate tickets reserved}}" # messageFormat interpolation + passed_courses_and_workshops: "Passed courses and workshops" + no_passed_courses_or_workshop: "No passed courses or workshops" + invoices: "Invoices" + invoice_#: "Invoice #" + download_the_refund_invoice: "Download the refund invoice" + expiration_date: "Expiration date" + you_intentionally_decide_to_extend_the_user_s_subscription_by_offering_him_free_days: "You intentionally decide to extend the user's subscription by offering him free days." + you_intentionally_decide_to_extend_the_user_s_subscription_by_charging_him_again_for_his_current_subscription: "You intentionally decide to extend the user's subscription by charging him again for his current subscription." + until_(expiration_date): "Until (expiration date):" + you_successfully_changed_the_expiration_date_of_the_user_s_subscription: "You successfully changed the expiration date of the user's subscription" + a_problem_occurred_while_saving_the_date: "A problem occurred while saving the date." + new_subscription: "New subscription" + you_are_about_to_purchase_a_subscription_to_NAME: "You are about to purchase a subscription to {{NAME}}." # angular interpolation + subscription_successfully_purchased: "Subscription successfully purchased." + a_problem_occurred_while_taking_the_subscription: "A problem occurred while taking the subscription" + + admins_new: + # add a new administrator to the platform + add_an_administrator: "Add an administrator" + administrator_successfully_created_he_will_receive_his_connection_directives_by_email: "Administrator successfully created. {GENDER, select, female{She} other{He}} receive {GENDER, select, female{her} other{his}} connection directives by e-mail." # messageFormat interpolation + + authentication_new: + # add a new authentication provider (SSO) + add_a_new_authentication_provider: "Add a new authentication provider" + a_local_database_provider_already_exists_unable_to_create_another: "A \"Local Database\" provider already exists. Unable to create another." + local_provider_successfully_saved: "Local provider successfully saved." + it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider: "It is required to set the matching between User.uid and the API to add this provider" + security_issue_detected: "Security issue detected" + beware_the_oauth2_authenticatoin_provider_you_are_about_to_add_isnt_using_HTTPS: "Beware: the OAuth 2 provider you are about to add isn't using HTTPS." + this_is_a_serious_security_issue_on_internet_and_should_never_be_used_except_for_testing_purposes: "This is a serious security issue on internet and should never be used except for testing purposes." + do_you_really_want_to_continue: "Do you really want to continue?" + unsecured_oauth2_provider_successfully_added: "Unsecured OAuth 2.0 provider successfully added." + oauth2_provider_successfully_added: "OAuth 2.0 provider successfully added." + + authentication_edit: + # edit an authentication provider (SSO) + provider: "Provider :" + it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider: "It is required to set the matching between User.uid and the API to add this provider" + provider_successfully_updated: "Provider successfully updated" + an_error_occurred_unable_to_update_the_provider: "An error occurred: unable to update the provider." + + statistics: + # statistics tables + evolution: "Evolution" + age_filter: "Age filter" + from_age: "From" # context: age. eg: from 8 to 40 years old + to_age: "to" # context: age. eg: from 8 to 40 years old + _years_old: "years old" + start: "Start:" + end: "End:" + custom_filter: "Custom filter" + NO_: "NO" + criterion: "Criterion:" + value: "Value:" + exclude: "Exclude" + entries: "Entries:" + revenue_: "Revenue:" + average_age: "Average age:" + years_old: "Years old" + total: "Total" + gender: "Gender" + age: "Age" + revenue: "Revenue" + unknown: "Unknown" + user_id: "User ID" + + + stats_graphs: + # statistics graphs + data: "Data" + day: "Day" + week: "Week" + start: "Start:" + end: "End:" + revenue: "Revenue" + top_list_of: "Top list of" + number: "Number" + week_short: "Week" + week_of_START_to_END: "Week of {{START}} to {{END}}" # angular interpolation + no_data_for_this_period: "No data for this period" + + settings: + # global application parameters and customization + customize_the_application: "Customize the application" + general: "General" + fablab_title: "FabLab title" + fablab_name: "FabLab name" + title_concordance: "Title concordance" + male: "Male." + female: "Female." + eg: "eg:" + about: "About" + male_preposition: "the" + female_preposition: "the" + customize_information_messages: "Customize information messages" + message_of_the_machine_booking_page: "Message of the machine booking page:" + type_the_message_content: "Type the message content" + warning_message_of_the_training_booking_page: "Warning message of the training booking page:" + information_message_of_the_training_reservation_page: "Information message of the training reservation page:" + message_of_the_subscriptions_page: "Message of the subscriptions page:" + message_of_the_event_page_relative_to_the_reduced_rate_availability_conditions: "Message of the event page, relative to the reduced rate availability conditions:" + legal_documents: "Legal documents" + if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user: "If these documents are not filled, no consent about them will be asked." + general_terms_and_conditions_(T&C): "General terms and conditions (T&C)" + terms_of_service_(TOS): "Terms of service (TOS)" + customize_the_graphics: "Customize the graphics" + for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height: "For an optimal rendering, the logo image must be at the PNG format with a transparent background and an aspect ratio 3.5 wider than the height." + concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels: "Concerning the favicon, it must be at ICO format with a size of 16x16 pixels." + remember_to_refresh_the_page_for_the_changes_to_take_effect: "Remember to refresh the page for the changes to take effect." + logo_(white_background): "Logo (white background)" + change_the_logo: "Change the logo" + logo_(black_background): "Logo (black background)" + favicon: "Favicon" + change_the_favicon: "Change the favicon" + main_colour: "Main colour:" + primary: "Primary" + secondary_colour: "Secondary colour:" + secondary: "Secondary" + home_page: "Home page" + news_of_the_home_page: "News of the home page:" + type_your_news_here: "Type your news here" + leave_it_empty_to_not_bring_up_any_news_on_the_home_page: "Leave it empty to not bring up any news on the home page" + twitter_stream: "Twitter Stream:" + name_of_the_twitter_account: "Name of the Twitter account" + title_of_the_about_page: "Title of the About page" + shift_enter_to_force_carriage_return: "SHIFT + ENTER to force carriage return" + input_the_main_content: "Input the main content" + input_the_fablab_contacts: "Input the FabLab contacts" + reservations: "Reservations" + reservations_parameters: "Reservations parameters" + confine_the_booking_agenda: "Confine the booking agenda" + opening_time: "Opening time" + closing_time: "Closing time" + ability_for_the_users_to_move_their_reservations: "Ability for the users to move their reservations" + reservations_shifting: "Reservations shifting" + prior_period_(hours): "Prior period (hours)" + enabled: "Enabled" + disabled: "Disabled" + ability_for_the_users_to_cancel_their_reservations: "Ability for the users to cancel their reservations" + reservations_cancelling: "Reservations cancelling" + customization_of_SETTING_successfully_saved: "Customization of {{SETTING}} successfully saved." # angular interpolation + file_successfully_updated: "File successfully updated." diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml new file mode 100644 index 000000000..0a7a29ba0 --- /dev/null +++ b/config/locales/app.admin.fr.yml @@ -0,0 +1,463 @@ +fr: + app: + admin: + machines_new: + # ajout d'une nouvelle machine + declare_a_new_machine: "Déclarer une nouvelle machine" + watch_out_when_creating_a_new_machine_its_prices_are_initialized_at_0_for_all_subscriptions: "Attention, lors de la création d'une machine, ses tarifs de réservation sont initialisés à zero pour tout les abonnements." + consider_changing_them_before_creating_any_reservation_slot: "Pensez à les modifier avant de créer des créneaux pour cette machine." + + machines_edit: + # edition d'une machine + machine_edit: "Modifier une machine" + + calendar: + # gestion des créneaux machines et formations + calendar_management: "Gestion du calendrier" + ongoing_reservations: "Réservations en cours" + no_reservations: "Aucune réservation" + do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION: "Êtes-vous {GENDER, select, female{sûre} other{sûr}} de vouloir annuler la réservation de {USER}, le {DATE} à {TIME}, concernant {RESERVATION} ?" # messageFormat interpolation + reservation_cancellation_failed: "L'annulation de la réservation a échouée." + unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather: "Impossible de supprimer la dernière machine du créneau. Supprimez plutôt le créneau." + do_you_really_want_to_remove_MACHINE_from_this_slot: "Êtes-vous {GENDER, select, female{sûre} other{sûr}} de vouloir retirer \"{MACHINE}\" de ce créneau ?" # messageFormat interpolation + this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing: "Ceci interdira toute nouvelle réservation de cette machine sur ce créneau mais n'annulera pas les réservation existantes." + beware_this_cannot_be_reverted: "Attention : ceci n'est pas réversible." + the_machine_was_successfully_removed_from_the_slot: "La machine a bien été supprimée du créneau." + deletion_failed: "La suppression a échouée." + DATE_slot: "Créneau du {{DATE}} :" # angular interpolation + you_can_define_a_training_on_that_slot: "Vous pouvez définir une formation sur ce créneau :" + link_a_training: "Associer une formation" + or_: "Ou" + _select_some_machines: "sélectionner des machines" + number_of_tickets: "Nombre de places : " + adjust_the_opening_hours: "Ajuster l'horaire" + restrict_this_slot_with_labels_(optional): "Restreindre ce créneau avec des étiquettes (optionnel)" + the_slot_START-END_has_been_successfully_deleted: "Le créneau {{START}} - {{END}} a bien été supprimé" # angular interpolation + unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Le créneau {{START}} - {{END}} n'a pu être supprimé car il est déjà réservé par un membre" # angular interpolation + you_should_link_a_training_or_a_machine_to_this_slot: "Vous devriez associer une formation ou une machine à ce créneau." + + project_elements: + # gestion des éléments constituant les projets + projects_elements_management: "Gestion des éléments projets" + add_a_material: "Ajouter un matériau" + add_a_new_theme: "Ajouter une nouvelle thématique" + licences: "Licences" + add_a_new_licence: "Ajouter une nouvelle licence" + + trainings: + # suivre et surveiller les formations + add_a_new_training: "Ajouter une nouvelle formation" + beware_when_creating_a_training_its_reservation_prices_are_initialized_to_zero: "Attention, lors de la création d'une formation, ses tarifs de réservation sont initialisés à zero." + dont_forget_to_change_them_before_creating_slots_for_this_training: "Pensez à les modifier avant de créer des créneaux pour cette formation." + associated_machines: "Machines associées" + number_of_tickets: "Nombre de places" + training: "Formation" + year_NUMBER: "Année {{NUMBER}}" # angular interpolation + month_of_NAME: "Mois de {{NAME}}" # angular interpolation + NUMBER_reservation: "{NUMBER} {NUMBER, plural, =0{réservation} one{réservation} other{réservations}}" # messageFormat interpolation + none: "Aucune" + training_validation: "Validation formation" + training_of_the_ : "Formation du " # context: date. eg. "training of the september 1st 2012" + you_can_validate_the_training_of_the_following_members: "Vous pouvez valider la formation des membres suivants :" + no_reservation: "Aucune réservation" + validate_the_trainings: "Valider les formations" + edition_of_the_description_tooltip: "Édition de l'infobulle de description" + describe_the_training_in_a_few_words: "Décrivez la formation en quelques mots." + description_is_limited_to_255_characters: "La description est limitée à 255 caractères." + description_was_successfully_saved: "La description a bien été enregistrée." + training_successfully_deleted: "La formation a bien été supprimée." + unable_to_delete_the_training_because_some_users_alredy_booked_it: "La formation ne peut pas être supprimée car elle a déjà été réservée par des utilisateurs." + + events: + # gestion et suivi des stages et ateliers + fablab_courses_and_workshops: "Les Stages et ateliers du Fab Lab" + all_events: "Tous les évènements" + passed_events: "Les évènements déjà passés" + events_to_come: "Les évènements à venir" + from_DATE: "Du {{DATE}}" # angular interpolation + from_TIME: "De {{TIME}}" # angular interpolation + view_reservations: "Consulter les réservations" + + events_new: + # ajouter un nouveau atelier/stage + none: "Aucune" + every_days: "Tous les jours" + every_week: "Chaque semaine" + every_month: "Chaque mois" + every_year: "Chaque année" + + events_edit: + # modifier un évènement existant + edit_the_event: "Éditer l'évènement" + + event_reservations: + # liste des réservations sur un évènement + reservations: "Les réservations :" + payment_date: "Date de paiement" + reserved_tickets: "Places réservées" + show_the_event: "Afficher l'évènement" + no_reservations_for_now: "Aucune réservation pour le moment." + back_to_monitoring: "Retour au suivi" + + pricing: + # gestion des abonnements, des tarifs et des crédits + pricing_management: "Gestion de la tarification" + list_of_the_subscription_plans: "Liste des formules d'abonnements" + beware_the_subscriptions_are_disabled_on_this_application: "Attention, les abonnements sont désactivés sur cette application." + you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager: "Vous pouvez tout de même en créer mais ils ne seront disponibles qu'après un redéploiement du projet par le responsable du serveur." + for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later: "Pour des raisons de sécurité, veuillez ne pas créer d'abonnements si vous ne comptez pas les utiliser par la suite." + add_a_new_subscription_plan: "Ajouter une nouvelle formule d'abonnement" + duration: "Durée" + prominence: "Importance" + machine_hours: "Heures machines" + these_prices_match_machine_hours_rates_: "Ces tarifs correspondent au prix d'une heure machine" + _without_subscriptions: "sans abonnement" + credits: "Crédits" + related_trainings: "Formations associées" + add_a_machine_credit: "Ajouter un crédit Machine" + machine: "Machine" + hours: "Heures" + related_subscriptions: "Abonnements associés" + please_specify_a_number: "Veuillez spécifier un nombre." + none: "Aucune" # grammar note: concordance with "training". + an_error_occurred_while_saving_the_number_of_credits: "Une erreur est survenue lors de l'enregistrement du nombre de crédits." + an_error_occurred_while_deleting_credit_with_the_TRAINING: "Une erreur est survenue lors de la suppression du crédit avec la {{TRAINING}}." # angular interpolation + an_error_occurred_unable_to_find_the_credit_to_revoke: "Une erreur est survenue : impossible de retrouver le crédit à enlever." + an_error_occurred_while_creating_credit_with_the_TRAINING: "Une erreur est survenue lors de la création du crédit avec la {{TRAINING}}." # angular interpolation + not_set: "Non défini" + error_a_credit_linking_this_machine_with_that_subscription_already_exists: "Erreur : un crédit associant cette machine et cet abonnement existe déjà." + changes_have_been_successfully_saved: "Les modifications ont bien été enregistrées." + credit_was_successfully_saved: "Le crédit a bien été enregistré." + do_you_really_want_to_delete_this_subscription_plan: "Êtes-vous sûr(e) de vouloir supprimer cette formule d'abonnement ?" + subscription_plan_was_successfully_deleted: "La formule d'abonnement a bien été supprimée." + unable_to_delete_the_specified_subscription_an_error_occurred: "Impossible de supprimer l'abonnement spécifié, une erreur s'est produite." + + plans: + new: + # ajouter une formule d'abonnement sur la plate-forme + add_a_subscription_plan: "Ajouter une formule d'abonnement" + unable_to_create_the_subscription_please_try_again: "L'abonnement n'a pas pu être créé. Veuillez réessayer." + successfully_created_subscription(s)_dont_forget_to_redefine_prices: "Création du/des abonnement(s) réussie. N'oubliez pas de redéfinir les tarifs." + unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name: "Impossible d'enregistrer cet utilisateur. Vérifiez qu'il n'existe pas déjà un utilisateur du même nom." + edit: + # modifier une formule d'abonnement / les prix des heures machines + subscription_plan: "Formule d'abonnement :" + prices: "Tarifs" + copy_prices_from: "Copier les prix depuis" + machine: "Machine" + hourly_rate: "Tarif horaire" + unable_to_save_subscription_changes_please_try_again: "Les modifications de l'abonnement n'ont pas pu être enregistrées. Veuillez réessayer." + subscription_successfully_changed: "Modification de l'abonnement réussie." + + invoices: + # liste de toutes les factures & paramètres de facturation + invoices: "Factures" + invoices_list: "Liste des factures" + filter_invoices: "Filtrer les factures" + invoice_#_: "Facture n° :" + customer_: "Client :" + date_: "Date :" + invoice_#: "Facture n°" + customer: "Client" + credit_note: "Avoir" + invoicing_settings: "Paramètres de facturation" + change_logo: "Changer le logo" + john_smith: "Jean Dupont" + john_smith@example_com: "jean.dupont@example.com" + invoice_reference_: "Référence facture :" + code_: "Code :" + code_disabled: "Code désactivé" + order_#: "N° Commande :" + invoice_issued_on_DATE_at_TIME: "Facture éditée le {{DATE}} à {{TIME}}" # angular interpolation + object_reservation_of_john_smith_on_DATE_at_TIME: "Objet : Réservation de Jean Dupont le {{DATE}} à {{TIME}}" # angular interpolation + order_summary: "Récapitulatif de la commande :" + details: "Détails" + amount: "Montant" + machine_booking-3D_printer: "Réservation Machine - Imprimante 3D" + total_amount: "Montant total" + total_including_all_taxes: "Total TTC" + VAT_disabled: "TVA désactivée" + including_VAT: "Dont TVA" + including_total_excluding_taxes: "Dont total HT" + including_amount_payed_on_ordering: "Dont montant payé à la commande" + settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT: "Règlement effectué par carte bancaire le {{DATE}} à {{TIME}}, pour un montant de {{AMOUNT}}" + important_notes: "Informations importantes" + address_and_legal_information: "Adresse et informations légales" + invoice_reference: "Référence facture" + day: "Jour" + "#_of_invoice": "N° de facture" + online_sales: "Vente en ligne" + refund: "Remboursement" + documentation: "Documentation" + 2_digits_year_(eg_70): "Année sur 2 chiffres (ex. 70)" + 4_digits_year_(eg_1970): "Année sur 4 chiffres (ex. 1970)" + month_number_(eg_1): "Numéro du mois (ex. 1)" + 2_digits_month_number_(eg_01): "Numéro du mois sur 2 chiffres (ex. 01)" + 3_characters_month_name_(eg_JAN): "Nom du mois sur 3 lettres (ex. JAN)" + day_in_the_month_(eg_1): "Jour dans le mois (ex. 1)" + 2_digits_day_in_the_month_(eg_01): "Jour dans le mois sur 2 chiffres (ex. 01)" + (n)_digits_daily_count_of_invoices_(eg_ddd_002_2nd_invoice_of_the_day): "Nombre de facture dans le jour, sur (n) chiffres (ex. ddd => 002 : 2ème facture du jour)" + (n)_digits_monthly_count_of_invoices_(eg_mmmm_0012_12th_invoice_of_this_month): "Nombre de facture dans le mois, sur (n) chiffres (ex. mmmm => 0012 : 12ème facture ce mois)" + (n)_digits_annual_amount_of_invoices_(eg_yyyyyy_000008_8th_invoice_of_this_year): "Nombre de facture dans l'année, sur (n) chiffres (ex. yyyyyy => 000008 : 8ème facture cette année)" + beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left: "Attention : si le nombre dépasse la longueur demandée, il sera tronqué par la gauche." + (n)_digits_count_of_orders_(eg_nnnn_0327_327th_order): "Nombre de commandes, sur (n) chiffres (ex. nnnn => 0327 : 327ème commande)" + (n)_digits_daily_count_of_orders_(eg_ddd_002_2nd_order_of_the_day): "Nombre de commandes dans le jour, sur (n) chiffres (ex. ddd => 002 : 2ème commande du jour)" + (n)_digits_monthly_count_of_orders_(eg_mmmm_0012_12th_order_of_this_month): "Nombre de commandes dans le mois, sur (n) chiffres (ex. mmmm => 0012 : 12ème commande ce mois)" + (n)_digits_annual_amount_of_orders_(eg_yyyyyy_000008_8th_order_of_this_year): "Nombre de commandes dans l'année, sur (n) chiffres (ex. yyyyyy => 000008 : 8ème commande cette année)" + add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned: "Ajoute une information relative à la vente en ligne, uniquement si cela concerne la facture." + this_will_never_be_added_when_a_refund_notice_is_present: "Ceci ne sera jamais cumulé avec une information de remboursement." + (eg_X[/VL]_will_add_/VL_to_the_invoices_settled_with_stripe): '(ex. X[/VL] ajoutera "/VL" aux factures réglées avec stripe)' + add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Ajoute une information relative aux remboursements, uniquement si cela concerne la facture. " + this_will_never_be_added_when_an_online_sales_notice_is_present: "Ceci ne sera jamais cumulé avec une information de vente en ligne." + (eg_R[/A]_will_add_/A_to_the_refund_invoices): '(ex. R[/A] ajoutera "/A" aux factures de remboursement)' + code: "Code" + enable_the_code: "Activer le code" + enabled: "Activé" + disabled: "Désactivé" + order_number: "Numéro de Commande" + elements: "Éléments" + VAT: "TVA" + enable_VAT: "Activer la TVA" + VAT_rate: "Taux de TVA" + refund_invoice_successfully_created: "La facture d'avoir a bien été créée." + create_a_refund_on_this_invoice: "Générer un avoir sur cette facture" + creation_date_for_the_refund: "Date d'émission de l'avoir" + creation_date_is_required: "La date d'émission est requise." + refund_mode: "Mode de remboursement :" + do_you_want_to_disable_the_user_s_subscription: "Souhaitez-vous désactiver l'abonnement de l'utilisateur :" + elements_to_refund: "Éléments à rembourser" + description_(optional): "Description (optionnelle) :" + will_appear_on_the_refund_invoice: "Apparaîtra sur la facture de remboursement." + none: "Aucun" # grammar note: concordance with "payment mean" + by_cash: "En espèces" + by_cheque: "Par chèque" + by_transfer: "Par virement" + you_must_select_at_least_one_element_to_create_a_refund: "Vous devez sélectionner au moins un élément sur lequel créer un avoir." + unable_to_create_the_refund: "Impossible de créer l'avoir" + invoice_reference_successfully_saved: "La référence facture a bien été enregistrée." + an_error_occurred_while_saving_invoice_reference: "Une erreur est survenue lors de l'enregistrement de la référence facture." + invoicing_code_succesfully_saved: "Le code de facturation a bien été enregistré." + an_error_occurred_while_saving_the_invoicing_code: "Une erreur est survenue lors de l'enregistrement du code de facturation." + code_successfully_activated: "Le code a bien été activé." + code_successfully_disabled: "Le code a bien été désactivé." + an_error_occurred_while_activating_the_invoicing_code: "Une erreur est survenue lors de l'activation du code de facturation." + order_number_successfully_saved: "Le numéro de commande a bien été enregistré." + an_error_occurred_while_saving_the_order_number: "Une erreur est survenue lors de l'enregistrement du numéro de commande." + VAT_rate_successfully_saved: "Le taux de TVA a bien été enregistré." + an_error_occurred_while_saving_the_VAT_rate: "Une erreur est survenue lors de l'enregistrement du taux de TVA." + VAT_successfully_activated: "La TVA a bien été activé." + VAT_successfully_disabled: "La TVA a bien été désactivé." + an_error_occurred_while_activating_the_VAT: "Une erreur est survenue lors de l'activation de la TVA." + text_successfully_saved: "Le texte a bien été enregistré." + an_error_occurred_while_saving_the_text: "Une erreur est survenue lors de l'enregistrement du texte." + address_and_legal_information_successfully_saved: "L'adresse et les informations légales ont bien été enregistrées." + an_error_occurred_while_saving_the_address_and_the_legal_information: "Une erreur est survenue lors de l'enregistrement de l'adresse et des informations légales." + logo_successfully_saved: "Le logo bien été enregistré." + an_error_occurred_while_saving_the_logo: "Une erreur est survenue lors de l'enregistrement du logo." + + members: + # gestion des utilisateurs, des groupes, des étiquettes, etc. + users_management: "Gestion des utilisateurs" + members: "Membres" + search_for_an_user: "Recherchez un utilisateur" + add_a_new_member: "Ajouter un nouveau membre" + reservations: "Réservations" + email: "Courriel" + phone: "Tel." + user_type: "Type utilisateur" + administrators: "Administrateurs" + search_for_an_administrator: "Recherchez un administrateur" + add_a_new_administrator: "Ajouter un nouvel administrateur" + groups: "Groupes" + authentication: "Authentification" + do_you_really_want_to_delete_this_administrator_this_cannot_be_undone: "Êtes-vous sûr de vouloir supprimer cet administrateur ? Cette opération est irréversible." + administrator_successfully_deleted: "L'administrateur a bien été supprimé." + unable_to_delete_the_administrator: "L'administrateur n'a pas pu être supprimé." + add_a_group: "Ajouter un groupe" + group_name: "Nom du groupe" + changes_successfully_saved: "Les modifications ont bien été enregistrées." + an_error_occurred_while_saving_changes: "Une erreur est survenue lors de l'enregistrement des modifications." + new_group_successfully_saved: "Le nouveau groupe a bien été enregistré." + an_error_occurred_when_saving_the_new_group: "Une erreur est survenue lors de l'enregistrement du nouveau groupe." + group_successfully_deleted: "Le groupe a bien été supprimé." + unable_to_delete_group_because_some_users_and_or_groups_are_still_linked_to_it: "Le groupe n'a pas pu être supprimé car des utilisateurs et/ou des abonnements sont toujours associés à ce dernier." + add_a_tag: "Ajouter une étiquette" + tag_name: "Nom de l'étiquette" + new_tag_successfully_saved: "La nouvelle étiquette a bien été enregistrée." + an_error_occurred_while_saving_the_new_tag: "Une erreur est survenue lors de l'enregistrement de la nouvelle étiquette." + tag_successfully_deleted: "L'étiquette a bien été supprimée." + an_error_occurred_and_the_tag_deletion_failed: "Une erreur est survenue et l'étiquette n'a pas pu être supprimé." + search_for_an_authentication_provider: "Recherchez un fournisseur d'authentification" + add_a_new_authentication_provider: "Ajouter un nouveau fournisseur d'authentification" + state: "État" + unknown: "Inconnu : " + active: "Actif" + pending: "En attente" + previous_provider: "Fournisseur précédent" + do_you_really_want_to_delete_the_TYPE_authentication_provider_NAME: "Êtes-vous sûr(e) de vouloir supprimer le fournisseur d'authentification {{TYPE}} : {{NAME}} ?" # angular interpolation + authentication_provider_successfully_deleted: "Le fournisseur d'authentification a bien été supprimée." + an_error_occurred_unable_to_delete_the_specified_provider: "Une erreur est survenue : impossible de supprimer le fournisseur spécifié." + + members_new: + # ajouter un membre + add_a_member: "Ajouter un membre" + + members_edit: + # modifier un membre + duration: "Durée :" + expires_at: "Expire le :" + price_: "Prix :" + offer_free_days: "Offrir des jours gratuits" + extend_subscription: "Prolonger l'abonnement" + user_has_no_current_subscription: "L'utilisateur n'a pas d'abonnement en cours." + subscribe_to_a_plan: "Souscrire à un abonnement" + next_trainings: "Les prochaines formations" + passed_trainings: "Les formations passées" + validated_trainings: "Les formations validées" + courses_and_workshops: "Ateliers et stages" + next_courses_and_workshops: "Les prochains stages et ateliers" + no_upcomning_courses_or_workshops: "Aucun stage ou atelier à venir" + NUMBER_full_price_tickets_reserved: "{NUMBER, plural, =0{} one{1 place plein tarif réservée} other{{NUMBER} places plein tarif réservées}}" # messageFormat interpolation + NUMBER_reduced_rate_tickets_reserved: "{NUMBER, plural, =0{} one{1 place à tarif réduit réservée} other{{NUMBER} places à tarif réduit réservées}}" # messageFormat interpolation + passed_courses_and_workshops: "Les stages et ateliers passés" + no_passed_courses_or_workshop: "Aucun stage ou atelier passé" + invoices: "Factures" + invoice_#: "Facture n°" + download_the_refund_invoice: "Télécharger l'avoir" + expiration_date: "Date d'expiration" + you_intentionally_decide_to_extend_the_user_s_subscription_by_offering_him_free_days: "Vous décidez délibérément d'étendre l'abonnement de l'utilisateur en lui offrant des jours gratuits." + you_intentionally_decide_to_extend_the_user_s_subscription_by_charging_him_again_for_his_current_subscription: "Vous décidez délibérément d'étendre l'abonnement de l'utilisateur en lui faisant repayer le prix de l'abonnement qu'il possède actuellement." + until_(expiration_date): "Jusqu'à (date d'expiration) :" + you_successfully_changed_the_expiration_date_of_the_user_s_subscription: "Vous avez bien modifié la date d'expiration de l'abonnement de l'utilisateur" + a_problem_occurred_while_saving_the_date: "Il y a eu un problème lors de l'enregistrement de la date." + new_subscription: "Nouvelle souscription" + you_are_about_to_purchase_a_subscription_to_NAME: "Vous êtes sur le point de souscrire à un abonnement l'utilisateur {{NAME}}." # angular interpolation + subscription_successfully_purchased: "La souscription à l'abonnement a été réalisée avec succès." + a_problem_occurred_while_taking_the_subscription: "Il y a eu un problème lors de la souscription à l'abonnement" + + admins_new: + # ajouter un nouvel administrateur à la plate-forme + add_an_administrator: "Ajouter un administrateur" + administrator_successfully_created_he_will_receive_his_connection_directives_by_email: "L'administrateur a bien été créé. {GENDER, select, female{Elle} other{Il}} recevra ses instructions de connexion par email." # messageFormat interpolation + + authentication_new: + # ajouter un nouveau fournisseur d'authentification (SSO) + add_a_new_authentication_provider: "Ajouter un fournisseur d'authentification" + a_local_database_provider_already_exists_unable_to_create_another: "Un fournisseur de type \"Base de données locale\" existe déjà. Impossible d'en créer un second." + local_provider_successfully_saved: "Le fournisseur local a bien été enregistré." + it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider: "Il est obligatoire d'établir la correspondance entre User.uid et l'API pour pouvoir ajouter ce fournisseur." + security_issue_detected: "Problème de sécurité détecté" + beware_the_oauth2_authenticatoin_provider_you_are_about_to_add_isnt_using_HTTPS: "Attention : le fournisseur d'authentification OAuth 2 que vous êtes sur le point d'ajouter n'utilise pas HTTPS." + this_is_a_serious_security_issue_on_internet_and_should_never_be_used_except_for_testing_purposes: "Ceci constitue un grave problème de sécurité sur internet et ne devrait jamais être utilisé en dehors de fins de tests." + do_you_really_want_to_continue: "Êtes vous sur de vouloir continuer ?" + unsecured_oauth2_provider_successfully_added: "Le fournisseur OAuth 2.0 (non sécurisé) a bien été enregistré." + oauth2_provider_successfully_added: "Le fournisseur OAuth 2.0 a bien été enregistré." + + authentication_edit: + # modifier un fournisseur d'authentification (SSO) + provider: "Fournisseur :" + it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider: "Il est obligatoire d'établir la correspondance entre User.uid et l'API pour pouvoir ajouter ce fournisseur." + provider_successfully_updated: "Le fournisseur a bien été mis à jour." + an_error_occurred_unable_to_update_the_provider: "Une error est survenue : impossible de mettre à jour le fournisseur." + + statistics: + # tableau de statistiques + evolution: "évolution" + age_filter: "Filtre d'âge" + from_age: "De" # context: age. eg: from 8 to 40 years old + to_age: "à" # context: age. eg: from 8 to 40 years old + _years_old: "ans" + start: "Début :" + end: "Fin :" + custom_filter: "Filtre personnalisé" + NO_: "NON" + criterion: "Critère :" + value: "Valeur :" + exclude: "Exclure" + entries: "Entrées :" + revenue_: "Chiffre d'affaires :" + average_age: "Âge moyen :" + years_old: "ans" + total: "Total" + gender: "Genre" + age: "Âge" + revenue: "Chiffre d'affaires" + unknown: "Inconnu" + user_id: "ID Utilisateur" + + + stats_graphs: + # graphiques de statistiques + data: "Données" + day: "Jour" + week: "Semaine" + start: "Début :" + end: "Fin :" + revenue: "Chiffre d'affaires" + top_list_of: "Palmarès des" + number: "Nombre" + week_short: "Sem." + week_of_START_to_END: "Semaine du {{START}} au {{END}}" # angular interpolation + no_data_for_this_period: "Pas de données pour cette période" + + settings: + # paramètres globaux de l'application et personnalisation + customize_the_application: "Personnalisation de l'application" + general: "Général" + fablab_title: "Titre du FabLab" + fablab_name: "Nom du FabLab" + title_concordance: "Accord du titre" + male: "Masculin." + female: "Féminin." + eg: "ex :" + about: "A propos" + male_preposition: "du" + female_preposition: "de la" + customize_information_messages: "Personnaliser les messages d'informations" + message_of_the_machine_booking_page: "Message sur la page de réservation d'une machine :" + type_the_message_content: "Saisir le contenu du message" + warning_message_of_the_training_booking_page: "Message d'avertissement sur la page de réservation d'une formation :" + information_message_of_the_training_reservation_page: "Message d'information sur la page de réservation d'une formation :" + message_of_the_subscriptions_page: "Message sur la page des abonnements :" + message_of_the_event_page_relative_to_the_reduced_rate_availability_conditions: "Message sur la page d'un évènement, relatif aux conditions d'application du tarif réduit :" + legal_documents: "Documents légaux" + if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user: "Si ces documents ne sont pas renseignés, aucun consentement à leur sujet ne sera demandé à l'utilisateur." + general_terms_and_conditions_(T&C): "Conditions générales de vente (CGV)" + terms_of_service_(TOS): "Conditions générales d'utilisation (CGU)" + customize_the_graphics: "Personnaliser la charte graphique" + for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height: "Pour un rendu optimal, l'image du logo doit être au format PNG avec un fond transparent et d'un aspect environ 3,5 fois plus long que haut." + concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels: "La favicon devrait quant à elle être au format ICO et d'une taille de 16x16 pixels." + remember_to_refresh_the_page_for_the_changes_to_take_effect: "Pensez à rafraîchir la page pour que les modifications prennent effet." + logo_(white_background): "Logo (fond blanc)" + change_the_logo: "Changer le logo" + logo_(black_background): "Logo (font noir)" + favicon: "Favicon" + change_the_favicon: "Changer la favicon" + main_colour: "Couleur principale :" + primary: "Primaire" + secondary_colour: "Couleur secondaire :" + secondary: "Secondaire" + home_page: "Page d'accueil" + news_of_the_home_page: "Brève de la page d'accueil :" + type_your_news_here: "Saisir votre brève ici" + leave_it_empty_to_not_bring_up_any_news_on_the_home_page: "Laisser vide pour ne pas faire apparaître de brève sur la page d'accueil" + twitter_stream: "Flux Twitter :" + name_of_the_twitter_account: "Nom du compte Twitter" + title_of_the_about_page: "Titre page A propos" + shift_enter_to_force_carriage_return: "MAJ. + ENTRÉE pour forcer le retour à la ligne" + input_the_main_content: "Saisir le contenu principal" + input_the_fablab_contacts: "Saisir les Contacts du FabLab" + reservations: "Réservations" + reservations_parameters: "Paramètres des réservations" + confine_the_booking_agenda: "Borner l'agenda de réservation" + opening_time: "Heure d'ouverture" + closing_time: "Heure de fermeture" + ability_for_the_users_to_move_their_reservations: "Possibilité pour l'utilisateur de déplacer ses réservations" + reservations_shifting: "Déplacement des réservations" + prior_period_(hours): "Délai préalable (en heures)" + enabled: "Activé" + disabled: "Désactivé" + ability_for_the_users_to_cancel_their_reservations: "Possibilité pour l'utilisateur d'annuler ses réservations" + reservations_cancelling: "Annulation des réservations" + customization_of_SETTING_successfully_saved: "La personnalisation de {{SETTING}} a bien été enregistrée." # angular interpolation + file_successfully_updated: "Le fichier a bien été mis à jour." diff --git a/config/locales/app.logged.en.yml b/config/locales/app.logged.en.yml new file mode 100644 index 000000000..80226da36 --- /dev/null +++ b/config/locales/app.logged.en.yml @@ -0,0 +1,169 @@ +en: + app: + logged: + profileCompletion: + # user's profile completion page when logging from an SSO provider + confirm_your_new_account: "Confirm your new account" + you_ve_just_created_a_new_account_on_the_fablab_by_logging_from: "You've just created a new account on the {NAME}, by logging from" # messageFormat interpolation + before_letting_you_use_the_application_we_need_some_more_details: "Before letting you use the application, we need some more details" + please_fill_the_following_form: "Please fill the following form" + some_data_may_have_already_been_provided_by_provider_and_cannot_be_modified: "Some data may have already been provided by {{NAME}} and cannot be modified" # angular interpolation + then_click_on_: "Then click on" + _to_start_using_the_application: "to start using the application" + do_you_already_have_an_account: "Do you already have an account?" + do_not_fill_the_form_below_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access: "Do not fill the form below but specify here the code you've received by email, to recover your access" + authentification_code: "Authentification code" + confirm_my_code: "Confirm my code" + an_unexpected_error_occurred_check_your_authentication_code: "An unexpected error occurred, please check your authentication code." + + dashboard: + profile: + # dashboard: my profile + last_activity_on_: "Last activity on" + i_want_to_change_group: "I want to change group!" + your_subscription_expires_on_: "Your subscription expires on" + no_subscriptions: "No subscriptions" + i_want_to_subscribe: "I want to subscribe!" + to_come: "to come" + approved: "approved" + projects: "Projects" + no_projects: "No projects" + labels: "Labels" + no_labels: "No labels" + delete_my_account: "Delete my account" + edit_my_profile: "Edit my profile" + change_my_data: "Change my data" + once_your_data_are_up_to_date_: "Once your data are up to date," + _click_on_the_synchronization_button_opposite_: "click on the synchronization button opposite" + _or_: 'or' + _disconnect_then_reconnect_: "disconnect then reconnect" + _for_your_changes_to_take_effect: "for your changes to take effect." + sync_my_profile: "Sync my profile" + your_group_has_been_successfully_changed: "Your group has been successfully changed." + an_unexpected_error_prevented_your_group_from_being_changed: "An unexpected error prevented your group from being changed." + do_you_really_want_to_delete_your_account: "Do you really want to delete your account?" + all_data_relative_to_your_projects_will_be_lost: "All data relative to your projects will be lost." + your_user_account_has_been_successfully_deleted_goodbye: "Your user account has been successfully deleted. Goodbye." + an_error_occured_preventing_your_account_from_being_deleted: "An error occurred, preventing your account from being deleted." + projects: + # dashboard: my projects + you_dont_have_any_projects: "You don't have any projects." + author: "Author" + collaborator: "Collaborator" + trainings: + # dashboard: my trainings + your_next_trainings: "Your next trainings" + your_previous_trainings: "Your previous trainings" + your_approved_trainings: "Your approved trainings" + events: + # dashboard: my events + your_next_courses_and_workshops: "Your next courses and workshops" + no_courses_or_workshops_to_come: "No courses or workshops to come" + your_previous_courses_and_workshops: "Your previous courses and workshops" + no_passed_courses_or_workshops: "No passed courses or workshops" + NUMBER_normal_places_reserved: "{NUMBER} {NUMBER, plural, =1{normal place reserved}, other{normal places reserved}}" # messageFormat interpolation + NUMBER_reduced_fare_places_reserved: "{NUMBER} {NUMBER, plural, =1{reduced fare place reserved}, other{reduced fare places reserved}" # messageFormat interpolation + invoices: + # dashboard: my invoices + reference_number: "Reference number" + + members_show: + # public profil of a member + members_list: "Members list" + last_activity_: "Last activity" + _on_: "on" + to_come: "to come" + approved: "approved" + projects: "Projects" + no_projects: "No projects" + author: "Author" + collaborator: "Collaborator" + + members: + # list of members accepting to be contacted + the_fablab_members: "The Fab Lab members" + avatar: "Avatar" + + projects_new: + # add a new project + add_a_new_project: "Add a new project" + save_button_value: "Save as draft" # "save as draft" + + projects_edit: + # modify an existing project + save_button_value: "Save" # "save" + edit_the_project: "Edit the project" + publish: "Publish" + + machines_reserve: + # book a machine + machine_planning: "Machine planning" + select_one_or_more_slots_in_the_calendar: "Select one or more slots in the calendar" + you_ve_just_selected_the_slot: "You've just selected the slot:" + datetime_to_time: "{{START_DATETIME}} to {{END_TIME}}" # angular interpolation, eg: Thursday, September 4 1986 8:30 PM to 10:00 PM + cost_of_a_machine_hour: "Cost of a machine hour" + offer_this_slot: "Offer this slot" + confirm_this_slot: "Confirm this slot" + remove_this_slot: "Remove this slot" + to_benefit_from_attractive_prices: "To benefit from attractive prices" + view_our_subscriptions: "View our subscriptions" + or: "or" + cost_of_the_subscription: "Cost of the subscription" + you_have_settled_the_following_machine_hours: "You have settled the following machine hours:" + you_have_settled_a_: "You have settled a" + i_want_to_change_the_following_reservation: "I want to change the following reservation:" + cancel_my_modification: "Cancel my modification" + select_a_new_slot_in_the_calendar: "Select a new slot in the calendar" + cancel_my_selection: "Cancel my selection" + tags_of_the_original_slot: "Tags of the original slot:" + tags_of_the_destination_slot: "Tags of the destination slot:" + confirm_my_modification: "Confirm my modification" + your_booking_slot_was_successfully_moved_from_: "Your booking slot was successfully moved from" + i_ve_reserved: "J'ai réservé" + not_available: "Not available" + unable_to_change_the_reservation: "Unable to change the reservation" + i_reserve: "I reserve" + i_shift: "I shift" + i_change: "I change" + do_you_really_want_to_cancel_this_reservation: "So you really want to cancel this reservation?" + reservation_was_cancelled_successfully: "Reservation was cancelled successfully." + cancellation_failed: "Cancellation failed." + a_problem_occured_during_the_payment_process_please_try_again_later: "A problem occurred during the payment process. Please try again later." + + trainings_reserve: + # book a training + trainings_planning: "Trainings planning" + select_a_slot_in_the_calendar: "Select a slot in the calendar" + you_ve_just_selected_the_slot: "You've just selected the slot:" + datetime_to_time: "{{START_DATETIME}} to {{END_TIME}}" # angular interpolation, eg: Thursday, September 4 1986 8:30 PM to 10:00 PM + offer_this_training: "Offer this training" + confirm_this_slot: "Confirm this slot" + remove_this_slot: "Remove this slot" + to_benefit_from_attractive_prices_and_a_free_training: "To benefit from attractives prices and a free training" + view_our_subscriptions: "View our subscriptions" + subscription_cost: "Subscription cost" + you_have_settled_the_training: "You have settled the training" + training_cost_: "Training cost:" + you_have_settled_a_: "You have settled a" + i_want_to_change_the_following_reservation: "I want to change the following reservation:" + cancel_my_modification: "Cancel my modification" + select_a_new_slot_in_the_calendar: "Select a new slot in the calendar" + cancel_my_selection: "Cancel my selection" + confirm_my_modification: "Confirm my modification" + your_booking_slot_was_successfully_moved_from_: "Your booking slot was successfully moved from" + i_ve_reserved: "I've reserved" + an_error_occured_preventing_the_booked_slot_from_being_modified: "An error occurred preventing the booked slot from being modified." + i_shift: "I shift" + i_change: "I change" + do_you_really_want_to_cancel_this_reservation: "Do you really want to cancel this reservation?" + cancellation_failed: "Cancellation failed." + a_problem_occured_during_the_payment_process_please_try_again_later: "A problem occured during the payment process. Please try again later." + + notifications: + notifications_center: "Notifications center" + mark_all_as_read: "Mark all as read" + notif_title: "Title" + no_new_notifications: "No new notifications." + archives: "Archives" + no_archived_notifications: "No archived notifications." + load_the_next_notifications: "Load the next notifications..." diff --git a/config/locales/app.logged.fr.yml b/config/locales/app.logged.fr.yml new file mode 100644 index 000000000..53044003c --- /dev/null +++ b/config/locales/app.logged.fr.yml @@ -0,0 +1,169 @@ +fr: + app: + logged: + profileCompletion: + # page de complétion du profil utilisateur, à la première connexion depuis un SSO + confirm_your_new_account: "Confirmez votre nouveau compte" + you_ve_just_created_a_new_account_on_the_fablab_by_logging_from: "Vous venez de créer un nouveau compte sur {GENDER, select, male{le} female{la} other{les}} {NAME}, en vous connectant depuis" # messageFormat interpolation + before_letting_you_use_the_application_we_need_some_more_details: "Avant de vous laisser utiliser l'application, nous avons besoin de quelques renseignements supplémentaires" + please_fill_the_following_form: "Merci de compléter le formulaire suivant" + some_data_may_have_already_been_provided_by_provider_and_cannot_be_modified: "Certaines informations peuvent nous avoir été déjà fournies par {{NAME}} et ne sont pas modifiables" # angular interpolation + then_click_on_: "Cliquez ensuite sur" + _to_start_using_the_application: "pour commencer à utiliser l'application" + do_you_already_have_an_account: "Vous possédez déjà un compte ?" + do_not_fill_the_form_below_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access: "Ne remplissez pas le formulaire ci-dessous mais indiquez ici le code qui vous a été fourni par e-mail, cela vous permettra de récupérer l'accès à votre compte." + authentification_code: "Code d'authentification" + confirm_my_code: "Valider mon code" + an_unexpected_error_occurred_check_your_authentication_code: "Une erreur inattendue est survenue, vérifiez votre code d'authentification." + + dashboard: + profile: + # tableau de bord : mon profil + last_activity_on_: "Dernière activité le" + i_want_to_change_group: "Je veux changer de groupe !" + your_subscription_expires_on_: "Votre abonnement expire le" + no_subscriptions: "Aucun abonnement" + i_want_to_subscribe: "Je veux m'abonner !" + to_come: "à venir" + approved: "validée" + projects: "Projets" + no_projects: "Aucun projet" + labels: "Étiquettes" + no_labels: "Aucune étiquette" + delete_my_account: "Supprimer mon compte" + edit_my_profile: "Éditer votre profil" + change_my_data: "Modifier mes données" + once_your_data_are_up_to_date_: "Une fois vos données à jour," + _click_on_the_synchronization_button_opposite_: "cliquez sur le bouton de synchronisation ci-contre" + _or_: 'ou' + _disconnect_then_reconnect_: "déconnectez-vous puis re-connectez vous" + _for_your_changes_to_take_effect: "pour que les modifications soient prises en compte." + sync_my_profile: "Synchroniser mon profil" + your_group_has_been_successfully_changed: "Votre groupe a bien été changé." + an_unexpected_error_prevented_your_group_from_being_changed: "Une erreur inattendue a empêché votre changement de groupe." + do_you_really_want_to_delete_your_account: "Êtes-vous sûr de vouloir supprimer votre compte ?" + all_data_relative_to_your_projects_will_be_lost: "Toutes les données relatives à vos projets seront détruites." + your_user_account_has_been_successfully_deleted_goodbye: "Votre compte utilisateur a bien été supprimé. Au revoir." + an_error_occured_preventing_your_account_from_being_deleted: "Une erreur est survenue qui a empêché la suppression de votre compte." + projects: + # tableau de bord : mes projets + you_dont_have_any_projects: "Vous n'avez aucun projet." + author: "Auteur" + collaborator: "Collaborateur" + trainings: + # tableau de bord : mes formations + your_next_trainings: "Vos prochaines formations" + your_previous_trainings: "Vos formations passées" + your_approved_trainings: "Vos formations validées" + events: + # tableau de bord : mes évènements + your_next_courses_and_workshops: "Vos prochains stages et ateliers" + no_courses_or_workshops_to_come: "Aucun stage ou atelier à venir" + your_previous_courses_and_workshops: "Vos stages et ateliers passés" + no_passed_courses_or_workshops: "Aucun stage ou atelier passé" + NUMBER_normal_places_reserved: "{NUMBER} {NUMBER, plural, =1{place normale réservée}, other{places normales réservées}}" # messageFormat interpolation + NUMBER_reduced_fare_places_reserved: "{NUMBER} {NUMBER, plural, =1{place réservée à tarif réduit}, other{places réservées à tarif réduit}" # messageFormat interpolation + invoices: + # tableau de bord : mes factures + reference_number: "Référence" + + members_show: + # profil public d'un membre + members_list: "Liste des membres" + last_activity_: "Dernière activité" + _on_: "le" + to_come: "à venir" + approved: "validée" + projects: "Projets" + no_projects: "Aucun projet" + author: "Auteur" + collaborator: "Collaborateur" + + members: + # liste des membres qui acceptent d'être contactés + the_fablab_members: "Les membres du Fab Lab" + avatar: "Avatar" + + projects_new: + # ajouter un nouveau projet + add_a_new_project: "Ajouter un nouveau projet" + save_button_value: "Enregistrer comme brouillon" # "save as draft" + + projects_edit: + # modifier un projet existant + save_button_value: "Enregistrer" # "save" + edit_the_project: "Éditer le projet" + publish: "Publier" + + machines_reserve: + # réserver une machine + machine_planning: "Planning machine" + select_one_or_more_slots_in_the_calendar: "Sélectionnez un ou plusieurs créneaux dans le calendrier" + you_ve_just_selected_the_slot: "Vous venez de sélectionner le créneau :" + datetime_to_time: "{{START_DATETIME}} à {{END_TIME}}" # angular interpolation, eg: Thursday, September 4 1986 8:30 PM to 10:00 PM + cost_of_a_machine_hour: "Coût de l'heure machine" + offer_this_slot: "Offrir ce créneau" + confirm_this_slot: "Valider ce créneau" + remove_this_slot: "Supprimer ce créneau" + to_benefit_from_attractive_prices: "Pour bénéficier de prix avantageux" + view_our_subscriptions: "Consultez nos abonnements" + or: "ou" + cost_of_the_subscription: "Coût de l'abonnement" + you_have_settled_the_following_machine_hours: "Vous avez réglé les heures machines suivantes :" + you_have_settled_a_: "Vous avez réglé un" + i_want_to_change_the_following_reservation: "Je souhaite modifier ma réservation suivante :" + cancel_my_modification: "Annuler ma modification" + select_a_new_slot_in_the_calendar: "Sélectionnez un nouveau créneau dans le calendrier" + cancel_my_selection: "Annuler ma sélection" + tags_of_the_original_slot: "Étiquettes du créneau d'origine :" + tags_of_the_destination_slot: "Étiquettes du créneau de destination :" + confirm_my_modification: "Valider ma modification" + your_booking_slot_was_successfully_moved_from_: "Votre créneau de réservation a bien été déplacé du" + i_ve_reserved: "J'ai réservé" + not_available: "Non disponible" + unable_to_change_the_reservation: "Impossible de modifier la réservation" + i_reserve: "Je réserve" + i_shift: "Je déplace" + i_change: "Je change" + do_you_really_want_to_cancel_this_reservation: "Êtes-vous sur de vouloir annuler cette réservation ?" + reservation_was_cancelled_successfully: "La réservation a bien été annulée." + cancellation_failed: "L'annulation a échoué." + a_problem_occured_during_the_payment_process_please_try_again_later: "Il y a eu un problème lors de la procédure de paiement. Veuillez réessayer plus tard." + + trainings_reserve: + # réserver une formation + trainings_planning: "Planning formation" + select_a_slot_in_the_calendar: "Sélectionnez un créneau dans le calendrier" + you_ve_just_selected_the_slot: "Vous venez de sélectionner le créneau :" + datetime_to_time: "{{START_DATETIME}} à {{END_TIME}}" # angular interpolation, eg: Thursday, September 4 1986 8:30 PM to 10:00 PM + offer_this_training: "Offrir cette formation" + confirm_this_slot: "Valider ce créneau" + remove_this_slot: "Supprimer ce créneau" + to_benefit_from_attractive_prices_and_a_free_training: "Pour bénéficier de prix avantageux et d'une formation offerte" + view_our_subscriptions: "Consultez nos abonnements" + subscription_cost: "Coût de l'abonnement" + you_have_settled_the_training: "Vous avez réglé la formation" + training_cost_: "Coût de la formation :" + you_have_settled_a_: "Vous avez réglé un" + i_want_to_change_the_following_reservation: "Je souhaite modifier ma réservation suivante :" + cancel_my_modification: "Annuler ma modification" + select_a_new_slot_in_the_calendar: "Sélectionnez un nouveau créneau dans le calendrier" + cancel_my_selection: "Annuler ma sélection" + confirm_my_modification: "Valider ma modification" + your_booking_slot_was_successfully_moved_from_: "Votre créneau de réservation a bien été déplacé du" + i_ve_reserved: "J'ai réservé" + an_error_occured_preventing_the_booked_slot_from_being_modified: "Une erreur est survenue, empêchant la modification du créneau réservé." + i_shift: "Je déplace" + i_change: "Je change" + do_you_really_want_to_cancel_this_reservation: "Êtes-vous sur de vouloir annuler cette réservation ?" + cancellation_failed: "L'annulation a échoué." + a_problem_occured_during_the_payment_process_please_try_again_later: "Il y a eu un problème lors de la procédure de paiement. Veuillez réessayer plus tard." + + notifications: + notifications_center: "Centre de notifications" + mark_all_as_read: "Tout marquer comme lu" + notif_title: "Intitulé" + no_new_notifications: "Aucune nouvelle notification." + archives: "Archives" + no_archived_notifications: "Aucune notification archivée." + load_the_next_notifications: "Charger les notifications suivantes..." diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml new file mode 100644 index 000000000..8eef9e109 --- /dev/null +++ b/config/locales/app.public.en.yml @@ -0,0 +1,214 @@ +en: + app: + public: + common: + # header and "about" page + about_the_fablab: "About the {NAME}" # messageFormat interpolation + return: "Return" + + # dashboard sections + dashboard: "Dashboard" + my_profile: "My Profile" + my_projects: "My Projects" + my_trainings: "My Trainings" + my_courses_and_workshops: "My Courses and Workshops" + my_invoices: "My Invoices" + + # login/logout + sign_out: "Sign Out" + sign_up: "Sign Up" + sign_in: "Sign In" + + # left menu + notifications: "Notifications" + admin: "Admin" + reduce_panel: "Reduce panel" + + # left menu (public) + home: "Home" + reserve_a_machine: "Reserve a Machine" + trainings_registrations: "Trainings registrations" + courses_and_workshops_registrations: "Courses and Workshops registrations" + projects_gallery: "Projects gallery" + subscriptions: "Subscriptions" + + # left menu (admin) + trainings_monitoring: "Trainings monitoring" + manage_the_calendar: "Manage the Calendar" + manage_the_users: "Manage the Users" + manage_the_invoices: "Manage the invoices" + subscriptions_and_prices: "Subscriptions and Prices" + courses_and_workshops_monitoring: "Courses and Workshops monitoring" + manage_the_machines: "Manage the Machines" + manage_the_projects_elements: "Manage the Projects Elements" + statistics: "Statistics" + customization: "Customization" + + # account creation modal + create_your_account: "Create your account" + man: "Man" + woman: "Woman" + gender_is_required: "Gender is required." + your_first_name: "Your first name" + first_name_is_required: "First name is required." + your_surname: "Your surname" + surname_is_required: "Surname is required." + your_pseudonym: "Your pseudonym" + pseudonym_is_required: "Pseudonym is required." + your_email_address: "Your e-mail address" + email_is_required: "E-mail address is required." + your_password: "Your password" + password_is_required: "Password is required." + password_is_too_short_(minimum_8_characters): "Password is too short (minimum 8 characters)" + type_your_password_again: "Type your password again" + password_confirmation_is_required: "Password confirmation is required." + password_does_not_match_with_confirmation: "Password does not match with confirmation." + your_user_s_profile: "Your user's profile" + user_s_profile_is_required: "User's profile is required." + birth_date: "Birth date" + birth_date_is_required: "Birth date is required." + phone_number: "Phone number" + phone_number_is_required: "Phone number is required." + i_authorize_Fablab_users_registered_on_the_site_to_contact_me: "I authorize FabLab users, registered on the site, to contact me" + i_ve_read_and_i_accept_: "I've read and I accept" + _the_fablab_policy: "the FabLab policy" + + # password modification modal + change_your_password: "Change your password" + your_new_password: "Your new password" + your_password_was_successfully_changed: "Your password was successfully changed." + + # connection modal + connection: "Connection" + password_forgotten: "Forgotten password?" + not_registered_to_the_fablab: "Not registered to the Fablab?" + create_an_account: "Create an account" + wrong_email_or_password: "Wrong e-mail or password." + + # forgotten password modal + your_email_address_is_unknown: "Your e-mail address is unknown." + you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "You will receive in a moment, an e-mail with instructions to reset your password." + + about: + # about page + read_the_fablab_policy: "Read the FabLab policy" + your_fablab_s_contacts: "Your FabLab's contacts" + + home: + # home page + latest_documented_projects: "The latest documented projects" + follow_us: "Follow Us" + latest_tweets: "The latest tweets" + latest_registered_members: "Latest registered members" + create_an_account: "Create an account" + discover_members: "Discover members" + + # next events summary on the home page + fablab_s_next_courses_and_workshops: "Fablab's next courses and workshops" + every_events: "Every events" + from_date_to_date: "From {{START}} to {{END}}" # angular interpolation + on_the_date: "On the {{DATE}}" # angular interpolation + from_time_to_time: "From {{START}} to {{END}}" # angular interpolation + free_entry: "Free entry" + free_admission: "Free admission" + full_price: "Full price" + event_full: "Event full" + + projects_list: + # projects gallery + the_fablab_projects: "The Fab Lab projects" + add_a_project: "Add a project" + filter_projects: "Filter projects:" + reset_all_filters: "Reset all filters" + search: "Search" + all_projects: "All projects" + my_projects: "My projects" + projects_to_whom_i_take_part_in: "Projects to whom I take part in" + all_machines: "All machines" + all_themes: "All themes" + all_materials: "All materials" + load_next_projects: "Load next projects..." + + projects_show: + # details of a projet + project_description: "Project description" + by_name: "By {{NAME}}" # angular interpolation + posted_on_: "Posted on" + CAD_file_to_download: "{COUNT, plural, =0{No CAD file} =1{CAD file to download} other{CAD files to download}" # messageFormat interpolation + licence: "Licence" + report_an_abuse: "Report an abuse" + unauthorized_operation: "Unauthorized operation" + your_report_was_successful_thanks: "Your report was successful. Thank you." + an_error_occured_while_sending_your_report: "An error occurred while sending your report." + your_first_name: "Your first name" + your_first_name_is_required: "Your first name is required." + your_surname: "Your surname" + your_surname_is_required: "Your surname is required." + your_email_address: "Your email address" + your_email_address_is_required: "Your email address is required." + tell_us_why_this_looks_abusive: "Tell us why this looks abusive" + message_is_required: "Message is required." + report: "Report" + + machines_list: + # list of machines + the_fablab_s_machines: "The FabLab's machines" + add_a_machine: "Add a machine" + book: "Book" + _or_the_: " or the " + + machines_show: + # details of a machine + book_this_machine: "Book this machine" + files_to_download: "Files to download" + projects_using_the_machine: "Projects using the machine" + _or_the_: " or the " + unauthorized_operation: "Unauthoried operation" + the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users: "The machine can't be deleted because it's already reserved by some users." + + plans: + # summary of the subscriptions + subcriptions: "Subscriptions" + i_choose_that_plan: "I choose that plan" + i_subscribe_online: "I subscribe online" + i_already_subscribed: "I already subscribed" + more_informations: "More informations" + your_subscription_expires_on_the_DATE: "Your subscription expires on the {{DATE}}" # angular interpolation + my_group: "My group" + his_group: "{GENDER, select, male{His} female{Her} other{Its}} group" # messageFormat interpolation + he_wants_to_change_group: "{ROLE, select, admin{He wants} other{I want}} to change group" # messageFormat interpolation + change_my_group: "Change {ROLE, select, admin{{GENDER, select, male{his} female{her} other{its}}} other{my}} group" # messageFormat interpolation + your_subscription_has_expired_on_the_DATE: "Your subscription has expired on the {{DATE}}" # angular interpolation + you_ve_just_payed_the_: "You've just payed the" + thank_you_your_subscription_is_successful: "Thank you. Your subscription is successful!" + your_group_was_successfully_changed: "Your group was successfully changed." + the_user_s_group_was_successfully_changed: "The user's group was successfully changed." + an_error_prevented_your_group_from_being_changed: "An error prevented your group from being changed." + an_error_prevented_to_change_the_user_s_group: "An error prevented to change the user's group." + an_error_occured_during_the_payment_process_please_try_again_later: "An error occurred during the payment process. Please try again later." + subscription_confirmation: "Subscription confirmation" + here_is_the_NAME_subscription_summary: "Here is the {{NAME}}'s subscription summary:" # angular interpolation + + events_list: + # Fablab's events list + the_fablab_s_courses_and_workshops: "The Fablab's courses and workshops" + + events_show: + # details and booking of an event + event_description: "Event description" + downloadable_documents: "Downloadable documents" + informations_and_booking: "Informations and booking" + beginning: "Beginning:" + ending: "Ending:" + opening_hours: "Opening hours:" + reduced_rate*: "Reduced rate*:" + tickets_still_availables: "Tickets still available:" + sold_out: "Sold out." + free_entry: "Free entry" + ticket: "{NUMBER, plural, one{ticket} other{tickets}}" # messageFormat interpolation + make_a_gift_of_this_reservation: "Make a gift of this reservation" + you_can_find_your_reservation_s_details_on_your_: "You can find your reservation's details on your" + you_booked_(DATE): "You booked ({{DATE}}):" # angular interpolation + book: "Book" + change_the_reservation: "Change the reservation" + you_can_shift_this_reservation_on_the_following_slots: "You can shift this reservation on the following slots:" \ No newline at end of file diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml new file mode 100644 index 000000000..67a3422d3 --- /dev/null +++ b/config/locales/app.public.fr.yml @@ -0,0 +1,214 @@ +fr: + app: + public: + common: + # en-tête et page "à propos" + about_the_fablab: "A propos {GENDER, select, male{du} female{de la} other{des}} {NAME}" # messageFormat interpolation + return: "Retour" + + # sections du tableau de bord + dashboard: "Tableau de bord" + my_profile: "Mon profil" + my_projects: "Mes projets" + my_trainings: "Mes formations" + my_courses_and_workshops: "Mes stages et ateliers" + my_invoices: "Mes factures" + + # connexion / déconnexion + sign_out: "Se déconnecter" + sign_up: "S'inscrire" + sign_in: "Se connecter" + + # menu de gauche + notifications: "Notifications" + admin: "Admin" + reduce_panel: "Réduire le volet" + + # menu de gauche (partie publique) + home: "Accueil" + reserve_a_machine: "Réserver une machine" + trainings_registrations: "Inscriptions formations" + courses_and_workshops_registrations: "Inscriptions stages et ateliers" + projects_gallery: "Galerie de projets" + subscriptions: "Abonnements" + + # menu de gauche (partie admin) + trainings_monitoring: "Suivi formations" + manage_the_calendar: "Gérer le calendrier" + manage_the_users: "Gérer les utilisateurs" + manage_the_invoices: "Gérer les factures" + subscriptions_and_prices: "Abonnements & Tarifs" + courses_and_workshops_monitoring: "Suivi stages et ateliers" + manage_the_machines: "Gérer les machines" + manage_the_projects_elements: "Gérer les éléments projets" + statistics: "Statistiques" + customization: "Personnalisation" + + # fenêtre de création de compte + create_your_account: "Créer votre compte" + man: "Homme" + woman: "Femme" + gender_is_required: "Le genre est requis." + your_first_name: "Votre prénom" + first_name_is_required: "Le prénom est requis." + your_surname: "Votre nom" + surname_is_required: "Le nom est requis." + your_pseudonym: "Votre pseudonyme" + pseudonym_is_required: "Le pseudonyme est requis." + your_email_address: "Votre adresse de courriel" + email_is_required: "L'adresse de courriel est requise." + your_password: "Votre mot de passe" + password_is_required: "Le mot de passe est requis." + password_is_too_short_(minimum_8_characters): "Le mot de passe est trop court (au moins 8 caractères)" + type_your_password_again: "Ressaisissez votre mot de passe" + password_confirmation_is_required: "La confirmation du mot de passe est requise." + password_does_not_match_with_confirmation: "Le mot de passe ne concorde pas avec la confirmation." + your_user_s_profile: "Votre profil utilisateur" + user_s_profile_is_required: "Le profil utilisateur est requis." + birth_date: "Date de naissance" + birth_date_is_required: "La date de naissance est requise." + phone_number: "Numéro de téléphone" + phone_number_is_required: "Le numéro de téléphone est requis." + i_authorize_Fablab_users_registered_on_the_site_to_contact_me: "J'autorise les utilisateurs du Fab Lab inscrits sur le site à me contacter" + i_ve_read_and_i_accept_: "J'ai lu et j'accepte" + _the_fablab_policy: "la charte d'utilisation du Fab Lab" + + # fenêtre de changement de mot de passe + change_your_password: "Modifier votre mot de passe" + your_new_password: "Votre nouveau mot de passe" + your_password_was_successfully_changed: "Votre mot de passe a bien été modifié." + + # fenêtre de connexion + connection: "Connexion" + password_forgotten: "Mot de passe oublié ?" + not_registered_to_the_fablab: "Vous n'êtes pas inscrit au FAB LAB ?" + create_an_account: "Créer un compte" + wrong_email_or_password: "Adresse courriel ou mot de passe incorrect." + + # mot de passe oublié + your_email_address_is_unknown: "Votre adresse de courriel est inconnue." + you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "Vous allez recevoir sous quelques minutes un courriel vous indiquant comment réinitialiser votre mot de passe." + + about: + # page à propos + read_the_fablab_policy: "Consulter les règles d'utilisation du Fab Lab" + your_fablab_s_contacts: "Vos contacts au Fab Lab" + + home: + # page d'accueil + latest_documented_projects: "Les derniers projets documentés" + follow_us: "Suivez-nous" + latest_tweets: "Les derniers tweets" + latest_registered_members: "Derniers members inscrits" + create_an_account: "Créer un compte" + discover_members: "Découvrir les membres" + + # résumé des prochains évènements sur la page d'acceuil + fablab_s_next_courses_and_workshops: "Les prochains ateliers et stages du Fab Lab" + every_events: "Tous les évènements" + from_date_to_date: "Du {{START}} au {{END}}" # angular interpolation + on_the_date: "Le {{DATE}}" # angular interpolation + from_time_to_time: "De {{START}} à {{END}}" # angular interpolation + free_entry: "Entrée libre" + free_admission: "Entrée gratuite" + full_price: "Plein tarif" + event_full: "Événement complet" + + projects_list: + # galerie des projets + the_fablab_projects: "Les projets du FabLab" + add_a_project: "Ajouter un projet" + filter_projects: "Filtrer les projets :" + reset_all_filters: "Réinitialiser tous les filtres" + search: "Rechercher" + all_projects: "Tous les projets" + my_projects: "Mes projets" + projects_to_whom_i_take_part_in: "Les projets auxquels je collabore" + all_machines: "Toutes les machines" + all_themes: "Toutes les thématiques" + all_materials: "Tous les matériaux" + load_next_projects: "Charger les projets suivants ..." + + projects_show: + # détails d'un projet + project_description: "Description du projet" + by_name: "Par {{NAME}}" # angular interpolation + posted_on_: "posté le" + CAD_file_to_download: "{COUNT, plural, =0{Aucun fichier CAO} =1{Fichier CAO à télécharger} other{Fichiers CAO à télécharger}}" # messageFormat interpolation + licence: "Licence" + report_an_abuse: "Signaler un abus" + unauthorized_operation: "Opération non autorisée" + your_report_was_successful_thanks: "Votre signalement a bien été pris en compte, merci." + an_error_occured_while_sending_your_report: "Une erreur est survenue lors de l'envoi de votre signalement." + your_first_name: "Votre prénom" + your_first_name_is_required: "Votre prénom est requis." + your_surname: "Votre nom" + your_surname_is_required: "Votre nom est requis." + your_email_address: "Votre adresse e-mail" + your_email_address_is_required: "Votre adresse e-mail est requise." + tell_us_why_this_looks_abusive: "Dites nous en quoi cela vous semble abusif" + message_is_required: "Le message est requis." + report: "Signaler" + + machines_list: + # liste des machines + the_fablab_s_machines: "Les machines du FabLab" + add_a_machine: "Ajouter une machine" + book: "Réserver" + _or_the_: " ou la " + + machines_show: + # détail d'une machine + book_this_machine: "Réserver cette machine" + files_to_download: "Fichiers à télécharger" + projects_using_the_machine: "Projets utilisant la machine" + _or_the_: " ou la " + unauthorized_operation: "Opération non autorisée" + the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users: "La machine ne peut pas être supprimée car elle a déjà été réservée par des utilisateurs." + + plans: + # page récapitulative des abonnements + subcriptions: "Les abonnements" + i_choose_that_plan: "Je choisis cette formule" + i_subscribe_online: "Je m'abonne en ligne" + i_already_subscribed: "Je suis déjà abonné" + more_informations: "Plus d'infos" + your_subscription_expires_on_the_DATE: "Votre abonnement expire au {{DATE}}" # angular interpolation + my_group: "Mon groupe" + his_group: "Son groupe" # messageFormat interpolation + he_wants_to_change_group: "{ROLE, select, admin{Il veut} other{Je veux}} changer de groupe" # messageFormat interpolation + change_my_group: "Changer {ROLE, select, admin{son} other{mon}} groupe" # messageFormat interpolation + your_subscription_has_expired_on_the_DATE: "Votre abonnement a expiré au {{DATE}}" # angular interpolation + you_ve_just_payed_the_: "Vous venez de régler l'" + thank_you_your_subscription_is_successful: "Merci. Votre abonnement a bien été pris en compte !" + your_group_was_successfully_changed: "Votre groupe a bien été changé." + the_user_s_group_was_successfully_changed: "Le groupe de l'utilisateur a bien été changé." + an_error_prevented_your_group_from_being_changed: "Une erreur a empêché votre changement de groupe." + an_error_prevented_to_change_the_user_s_group: "Une erreur a empêché le changement de groupe de l'utilisateur." + an_error_occured_during_the_payment_process_please_try_again_later: "Il y a eu un problème lors de la procédure de paiement. Veuillez réessayer plus tard." + subscription_confirmation: "Validation de l'abonnement" + here_is_the_NAME_subscription_summary: "Voici le récapitulatif de l'abonnement de {{NAME}} :" # angular interpolation + + events_list: + # liste des évènements du fablab + the_fablab_s_courses_and_workshops: "Les Stages et ateliers du Fab Lab" + + events_show: + # détails d'un événement et réservation + event_description: "Description de l'évènement" + downloadable_documents: "Documents à télécharger" + informations_and_booking: "Informations et réservation" + beginning: "Début :" + ending: "Fin :" + opening_hours: "Horaires :" + reduced_rate*: "Tarif réduit* :" + tickets_still_availables: "Places encore disponibles :" + sold_out: "Événement complet." + free_entry: "Entrée libre" + ticket: "{NUMBER, plural, =0{place} one{place} other{places}}" # messageFormat interpolation + make_a_gift_of_this_reservation: "Offrir cette réservation" + you_can_find_your_reservation_s_details_on_your_: "Vous pouvez retrouver le détail de votre réservation sur votre" + you_booked_(DATE): "Vous avez réservé ({{DATE}}) :" # angular interpolation + book: "Réserver" + change_the_reservation: "Modifier la réservation" + you_can_shift_this_reservation_on_the_following_slots: "Vous pouvez déplacer cette réservation sur les créneaux suivants :" \ No newline at end of file diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml new file mode 100644 index 000000000..ea671c1aa --- /dev/null +++ b/config/locales/app.shared.en.yml @@ -0,0 +1,262 @@ +en: + app: + shared: + buttons: + # translations of common buttons + confirm_changes: "Confirm changes" + consult: "Consult" + edit: "Edit" + change: "Change" + delete: "Delete" + browse: "Browse" + cancel: "Cancel" + close: "Close" + clear: "Clear" + today: "Today" + confirm: "Confirm" + save: "Save" + "yes": "Yes" + "no": "No" + apply: "Apply" + confirm_(payment_on_site): "Confirm (Payment on site)" + + elements: + # various translations used many times in the application + group: "Group" + subscription: "Subscription" + trainings: "Trainings" + no_trainings: "No trainings" + confirmation_required: "Confirmation required" + description: "Description" + machines: "Machines" + materials: "Materials" + date: "Date" + price: "Price" + download_the_invoice: "Download the invoice" + download_the_credit_note: "Download the credit note" + no_invoices_for_now: "No invoices for now." + email_address: "Email address" + user: "User" + pseudonym: "Pseudonym" + all_day: "All day" + reservation_was_successfully_cancelled: "Reservation was successfully cancelled." + title: "Title" + total_: "TOTAL :" + full_price_: "Full price:" + reduced_rate: "Reduced rate" + reduced_rate_: "Reduced rate:" + rough_draft: "Rough draft" + machines_and_materials: "Machines and materials" + collaborators: "Collaborators" + summary: "Summary" + you_ve_just_selected_a_: "You've just selected a" # you_ve_just_selected_a_ + _subscription + _subscription: "subscription" + confirm_and_pay: "Confirm and pay" + your_invoice_will_be_available_soon_from_your_: "Your invoice will be available soon form your" + add_an_event: "Add ane event" + load_the_next_courses_and_workshops: "Load the next courses and workshops..." + dates: "Dates:" + thank_you_your_payment_has_been_successfully_registered: "Thank you. Your payment has been successfully registered !" + surname: "Surname" + first_name: "First Name" + address: "Address" + interests: "Interests" + CAD_softwares_mastered: "CAD Softwares mastered" + name: "Name" + step_N: "Step {{INDEX}}" # angular interpolation + themes: "Themes" + tags: "Tages" + technical_specifications: "Technical specifications" + online_payment: "Online payment" + type: "Type" + partner: "Partner" + standard: "Standard" + year: "Year" + month: "Month" + subscription_price: "Subscription price" + model: "Model" + from_date: "From" # context: date. eg: "from 01/01 to 01/05" + from_time: "From" # context: time. eg. "from 18:00 to 21:00" + to_date: "to" # context: date. eg: "from 01/01 to 01/05" + to_time: "to" # context: time. eg. "from 18:00 to 21:00" + + messages: + you_will_lose_any_unsaved_modification_if_you_quit_this_page: "You will lose any unsaved modification if you quit this page" + you_will_lose_any_unsaved_modification_if_you_reload_this_page: "You will lose any unsaved modification if you reload this page" + + user: + # user edition form + add_an_avatar: "Add an avatar" + pseudonym: "Pseudonym" + email_address_is_required: "E-mail address is required." + change_password: "Change password" + new_password: "New password" + confirmation_of_new_password: "Confirmation of new password" + confirmation_of_password_is_required: "Confirmation of password is required." + confirmation_of_password_is_too_short_(minimum_8_characters): "Confirmation of password is too short (minimum 8 characters)." + confirmation_mismatch_with_password: "Confirmation mismatch with password." + date_of_birth: "Date of birth" + date_of_birth_is_required: "Date of birth is required." + + project: + # project edition form + name_is_required: "Name is required." + illustration: "Illustration" + add_an_illustration: "Add an illustration" + CAD_file: "CAD file" + add_a_new_file: "Add a new file" + description_is_required: "Description is required." + steps: "Steps" + step_title: "Step title" + add_a_picture: "Add a picture" + change_the_picture: "Change the picture" + delete_the_step: "Delete the step" + add_a_new_step: "Add a new step" + publish_your_project: "Publish your project" + employed_materials: "Employed materials" + employed_machines: "Employed machines" + creative_commons_licences: "Creative Commons licences" + + machine: + # machine edition form + name_is_required: "The name is required." + illustration: "Illustration" + add_an_illustration: "Add an illustration." + description_is_required: "Description is required." + technical_specifications_are_required: "Technical specifications are required." + attached_files_(pdf): "Attached files (pdf)" + attach_a_file: "Attach a file" + add_an_attachment: "Add an attachment" + validate_your_machine: "Validate your machine" + + plan_subscribe: + # frame to select a plan to subscribe + subscribe_online: "subscribe online" + do_not_subscribe: "do not subscribe" + + member_select: + # admin: choose a member to interact with + select_a_member: "Select a member" + please_select_a_member_first: "Please select a member first" + + stripe: + # stripe payment modal + i_have_read_and_accept_: "I have read, and accept" + _the_general_terms_and_conditions: "the general terms and conditions." + enter_your_card_number: "Enter your card number" + confirm_my_payment_of_: "Confirm my payment of" # context: confirm my payment of $20.00 + + valid_reservation_modal: + # dialog of on site payment for reservations + booking_confirmation: "Booking confirmation" + here_is_the_summary_of_the_slots_to_book_for_the_current_user: "Here is the summary of the slots to book for the current user:" + + event: + # event edition form (courses/workshops) + title_is_required: "Title is required." + matching_visual: "Matching visual" + choose_a_picture: "Choose a picture" + description_is_required: "Description is required." + attachments: "Attachments" + add_a_new_file: "Add a new file" + event_type: "Event type" + dates_and_opening_hours: "Dates and opening hours" + all_day: "All day" + start_date: "Start date" + end_date: "End date" + start_time: "Start time" + end_time: "End time" + recurrence: "Recurrence" + _and_ends_on: "and ends on" + prices_and_availabilities: "Prices and availabilities" + standard_rate: "Standard rate" + 0_=_free: "0 = free" + tickets_available: "Tickets available" + + plan: + # subscription plan edition form + general_informations: "General informations" + name_is_required: "Name is required." + name_length_must_be_less_than_24_characters: "Name length must be less than 24 characters." + type_is_required: "Type is required." + group: "Group" + transversal_(all_groups): "Transversal (all groups)" + group_is_required: "Group is required." + number_of_periods: "Number of periods" + number_of_periods_is_required: "Number of periods is required." + period: "Period" + period_is_required: "Period is required." + price_is_required: "Price is required." + visual_prominence_of_the_subscription: "Visual prominence of the subscription" + on_the_subscriptions_page_the_most_prominent_subscriptions_will_be_placed_at_the_top_of_the_list: "On the subscriptions page, the most prominent subscriptions will be placed at the top of the list." + an_evelated_number_means_a_higher_prominence: "An elevated number means a higher prominence." + rolling_subscription: "Rolling subscription?" + a_rolling_subscription_will_begin_the_day_of_the_first_training: "A rolling subscription will begin the day of the first trainings." + otherwise_it_will_begin_as_soon_as_it_is_bought: "Otherwise, it will begin as soon as it is bought." + information_sheet: "Information sheet" + attach_an_information_sheet: "Attach an information sheet" + notified_partner: "Notified partner" + new_user: "New user ..." + as_part_of_a_partner_subscription_some_notifications_may_be_sent_to_this_user: "As part of a partner subscription, some notifications may be sent to this user." + new_partner: "New partner" + email_address_is_required: "Email address is required." + + user_admin: + # partial form to edit/create an user (admin view) + group: "Group" + group_is_required: "Group is required" + disable_invoices_generation: "Disable invoices generation:" + no_more_invoices_will_be_generated_for_: "No more invoices will be generated for" + _the_payments_carried_out_at_the_reception_: "the payments carried out at the reception" + _regarding_this_user: "regarding this user." + trainings: "Trainings" + + authentication: + # partial form to edit/create an authentication provider (SSO) + provider_name_is_required: "Provider name is required." + authentication_type: "Authentication type" + authentication_type_is_required: "Authentication type is required." + + oauth2: + # edition/creation form of an OAuth2 authentication provider + common_url: "Common URL" + common_url_is_required: "Common URL is required." + provided_url_is_not_a_valid_url: "Provided URL is not a valid URL." + authorization_endpoint: "Authorization endpoint" + oauth2_authorization_endpoint_is_required: "OAuth 2.0 authorization endpoint is required." + provided_endpoint_is_not_valid: "Provided endpoint is not valid." + token_acquisition_endpoint: "Token acquisition endpoint" + oauth2_token_acquisition_endpoint_is_required: "OAuth 2.0 token acquisition endpoint is required." + profil_edition_url: "Profil edition URL" + profile_edition_url_is_required: "Profile edition URL is required." + client_identifier: "Client identifier" + oauth2_client_identifier_is_required: "OAuth 2.0 client identifier is required." + obtain_it_when_registering_with_your_provider: "Obtain it when registering with your provider." + client_secret: "Client secret" + oauth2_client_secret_is_required: "OAuth 2.0 client secret is required." + define_the_fields_mapping: "Define the fields mapping" + add_a_match: "Add a match" + field: "Fiels" + api_endpoint_url: "API endpoint URL" + api_type: "API type" + api_fields: "API fields" + + confirm_modify_slot_modal: + # machine/training slot modification modal + change_the_slot: "Change the slot" + do_you_want_to_change_your_booking_slot_initially_planned_at: "Do you want to change your booking slot, initially planned at:" + do_you_want_to_change_NAME_s_booking_slot_initially_planned_at: "Do you want to change {{NAME}}'s booking slot, initially planned at:" # angular interpolation + cancel_this_reservation: "Cancel this reservation" + i_want_to_change_date: "I want to change date" + + request_training_modal: + # modal introducing that a training is requested before booking a machine + to_book_the_MACHINE_you_must_have_completed_the_TRAINING: "To book the \"{{MACHINE}}\" you must have completed the {{TRAINING}}." # angular interpolation + register_for_the_training: "Enroll in the training" + i_dont_want_to_register_now: "I don't want to enroll now" + + training_reservation_modal: + # modal introducing that a user must wait for his training being validated before booking a machine + machine_reservation: "Machine reservation" + you_must_wait_for_your_training_is_being_validated_by_the_fablab_team_to_book_this_machine: "You must wait for your training is being validated by the FabLab team to book this machine." + your_training_will_occur_: "Your training will occur" diff --git a/config/locales/app.shared.fr.yml b/config/locales/app.shared.fr.yml new file mode 100644 index 000000000..743a23843 --- /dev/null +++ b/config/locales/app.shared.fr.yml @@ -0,0 +1,262 @@ +fr: + app: + shared: + buttons: + # traductions de boutons communs + confirm_changes: "Valider les modifications" + consult: "Consulter" + edit: "Éditer" + change: "Modifier" + delete: "Supprimer" + browse: "Parcourir" + cancel: "Annuler" + close: "Fermer" + clear: "Effacer" + today: "Aujourd'hui" + confirm: "Valider" + save: "Enregistrer" + "yes": "Oui" + "no": "Non" + apply: "Appliquer" + confirm_(payment_on_site): "Valider (Paiement sur place)" + + elements: + # traductions variées utilisées à plusieurs reprises dans l'application + group: "Groupe" + subscription: "Abonnement" + trainings: "Formations" + no_trainings: "Aucune formation" + confirmation_required: "Confirmation requise" + description: "Description" + machines: "Machines" + materials: "Matériaux" + date: "Date" + price: "Prix" + download_the_invoice: "Télécharger la facture" + download_the_credit_note: "Télécharger l'avoir" + no_invoices_for_now: "Aucune facture pour le moment." + email_address: "Adresse de courriel" + user: "Utilisateur" + pseudonym: "Pseudonyme" + all_day: "Toute la journée" + reservation_was_successfully_cancelled: "La réservation a bien été annulée." + title: "Titre" + total_: "TOTAL :" + full_price_: "Plein tarif :" + reduced_rate: "Tarif réduit" + reduced_rate_: "Tarif réduit :" + rough_draft: "Brouillon" + machines_and_materials: "Machines et matériaux" + collaborators: "Les collaborateurs" + summary: "Résumé" + you_ve_just_selected_a_: "Vous venez de sélectionner un" # you_ve_just_selected_a_ + _subscription + _subscription: "abonnement" + confirm_and_pay: "Valider et payer" + your_invoice_will_be_available_soon_from_your_: "Votre facture sera bientôt disponible depuis votre" + add_an_event: "Ajouter un évènement" + load_the_next_courses_and_workshops: "Charger les stages et ateliers suivants ..." + dates: "Dates :" + thank_you_your_payment_has_been_successfully_registered: "Merci. Votre paiement a bien été pris en compte !" + surname: "Nom" + first_name: "Prénom" + address: "Adresse" + interests: "Centres d'intérêts" + CAD_softwares_mastered: "Logiciels de conception maîtrisés" + name: "Nom" + step_N: "Étape {{INDEX}}" # angular interpolation + themes: "Thématiques" + tags: "Étiquettes" + technical_specifications: "Caractéristiques techniques" + online_payment: "Paiement en ligne" + type: "Type" + partner: "Partenaire" + standard: "Standard" + year: "Année" + month: "Mois" + subscription_price: "Coût de l'abonnement" + model: "Modèle" + from_date: "Du" # context: date. eg: "from 01/01 to 01/05" + from_time: "De" # context: time. eg. "from 18:00 to 21:00" + to_date: "au" # context: date. eg: "from 01/01 to 01/05" + to_time: "à" # context: time. eg. "from 18:00 to 21:00" + + messages: + you_will_lose_any_unsaved_modification_if_you_quit_this_page: "Vous perdrez les modifications non enregistrées si vous quittez cette page" + you_will_lose_any_unsaved_modification_if_you_reload_this_page: "Vous perdrez les modifications non enregistrées si vous rechargez cette page" + + user: + # formulaire d'édition du profil utilisateur + add_an_avatar: "Ajouter un avatar" + pseudonym: "Pseudonyme" + email_address_is_required: "L'adresse de courriel est requise." + change_password: "Changer de mot de passe" + new_password: "Nouveau mot de passe" + confirmation_of_new_password: "Confirmation du nouveau mot de passe" + confirmation_of_password_is_required: "La confirmation du mot de passe est requise." + confirmation_of_password_is_too_short_(minimum_8_characters): "La confirmation du mot de passe est trop courte (au moins 8 caractères)." + confirmation_mismatch_with_password: "La confirmation ne concorde pas avec le mot de passe." + date_of_birth: "Date de naissance" + date_of_birth_is_required: "La date de naissance est requise." + + project: + # formulaire d'étition d'un projet + name_is_required: "Le nom est requis." + illustration: "Illustration" + add_an_illustration: "Ajouter un visuel" + CAD_file: "Fichier CAO" + add_a_new_file: "Ajouter un nouveau fichier" + description_is_required: "La description est requise." + steps: "Étapes" + step_title: "Titre de l'étape" + add_a_picture: "Ajouter une image" + change_the_picture: "Modifier l'image" + delete_the_step: "Supprimer l'étape" + add_a_new_step: "Ajouter une nouvelle étape" + publish_your_project: "Publier votre projet" + employed_materials: "Matériaux utilisés" + employed_machines: "Machines utilisées" + creative_commons_licences: "Licences Creative Commons" + + machine: + # formulaire d'édition d'une machine + name_is_required: "Le nom est requis." + illustration: "Visuel" + add_an_illustration: "Ajouter un visuel" + description_is_required: "La description est requise." + technical_specifications_are_required: "Les caractéristiques techniques sont requises." + attached_files_(pdf): "Pièces jointes (pdf)" + attach_a_file: "Joindre un fichier" + add_an_attachment: "Ajouter une pièce jointe" + validate_your_machine: "Valider votre machine" + + plan_subscribe: + # cadre de souscription à un abonnement + subscribe_online: "je m'abonne en ligne" + do_not_subscribe: "je ne souhaite pas m'abonner" + + member_select: + # admin : choisir un membre avec lequel interagir + select_a_member: "Sélectionnez un membre" + please_select_a_member_first: "Veuillez tout d'abord sélectionner un membre" + + stripe: + # fenêtre de paiement stripe + i_have_read_and_accept_: "J'ai bien pris connaissance, et accepte" + _the_general_terms_and_conditions: "les conditions générales de vente." + enter_your_card_number: "Saisissez votre numéro de carte" + confirm_my_payment_of_: "Valider mon paiement de" # contexte : valider mon paiement de 20,00 € + + valid_reservation_modal: + # fenêtre de paiement sur place d'une réservation + booking_confirmation: "Validation réservation" + here_is_the_summary_of_the_slots_to_book_for_the_current_user: "Voici le récapitulatif des créneaux à réserver pour l'utilisateur courant :" + + event: + # formulaire d'édition d'un événement (stage/atelier) + title_is_required: "Le titre est requis." + matching_visual: "Visuel associé" + choose_a_picture: "Choisir une image" + description_is_required: "La description est requise." + attachments: "Pièces jointes" + add_a_new_file: "Ajouter un nouveau fichier" + event_type: "Type d'évènement" + dates_and_opening_hours: "Dates et horaires" + all_day: "Toute la journée" + start_date: "Date de début" + end_date: "Date de fin" + start_time: "Heure de début" + end_time: "Heure de fin" + recurrence: "Récurrence" + _and_ends_on: "et se terminera le" + prices_and_availabilities: "Tarifs et disponibilités" + standard_rate: "Tarif standard" + 0_=_free: "0 = gratuit" + tickets_available: "Places disponibles" + + plan: + # formulaire d'édition d'une formule d'abonnement + general_informations: "Informations générales" + name_is_required: "Le nom est requis." + name_length_must_be_less_than_24_characters: "Le nom doit faire moins de 24 caractères." + type_is_required: "Le type est requis." + group: "Groupe" + transversal_(all_groups): "Transversal (tout les groupes)" + group_is_required: "Le groupe est requis." + number_of_periods: "Nombre de périodes" + number_of_periods_is_required: "Le nombre de périodes est requis." + period: "Période" + period_is_required: "La période est requise." + price_is_required: "Le prix est requis." + visual_prominence_of_the_subscription: "Importance visuelle de l'abonnement" + on_the_subscriptions_page_the_most_prominent_subscriptions_will_be_placed_at_the_top_of_the_list: "Sur la page des abonnements, les abonnements les plus importants seront placés en haut de la liste." + an_evelated_number_means_a_higher_prominence: "Un nombre plus élevé traduit une importance plus élevée." + rolling_subscription: "Abonnement glissant ?" + a_rolling_subscription_will_begin_the_day_of_the_first_training: "Un abonnement glissant prendra effet seulement le jour de la première formation." + otherwise_it_will_begin_as_soon_as_it_is_bought: "Dans le cas contraire, il prendra effet dès sa date d'achat." + information_sheet: "Fiche descriptive" + attach_an_information_sheet: "Joindre une fiche descriptive" + notified_partner: "Partenaire notifié" + new_user: "Nouvel utilisateur ..." + as_part_of_a_partner_subscription_some_notifications_may_be_sent_to_this_user: "Dans le cadre d'un abonnement partenaire, certaines notifications pourront être adressées à cet utilisateur." + new_partner: "Nouveau partenaire" + email_address_is_required: "L'adresse e-mail est requise." + + user_admin: + # formulaire partiel d'édition/création utilisateur (vue admin) + group: "Groupe" + group_is_required: "Le groupe est requis." + disable_invoices_generation: "Désactiver la génération de factures :" + no_more_invoices_will_be_generated_for_: "Plus aucune facture ne sera générée pour" + _the_payments_carried_out_at_the_reception_: "les paiement effectués à l'accueil" + _regarding_this_user: "pour cet utilisateur." + trainings: "Formations" + + authentication: + # formulaire partiel d'édition/création d'un fournisseur d'authentification (SSO) + provider_name_is_required: "Le nom du fournisseur est requis." + authentication_type: "Type d'authentification" + authentication_type_is_required: "Le type d'authentification est requis." + + oauth2: + # formulaire d'édition/création d'un fournisseur d'authentification de type OAuth2 + common_url: "URL commune" + common_url_is_required: "L'URL commune est requise." + provided_url_is_not_a_valid_url: "L'URL fournie n'est pas une URL valide." + authorization_endpoint: "Terminaison d'autorisation" + oauth2_authorization_endpoint_is_required: "La terminaison d'autorisation OAuth 2 est requise." + provided_endpoint_is_not_valid: "La terminaison fournie n'est pas valide." + token_acquisition_endpoint: "Terminaison d'acquisition du jeton" + oauth2_token_acquisition_endpoint_is_required: "La terminaison d'acquisition du jeton OAuth 2 est requise." + profil_edition_url: "URL d'édition du profil" + profile_edition_url_is_required: "L'adresse d'édition du profil utilisateur est requise." + client_identifier: "Identifiant client" + oauth2_client_identifier_is_required: "L'identifiant client OAuth 2 est requis." + obtain_it_when_registering_with_your_provider: "Enregistrez-vous auprès du fournisseur pour l'obtenir." + client_secret: "Secret client" + oauth2_client_secret_is_required: "Le secret client OAuth 2 est requis." + define_the_fields_mapping: "Définir la correspondance des champs" + add_a_match: "Ajouter une correspondance" + field: "Champ" + api_endpoint_url: "Terminaison/URL de l'API" + api_type: "Type d'API" + api_fields: "Champ de l'API" + + confirm_modify_slot_modal: + # fenêtre de modification d'un créneau de réservation machine/formation + change_the_slot: "Modifier le créneau" + do_you_want_to_change_your_booking_slot_initially_planned_at: "Souhaitez-vous changer votre créneau de réservation initialement prévu au :" + do_you_want_to_change_NAME_s_booking_slot_initially_planned_at: "Souhaitez-vous changer le créneau de réservation de {{NAME}}, initialement prévu au :" # angular interpolation + cancel_this_reservation: "Annuler cette réservation" + i_want_to_change_date: "Je veux changer de date" + + request_training_modal: + # fenêtre présentant l'obligation de participer à une formation avant de réserver une machine + to_book_the_MACHINE_you_must_have_completed_the_TRAINING: "Pour réserver la machine \"{{MACHINE}}\" vous devez avoir suivi la {{TRAINING}}." # angular interpolation + register_for_the_training: "S'inscrire à la formation" + i_dont_want_to_register_now: "Je ne souhaite pas m'inscrire pour l'instant" + + training_reservation_modal: + # fenêtre présentant l'obligation d'attendre la validation de la formation pour réserver une machine + machine_reservation: "Réservation machine" + you_must_wait_for_your_training_is_being_validated_by_the_fablab_team_to_book_this_machine: "Il faut attendre que votre formation soit validée par l'équipe du Fab Lab pour réserver cette machine." + your_training_will_occur_: "Votre formation aura lieu le" diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index abccdb087..7ebd598f4 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -18,8 +18,13 @@ en: unconfirmed: "You have to confirm your account before continuing." mailer: confirmation_instructions: + action: + instruction: subject: "Confirmation instructions" reset_password_instructions: + action: + instruction: + ignore_otherwise: subject: "Reset password instructions" unlock_instructions: subject: "Unlock Instructions" diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml index ac9ff4b42..a1477eb79 100644 --- a/config/locales/devise.fr.yml +++ b/config/locales/devise.fr.yml @@ -1,95 +1,64 @@ +# Autres traductions disponibles sur https://github.com/plataformatec/devise/wiki/I18n + fr: devise: confirmations: - confirmed: Votre compte a été confirmé avec succès. Vous êtes désormais connecté(e). - send_instructions: Vous allez recevoir sous quelques minutes un e-mail comportant des instructions pour confirmer votre compte. - send_paranoid_instructions: Si votre e-mail existe sur notre base de données, vous recevrez sous quelques minutes un message avec des instructions pour confirmer votre compte. - new: - resend_confirmation_instructions: Renvoyer les instructions de confirmation + confirmed: "Votre compte a été confirmé avec succès. Vous êtes désormais connecté(e)." + send_instructions: "Vous allez recevoir sous quelques minutes un e-mail comportant des instructions pour confirmer votre compte." + send_paranoid_instructions: "Si votre e-mail existe sur notre base de données, vous recevrez sous quelques minutes un message avec des instructions pour confirmer votre compte." failure: - already_authenticated: Vous êtes déjà connecté(e). - inactive: Votre compte n’est pas encore activé. - invalid: E-mail ou mot de passe incorrect. - invalid_token: Jeton d'authentification incorrect. - locked: Votre compte est verrouillé. - not_found_in_database: E-mail ou mot de passe incorrect. - timeout: Votre session est périmée, veuillez vous reconnecter pour continuer. - unauthenticated: Vous devez vous connecter ou vous enregistrer pour continuer. - unconfirmed: Vous devez confirmer votre compte par e-mail. + already_authenticated: "Vous êtes déjà connecté(e)." + inactive: "Votre compte n’est pas encore activé." + invalid: "E-mail ou mot de passe incorrect." + invalid_token: "Jeton d'authentification incorrect." + locked: "Votre compte est verrouillé." + not_found_in_database: "E-mail ou mot de passe incorrect." + timeout: "Votre session est périmée, veuillez vous reconnecter pour continuer." + unauthenticated: "Vous devez vous connecter ou vous enregistrer pour continuer." + unconfirmed: "Vous devez confirmer votre compte par e-mail." mailer: confirmation_instructions: - action: Confirmer mon e-mail - greeting: Bienvenue %{recipient}! - instruction: ! 'Vous pouvez confirmer votre e-mail grâce au lien ci-dessous:' - subject: Confirmation d'inscription + action: "Confirmer mon e-mail !" + instruction: "Vous pouvez finaliser votre inscription en confirmant votre adresse mail, en cliquant sur le lien suivant :" + subject: "Confirmation d'inscription" reset_password_instructions: - action: Changer mon mot de passe - greeting: Bonjour %{recipient}! - instruction: ! 'Quelqu''un a demandé un lien pour changer votre mot de passe, le voici :' - instruction_2: Si vous n’avez pas émis cette demande, merci d’ignore cet e-mail. - instruction_3: Votre mot de passe ne changera pas tant que vous n’aurez pas cliqué sur ce lien et renseigné un nouveau mot de passe. - subject: Instructions pour changer le mot de passe + action: "Changer mon mot de passe" + instruction: "Quelqu'un a demandé un lien pour changer votre mot de passe. Vous pouvez le faire via le lien ci-dessous." + ignore_otherwise: "Si vous n'êtes pas à l'origine de cette demande, merci d'ignorer ce message." + subject: "Instructions pour changer le mot de passe" unlock_instructions: - action: Débloquer mon compte - greeting: Bonjour %{recipient}! - instruction: ! 'Suivez ce lien pour débloquer votre compte:' - message: Votre compte a été bloqué suite à un nombre d’essais de connexions manqués trop important - subject: Instructions pour déverrouiller le compte + subject: "Instructions pour déverrouiller le compte" omniauth_callbacks: - failure: ! 'Nous ne pouvons pas vous authentifier depuis %{kind} pour la raison suivante : ''%{reason}''.' - success: Autorisé avec succès par votre compte %{kind}. + failure: "Nous ne pouvons pas vous authentifier depuis %{kind} pour la raison suivante : ''%{reason}''." + success: "Autorisé avec succès par votre compte %{kind}." passwords: - no_token: Vous ne pouvez pas accéder à cette page si vous n’y accédez pas depuis un e-mail de réinitialisation de mot de passe. Si vous venez en effet d’un tel e-mail, vérifiez que vous avez copié l’adresse URL en entier. - send_instructions: Vous allez recevoir sous quelques minutes un e-mail vous indiquant comment réinitialiser votre mot de passe. - send_paranoid_instructions: Si votre e-mail existe dans notre base de données, vous recevrez un lien vous permettant de récupérer votre mot de passe. - updated: Votre mot de passe a été modifié avec succès. Vous êtes maintenant connecté(e). - updated_not_active: Votre mot de passe a été modifié avec succès. - new: - forgot_your_password: Mot de passe oublié ? - send_me_reset_password_instructions: Réinitialiser mon mot de passe + no_token: "Vous ne pouvez pas accéder à cette page si vous n’y accédez pas depuis un e-mail de réinitialisation de mot de passe. Si vous venez en effet d’un tel e-mail, vérifiez que vous avez copié l’adresse URL en entier." + send_instructions: "Vous allez recevoir sous quelques minutes un e-mail vous indiquant comment réinitialiser votre mot de passe." + send_paranoid_instructions: "Si votre e-mail existe dans notre base de données, vous recevrez un lien vous permettant de récupérer votre mot de passe." + updated: "Votre mot de passe a été modifié avec succès. Vous êtes maintenant connecté(e)." + updated_not_active: "Votre mot de passe a été modifié avec succès." registrations: - destroyed: Au revoir ! Votre compte a été annulé avec succès. Nous espérons vous revoir bientôt. - signed_up: Bienvenue ! Vous vous êtes enregistré(e) avec succès. - signed_up_but_inactive: Vous vous êtes enregistré(e) avec succès. Cependant, nous n’avons pas pu vous connecter car votre compte n’a pas encore été activé. - signed_up_but_locked: Vous vous êtes enregistré(e) avec succès. Cependant, nous n’avons pas pu vous connecter car votre compte est verrouillé. - signed_up_but_unconfirmed: Un message avec un lien de confirmation vous a été envoyé par mail. Veuillez suivre ce lien pour activer votre compte. - update_needs_confirmation: Vous avez modifié votre compte avec succès, mais nous devons vérifier votre nouvelle adresse e-mail. Veuillez consulter vos e-mails et cliquer sur le lien de confirmation pour finaliser votre nouvelle adresse. - updated: Votre compte a été modifié avec succès. - edit: - are_you_sure: Êtes-vous sûr ?! - cancel_my_account: Supprimer mon compte - leave_blank_if_you_don_t_want_to_change_it: laissez ce champ vide pour le laisser inchangé - title: Éditer %{resource} - we_need_your_current_password_to_confirm_your_changes: nous avons besoin de votre mot de passe actuel pour valider ces modifications - new: - sign_up: Créer votre compte sereale + destroyed: "Au revoir ! Votre compte a été annulé avec succès. Nous espérons vous revoir bientôt." + signed_up: "Bienvenue ! Vous vous êtes enregistré(e) avec succès." + signed_up_but_inactive: "Vous vous êtes enregistré(e) avec succès. Cependant, nous n’avons pas pu vous connecter car votre compte n’a pas encore été activé." + signed_up_but_locked: "Vous vous êtes enregistré(e) avec succès. Cependant, nous n’avons pas pu vous connecter car votre compte est verrouillé." + signed_up_but_unconfirmed: "Un message avec un lien de confirmation vous a été envoyé par mail. Veuillez suivre ce lien pour activer votre compte." + update_needs_confirmation: "Vous avez modifié votre compte avec succès, mais nous devons vérifier votre nouvelle adresse e-mail. Veuillez consulter vos e-mails et cliquer sur le lien de confirmation pour finaliser votre nouvelle adresse." + updated: "Votre compte a été modifié avec succès." sessions: - signed_in: Connecté(e) avec succès. - signed_out: Déconnecté(e) avec succès. - new: - sign_in: Connexion - remember: Se souvenir de moi + signed_in: "Connecté(e) avec succès." + signed_out: "Déconnecté(e) avec succès." unlocks: - send_instructions: Vous allez recevoir un e-mail sous quelques minutes contenant les instructions nécessaires au déblocage de votre compte. - send_paranoid_instructions: Si votre compte existe, vous recevrez un e-mail indiquant comment le déverrouiller sous quelques minutes. - unlocked: Votre compte a été débloqué avec succès. Veuillez vous connecter. - new: - resend_unlock_instructions: Renvoyer les instructions de débloquage - shared: - links: - didn_t_receive_confirmation_instructions: Vous n’avez pas reçu l’e-mail de confirmation ? - didn_t_receive_unlock_instructions: Vous n’avez pas reçu l’e-mail de débloquage ? - forgot_your_password: Mot de passe oublié ? - sign_in: Connexion - sign_in_with_provider: Connexion avec %{provider} - sign_up: Créer un compte + send_instructions: "Vous allez recevoir un e-mail sous quelques minutes contenant les instructions nécessaires au déblocage de votre compte." + send_paranoid_instructions: "Si votre compte existe, vous recevrez un e-mail indiquant comment le déverrouiller sous quelques minutes." + unlocked: "Votre compte a été débloqué avec succès. Veuillez vous connecter." errors: messages: - already_confirmed: a déjà été confirmé(e) - confirmation_period_expired: doit être confirmé(e) en %{period}, veuillez en demander un(e) autre - expired: est périmé, veuillez en demander un autre - not_found: n’a pas été trouvé(e) - not_locked: n’était pas verrouillé(e) + already_confirmed: "a déjà été confirmé(e)" + confirmation_period_expired: "doit être confirmé(e) en %{period}, veuillez en demander un(e) autre" + expired: "est périmé, veuillez en demander un autre" + not_found: "n’a pas été trouvé(e)" + not_locked: "n’était pas verrouillé(e)" not_saved: - one: ! 'une erreur a empêché ce (ou cette) %{resource} d’être enregistré(e) :' - other: ! '%{count} erreurs ont empêché ce (ou cette) %{resource} d’être enregistré(e) :' + one: "une erreur a empêché ce (ou cette) %{resource} d’être enregistré(e) :" + other: "%{count} erreurs ont empêché ce (ou cette) %{resource} d’être enregistré(e) :" diff --git a/config/locales/en.yml b/config/locales/en.yml index 065395716..ee5b81714 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,23 +1,227 @@ -# Files in the config/locales directory are used for internationalization -# and are automatically loaded by Rails. If you want to use locales other -# than English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t 'hello' -# -# In views, this is aliased to just `t`: -# -# <%= t('hello') %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# To learn more, please read the Rails Internationalization guide -# available at http://guides.rubyonrails.org/i18n.html. - en: - hello: "Hello world" + + duration: + # subscription plan duration + year: + one: 'one year' + other: '%{count} years' + month: + one: 'one month' + other: '%{count} months' + week: + one: 'one week' + other: '%{count} weeks' + + time: + formats: + # See http://apidock.com/ruby/DateTime/strftime for a list of available directives + hour_minute: "%I:%M %p" + + errors: &errors + messages: &errors_messages + # CarrierWave + carrierwave_processing_error: "failed to be processed" + carrierwave_integrity_error: "is not of an allowed file type" + carrierwave_download_error: "could not be downloaded" + extension_white_list_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}" + extension_black_list_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}" + rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image? Original Error: %{e}" + mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type? Original Error: %{e}" + mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}" + wrong_size: "is the wrong size (should be %{file_size})" + size_too_small: "is too small (should be at least %{file_size})" + size_too_big: "is too big (should be at most %{file_size})" + + activemodel: + errors: + <<: *errors + + omniauth: + # messages d'erreur lors de l'import d'un compte depuis un SSO + email_already_linked_to_another_account_please_input_your_authentication_code: "E-mail address \"%{OLD_MAIL}\" is already linked to another account, please input your authentication code." + your_username_is_already_linked_to_another_account_unable_to_update_it: "Your username (%{USERNAME}) is already linked to another account, unable to update it." + your_email_address_is_already_linked_to_another_account_unable_to_update_it: "Your e-mail address (%{EMAIL}) is already linked to another account, unable to update it." + this_account_is_already_linked_to_an_user_of_the_platform: "This account %{NAME} is already linked to an user of the platform." + + availabilities: + # availability slots in the calendar + not_available: "Not available" + i_ve_reserved: "I've reserved" + must_be_at_least_1_hour_after_the_start_date: "must be at least 1 hour after the start date" + must_be_associated_with_at_least_1_machine: "must be associated with at least 1 machine" + + members: + # members management + unable_to_change_the_group_while_a_subscription_is_running: "Unable to change the group while a subscription is running" + please_input_the_authentication_code_sent_to_the_address: "Please input the authentication code sent to the e-mail address %{EMAIL}" + your_authentication_code_is_not_valid: "Your authentication code is not valid." + + invoices: + # PDF invoices generation + refund_invoice_reference: "Refund invoice reference: %{REF}" + invoice_reference: "Invoice reference: %{REF}" + code: "Code: %{CODE}" + order_number: "Order #: %{NUMBER}" + invoice_issued_on_DATE: "Invoice issued on %{DATE}" + refund_invoice_issued_on_DATE: "Refund invoice issued on %{DATE}" + cancellation_of_invoice_REF: "Cancellation of invoice %{REF}" + reservation_of_USER_on_DATE_at_TIME: "Reservation of %{USER} on %{DATE} at %{TIME}" + cancellation: "Cancellation" + object: "Object:" + order_summary: "Order summary:" + details: "Details" + amount: "Amount" + subscription_extended_for_free_from_START_to_END: "Subscription extended for free - From %{START} to %{END}" + subscription_NAME_from_START_to_END: "Subscription - From %{START} to %{END}" + machine_reservation_DESCRIPTION: "Machine reservation - %{DESCRIPTION}" + training_reservation_DESCRIPTION: "Training reservation - %{DESCRIPTION}" + courses_and_workshops_reservation_DESCRIPTION: "Courses and Workshops reservation - %{DESCRIPTION}" + full_price_ticket: + one: "One full price ticket" + other: "%{count} full price tickets" + reduced_rate_ticket: + one: "One reduced rate ticket" + other: "%{count} reduced rate tickets" + reservation_other: "Reservation (other)" + total_including_all_taxes: "Total incl. all taxes" + including_VAT_RATE: "Including VAT %{RATE}%" + including_total_excluding_taxes: "Including Total excl. taxes" + including_amount_payed_on_ordering: "Including amount payed on ordering" + total_amount: "Total amount" + refund_on_DATE: "Refund on %{DATE}" + by_stripe_online_payment: "by Stripe (online payment)" + by_cheque: "by cheque" + by_transfer: "by transfer" + by_cash: "by cash" + no_refund: "No refund" + settlement_by_debit_card: "Settlement by debit card" + settlement_done_at_the_reception: "Settlement done at the reception" + on_DATE_at_TIME: "on %{DATE} at %{TIME}," + for_an_amount_of_AMOUNT: "for an amount of %{AMOUNT}" + on_DATE_from_START_to_END: "On %{DATE} from %{START} to %{END}" # eg: on feb. 7 from 7AM to 9AM + from_STARTDATE_to_ENDDATE_from_STARTTIME_to_ENDTIME: "From %{STARTDATE} to %{ENDDATE}, from %{STARTTIME} to %{ENDTIME}" # eg: from feb. 7 to feb. 10, from 6PM to 10PM + subscription_of_NAME_for_DURATION_starting_from_DATE: "Subscription of %{NAME} for %{DURATION} starting from %{DATE}" + subscription_of_NAME_extended_starting_from_STARTDATE_until_ENDDATE: "Subscription of %{NAME} extended (Free days) starting from %{STARTDATE} until %{ENDDATE}" + + trainings: + # training availabilities + i_ve_reserved: "I've reserved" + completed: "Completed" + + export_members: + # members list export to EXCEL format + id: "ID" + surname: "Surname" + first_name: "First name" + email: "E-mail" + gender: "Gender" + age: "Age" + phone: "Phone" + group: "Group" + subscription: "Subscription" + subscription_end_date: "Subscription end date" + validated_trainings: "Validated trainings" + man: "Man" + woman: "Woman" + without_subscriptions: "Without subscriptions" + + export_reservations: + # machines/trainings/events reservations list to EXCEL format + customer_id: "Customer ID" + customer: "Customer" + reservation_date: "Reservation date" + reservation_type: "Reservation type" + reservation_object: "Reservation object" + slots_number_hours_tickets: "Slots number (hours/tickets)" + payment_method: "Payment method" + local_payment: "Payment at the reception" + online_payment: "Online payment" + + export_subscriptions: + # subscriptions list export to EXCEL format + id: "ID" + customer: "Customer" + email: "E-mail" + subscription: "Subscription" + period: "Period" + start_date: "Start date" + expiration_date: "Expiration date" + amount: "Amount" + payment_method: "Payment method" + local_payment: "Payment at the reception" + online_payment: "Online payment" + + api: + notifications: + # internal app notifications + notify_admin_abuse_reported: + an_abuse_was_reported_on_TYPE_ID_NAME_html: "An abuse was reported on %{TYPE} %{ID}: %{NAME}." + notify_admin_invoicing_changed: + invoices_generation_was_STATUS_for_user_NAME_html: "Invoices generation was {STATUS, select, true{disabled} other{enabled}} for user {NAME}." # messageFormat interpolation + notify_admin_member_create_reservation: + a_RESERVABLE_reservation_was_made_by_USER_html: "A %{RESERVABLE} reservation was made by %{USER}." + notify_admin_profile_complete: + account_imported_from_PROVIDER_(UID)_has_completed_its_informations_html: "Account imported from %{PROVIDER} (%{UID}) has completed its informations." + notify_admin_slot_is_canceled: + USER_s_reservation_on_the_DATE_was_cancelled_remember_to_generate_a_refund_invoice_if_applicable_html: "%{USER}'s reservation, on the %{DATE}, was cancelled. Remember to generate a refund invoice if applicable." + notify_admin_slot_is_modified: + a_booking_slot_was_modified: "A booking slot was modified." + notify_admin_subscribed_plan: + subscription_PLAN_has_been_subscribed_by_USER_html: "Subscription %{PLAN} has been subscribed by %{USER}." + notify_admin_subscription_canceled: + USER_s_subscription_has_been_cancelled: "%{USER}'s subscription has been cancelled." + notify_admin_subscription_extended: + subscription_PLAN_of_the_member_USER_has_been_extended_FREE_until_DATE_html: "Subscription {PLAN} of the member {USER} has been extended {FREE, select, true{for free} other{}} until {DATE}." # messageFormat interpolation + notify_admin_subscription_is_expired: + USER_s_subscription_has_expired: "%{USER}'s subscription has expired." + notify_admin_subscription_will_expire_in_7_days: + USER_s_subscription_will_expire_in_7_days: "%{USER}'s subscription will expire in 7 days." + notify_admin_user_group_changed: + user_NAME_changed_his_group_html: "User {NAME} changed {GENDER, select, true{his} other{her}} group." # messageFormat interpolation + notify_admin_user_merged: + user_NAME_has_merged_his_account_with_the_one_imported_from_PROVIDER_(UID)_html: "User {NAME} has merged {GENDER, select, true{his} other{her}} account with the one imported from {PROVIDER} ({UID})." # messageFormat interpolation + notify_admin_when_project_published: + project_NAME_has_been_published_html: "Project %{NAME} has been published." + notify_admin_when_user_is_created: + a_new_user_account_has_been_created_NAME_EMAIL_html: "A new user account has been created: %{NAME} <%{EMAIL}>." + notify_admin_when_user_is_imported: + a_new_user_account_has_been_imported_from_PROVIDER_(UID)_html: "A new user account has been imported from: %{PROVIDER} (%{UID})." + notify_member_create_reservation: + your_reservation_RESERVABLE_was_successfully_saved_html: "Your reservation %{RESERVABLE} was successfully saved." + notify_member_slot_is_canceled: + your_reservation_RESERVABLE_of_DATE_was_successfully_cancelled: "Your reservation %{RESERVABLE} of %{DATE} was successfully cancelled." + notify_member_slot_is_modified: + your_reservation_slot_was_successfully_changed: "Your reservation slot was successfully changed." + notify_member_subscribed_plan: + you_have_subscribed_to_PLAN_html: "You have subscribed tp %{PLAN}." + notify_member_subscribed_plan_is_changed: + you_have_changed_your_subscription_to_PLAN_html: "You have changed your subscription to %{PLAN}." + notify_member_subscription_canceled: + your_subscription_PLAN_was_successfully_cancelled_html: "Your subscription %{PLAN} was successfully cancelled." + notify_member_subscription_extended: + your_subscription_PLAN_has_been_extended_FREE_until_DATE_html: "Your subscription {PLAN} has been extended {FREE, select, true{for free} other{}} until {DATE}." # messageFormat interpolation + notify_member_subscription_is_expired: + your_subscription_has_expired: "Your subscription has expired." + notify_member_subscription_will_expire_in_7_days: + your_subscription_will_expire_in_7_days: "Your subscription will expire in 7 days." + notify_partner_subscribed_plan: + subscription_partner_PLAN_has_been_subscribed_by_USER_html: "Partner subscription %{PLAN} has been subscribed by %{USER}." + notify_project_author_when_collaborator_valid: + USER_became_collaborator_of_your_project: "%{USER} became collaborator of your project:" + notify_project_collaborator_to_valid: + you_are_invited_to_collaborate_on_the_project: "You are invited to collaborate on the project:" + notify_user_auth_migration: + your_account_was_migrated: "You account was successfully migrated to the new authentication system." + notify_user_profile_complete: + your_profile_was_completed: "Your profile was successfully completed, you now have access to the entire platform." + notify_user_training_valid: + your_TRAINING_was_validated_html: "Your training %{TRAINING} was successfully validated." + notify_user_user_group_changed: + your_group_has_changed: "Your group has changed." + notify_user_when_avoir_ready: + your_avoir_is_ready_html: "Your refund invoice #%{REFERENCE}, of %{AMOUNT}, is ready. Click here to download." + notify_user_when_invoice_ready: + your_invoice_is_ready_html: "Your invoice #%{REFERENCE}, of %{AMOUNT} is ready. Click here to download." + undefined_notification: + unknown_notification: "Unknown notification" + notification_ID_wrong_type_TYPE_unknown: "Notification %{ID} wrong (type %{TYPE} unknown)" \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4fa5901c4..3525a30f3 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1,242 +1,227 @@ fr: - date: - formats: - default: "%d/%m/%Y" - short_default: "%d/%m/%y" - short: "%e %b" - long: "%e %B %Y" - medium: "%e %b %Y" - day_name: "%A" - simple: "%A %e %B" - day_names: - - dimanche - - lundi - - mardi - - mercredi - - jeudi - - vendredi - - samedi - abbr_day_names: - - dim - - lun - - mar - - mer - - jeu - - ven - - sam - month_names: - - ~ - - janvier - - février - - mars - - avril - - mai - - juin - - juillet - - août - - septembre - - octobre - - novembre - - décembre - abbr_month_names: - - ~ - - jan - - fév - - mar - - avr - - mai - - juin - - juil - - août - - sept - - oct - - nov - - déc - order: - - :day - - :month - - :year + duration: + # durée d'une formule d'abonnement + year: + one: 'un an' + other: '%{count} ans' + month: + one: 'un mois' + other: '%{count} mois' + week: + one: 'une semaine' + other: '%{count} semaines' time: formats: - default: "%d %B %Y %H:%M" - short: "%d %b %H:%M" - long: "%A %d %B %Y %H:%M" + # Liste des directives disponibles sur http://apidock.com/ruby/DateTime/strftime hour_minute: "%H:%M" - hour_minute_long: "%Hh%M" - am: 'am' - pm: 'pm' - - datetime: - distance_in_words: - half_a_minute: "une demi-minute" - less_than_x_seconds: - zero: "moins d'une seconde" - one: "moins d'une seconde" - other: "moins de %{count} secondes" - x_seconds: - one: "1 seconde" - other: "%{count} secondes" - less_than_x_minutes: - zero: "moins d'une minute" - one: "moins d'une minute" - other: "moins de %{count} minutes" - x_minutes: - one: "il y a 1 minute" - other: "il y a %{count} minutes" - about_x_hours: - one: "environ une heure" - other: "environ %{count} heures" - x_days: - one: "1 jour" - other: "%{count} jours" - about_x_months: - one: "environ un mois" - other: "environ %{count} mois" - x_months: - one: "1 mois" - other: "%{count} mois" - about_x_years: - one: "environ un an" - other: "environ %{count} ans" - over_x_years: - one: "plus d'un an" - other: "plus de %{count} ans" - almost_x_years: - one: "presqu'un an" - other: "presque %{count} ans" - prompts: - year: "Année" - month: "Mois" - day: "Jour" - hour: "Heure" - minute: "Minute" - second: "Seconde" - - number: - format: - separator: "," - delimiter: " " - precision: 3 - significant: false - strip_insignificant_zeros: false - currency: - format: - format: "%n %u" - unit: "€" - separator: "," - delimiter: " " - precision: 2 - significant: false - strip_insignificant_zeros: false - percentage: - format: - delimiter: "" - precision: - format: - delimiter: "" - human: - format: - delimiter: "" - precision: 2 - significant: true - strip_insignificant_zeros: true - storage_units: - format: "%n %u" - units: - byte: - one: "octet" - other: "octets" - kb: "ko" - mb: "Mo" - gb: "Go" - tb: "To" - decimal_units: - format: "%n %u" - units: - unit: "" - thousand: "millier" - million: "million" - billion: "milliard" - trillion: "billion" - quadrillion: "million de milliards" - - distance: - centi: - one: "centimètre" - other: "centimètres" - unit: - one: "mètre" - other: "mètres" - thousand: - one: "km" - other: "kms" - billion: "gazillion-distance" - - support: - array: - words_connector: ", " - two_words_connector: " et " - last_word_connector: " et " - errors: &errors - format: "%{attribute} : %{message}" messages: &errors_messages - inclusion: "n'est pas inclus(e) dans la liste" - exclusion: "n'est pas disponible" - invalid: "n'est pas valide" - confirmation: "ne concorde pas avec la confirmation" - accepted: "doit être accepté(e)" - empty: "champ obligatoire" - blank: "champ obligatoire" - too_long: - one: "est trop long (pas plus d'un caractère)" - other: "est trop long (pas plus de %{count} caractères)" - too_short: - one: "est trop court (au moins un caractère)" - other: "est trop court (au moins %{count} caractères)" - wrong_length: - one: "ne fait pas la bonne longueur (doit comporter un seul caractère)" - other: "ne fait pas la bonne longueur (doit comporter %{count} caractères)" - not_a_number: "n'est pas un nombre" - not_an_integer: "doit être un nombre entier" - greater_than: "doit être supérieur à %{count}" - greater_than_or_equal_to: "doit être supérieur ou égal à %{count}" - equal_to: "doit être égal à %{count}" - less_than: "doit être inférieur à %{count}" - less_than_or_equal_to: "doit être inférieur ou égal à %{count}" - odd: "doit être impair" - even: "doit être pair" - taken: "n'est pas disponible" - record_invalid: "La validation a échoué : %{errors}" - # ***************** CarrierWave ********************* # - carrierwave_processing_error: failed to be processed - carrierwave_integrity_error: is not of an allowed file type - carrierwave_download_error: could not be downloaded - extension_white_list_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}" - extension_black_list_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}" - rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image? Original Error: %{e}" - mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type? Original Error: %{e}" - mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}" + # CarrierWave + carrierwave_processing_error: "n'a pas pu être traité" + carrierwave_integrity_error: "n'est pas d'un type de fichier autorisé" + carrierwave_download_error: "ne peut pas être téléchargé" + extension_white_list_error: "Vous n'êtes pas autorisé à envoyer des fichiers %{extension}, les types autorisés sont : %{allowed_types}" + extension_black_list_error: "Vous n'êtes pas autorisé à envoyer des fichiers %{extension}, les types interdits sont : %{prohibited_types}" + rmagick_processing_error: "La manipulation avec rmagick a échoué, peut-être ne s'agit-il pas d'une image ? Erreur d'origine : %{e}" + mime_types_processing_error: "Le traitement avec MIME::Types a échoué, le content-type est-il correct ? Erreur d'origine : %{e}" + mini_magick_processing_error: "La manipulation avec MiniMagick a échoué, peut-être ne s'agit-il pas d'une image ? Erreur d'origine : %{e}" wrong_size: "ne fait pas la taille du fichier (doit comporter %{file_size})" - size_too_small: "est trop petit (au moins %{file_size})" - size_too_big: "est trop grand (pas plus de %{file_size})" - - template: &errors_template - header: - one: "Impossible d'enregistrer ce(tte) %{model} : 1 erreur" - other: "Impossible d'enregistrer ce(tte) %{model} : %{count} erreurs" - body: "Veuillez vérifier les champs suivants : " + size_too_small: "est trop petite (au moins %{file_size})" + size_too_big: "est trop grande (pas plus de %{file_size})" activemodel: errors: <<: *errors - notifications_mailer: - send_mail_by: - subject_notify_admin_when_project_published: "Un projet a été publié." - subject_notify_project_collaborator_to_valid: "Invitation à collaborer sur un projet" - subject_notify_project_author_when_collaborator_valid: "Un nouveau collaborateur dans votre projet" - subject_notify_admin_when_user_is_created: "Un compte utilisateur a été créé" + omniauth: + # messages d'erreur lors de l'import d'un compte depuis un SSO + email_already_linked_to_another_account_please_input_your_authentication_code: "L'adresse de courriel \"%{OLD_MAIL}\" est déjà associée à un compte utilisateur, merci de saisir votre code d'authentification." + your_username_is_already_linked_to_another_account_unable_to_update_it: "Votre nom d'utilisateur (%{USERNAME}) est déjà associée à un autre compte utilisateur, impossible de le mettre à jour." + your_email_address_is_already_linked_to_another_account_unable_to_update_it: "Votre adresse de courriel (%{EMAIL}) est déjà associée à un autre compte utilisateur, impossible de la mettre à jour." + this_account_is_already_linked_to_an_user_of_the_platform: "Ce compte %{NAME} est déjà lié à un utilisateur de la plate-forme." + + availabilities: + # créneaux de disponibilité dans le calendrier + not_available: "Non disponible" + i_ve_reserved: "J'ai réservé" + must_be_at_least_1_hour_after_the_start_date: "doit être au moins 1 heure après la date de début" + must_be_associated_with_at_least_1_machine: "doit être associé avec au moins 1 machine" + + members: + # gestion des membres + unable_to_change_the_group_while_a_subscription_is_running: "Impossible de changer le groupe tant qu'un abonnement est en cours" + please_input_the_authentication_code_sent_to_the_address: "Merci d'enter le code d'authentification qui a été envoyé à l'adresse de courriel %{EMAIL}" + your_authentication_code_is_not_valid: "Votre code d'authentification n'est pas valide." + + invoices: + # génération des factures en PDF + refund_invoice_reference: "Référence de l'avoir : %{REF}" + invoice_reference: "Référence facture : %{REF}" + code: "Code : %{CODE}" + order_number: "N° Commande : %{NUMBER}" + invoice_issued_on_DATE: "Facture éditée le %{DATE}" + refund_invoice_issued_on_DATE: "Avoir édité le %{DATE}" + cancellation_of_invoice_REF: "Annulation de la facture %{REF}" + reservation_of_USER_on_DATE_at_TIME: "Réservation de %{USER} le %{DATE} à %{TIME}" + cancellation: "Annulation" + object: "Objet :" + order_summary: "Récapitulatif de la commande :" + details: "Détails" + amount: "Montant" + subscription_extended_for_free_from_START_to_END: "Abonnement prolongé gratuitement - Du %{START} au %{END}" + subscription_NAME_from_START_to_END: "Abonnement - Du %{START} au %{END}" + machine_reservation_DESCRIPTION: "Réservation Machine - %{DESCRIPTION}" + training_reservation_DESCRIPTION: "Réservation Formation - %{DESCRIPTION}" + courses_and_workshops_reservation_DESCRIPTION: "Réservation Ateliers et Stages - %{DESCRIPTION}" + full_price_ticket: + one: "Une place plein tarif" + other: "%{count} places plein tarif" + reduced_rate_ticket: + one: "Une place à tarif réduit" + other: "%{count} places à tarif réduit" + reservation_other: "Réservation (autre)" + total_including_all_taxes: "Total TTC" + including_VAT_RATE: "Dont TVA %{RATE}%" + including_total_excluding_taxes: "Dont total HT" + including_amount_payed_on_ordering: "Dont montant payé à la commande" + total_amount: "Montant total" + refund_on_DATE: "Remboursement le %{DATE}" + by_stripe_online_payment: "par Stripe (paiement en ligne)" + by_cheque: "par chèque" + by_transfer: "par virement" + by_cash: "en espèces" + no_refund: "Pas de remboursement" + settlement_by_debit_card: "Règlement effectué par carte bancaire" + settlement_done_at_the_reception: "Règlement effectué à l'accueil" + on_DATE_at_TIME: "le %{DATE} à %{TIME}," + for_an_amount_of_AMOUNT: "pour un montant de %{AMOUNT}" + on_DATE_from_START_to_END: "Le %{DATE} de %{START} à %{END}" # eg: on feb. 7 from 7AM to 9AM + from_STARTDATE_to_ENDDATE_from_STARTTIME_to_ENDTIME: "Du %{STARTDATE} au %{ENDDATE}, de %{STARTTIME} à %{ENDTIME}" # eg: from feb. 7 to feb. 10, from 6PM to 10PM + subscription_of_NAME_for_DURATION_starting_from_DATE: "Abonnement de %{NAME} pour %{DURATION} à compter du %{DATE}" + subscription_of_NAME_extended_starting_from_STARTDATE_until_ENDDATE: "Prolongement Abonnement (Jours gratuits) de %{NAME} à compter du %{STARTDATE} jusqu'au %{ENDDATE}" + + trainings: + # disponibilités formations + i_ve_reserved: "J'ai réservé" + completed: "Effectué" + + export_members: + # export de la liste des members au format EXCEL + id: "ID" + surname: "Nom" + first_name: "Prénom" + email: "Courriel" + gender: "Genre" + age: "Âge" + phone: "Tel." + group: "Groupe" + subscription: "Abonnement" + subscription_end_date: "Date de fin de l'abonnement" + validated_trainings: "Formations validées" + man: "Homme" + woman: "Femme" + without_subscriptions: "Sans Abonnement" + + export_reservations: + # export de la liste des réservations machines/formations/évènements au format EXCEL + customer_id: "ID client" + customer: "Client" + reservation_date: "Date de réservation" + reservation_type: "Type de réservation" + reservation_object: "Objet de la réservation" + slots_number_hours_tickets: "Nombre de créneaux (heures/places)" + payment_method: "Méthode de paiement" + local_payment: "Paiement à l'accueil" + online_payment: "Paiement en ligne" + + export_subscriptions: + # export de la liste des abonnements au format EXCEL + id: "ID" + customer: "Client" + email: "Courriel" + subscription: "Abonnement" + period: "Période" + start_date: "Date de début" + expiration_date: "Date d'expiration" + amount: "Montant" + payment_method: "Méthode de paiement" + local_payment: "Paiement à l'accueil" + online_payment: "Paiement en ligne" + + api: + notifications: + # notifications internes à l'application + notify_admin_abuse_reported: + an_abuse_was_reported_on_TYPE_ID_NAME_html: "Un abus a été signalé sur %{TYPE} %{ID} : %{NAME}." + notify_admin_invoicing_changed: + invoices_generation_was_STATUS_for_user_NAME_html: "La génération de factures a été {STATUS, select, true{désactivée} other{activée}} pour l'utilisateur {NAME}." # messageFormat interpolation + notify_admin_member_create_reservation: + a_RESERVABLE_reservation_was_made_by_USER_html: "Une réservation %{RESERVABLE} a été effectuée par %{USER}." + notify_admin_profile_complete: + account_imported_from_PROVIDER_(UID)_has_completed_its_informations_html: "Le compte importé depuis %{PROVIDER} (%{UID}) a complété ses informations." + notify_admin_slot_is_canceled: + USER_s_reservation_on_the_DATE_was_cancelled_remember_to_generate_a_refund_invoice_if_applicable_html: "La réservation de %{USER}, le %{DATE}, a été annulée. Pensez a générer un avoir le cas échéant." + notify_admin_slot_is_modified: + a_booking_slot_was_modified: "Un créneau de réservation a été modifié." + notify_admin_subscribed_plan: + subscription_PLAN_has_been_subscribed_by_USER_html: "L'abonnement %{PLAN} a été souscrit par %{USER}." + notify_admin_subscription_canceled: + USER_s_subscription_has_been_cancelled: "L'abonnement de %{USER} a été annulé." + notify_admin_subscription_extended: + subscription_PLAN_of_the_member_USER_has_been_extended_FREE_until_DATE_html: "L'abonnement {PLAN} du membre {USER} a été prolongé {FREE, select, true{gratuitement} other{}} jusqu'au {DATE}." # messageFormat interpolation + notify_admin_subscription_is_expired: + USER_s_subscription_has_expired: "L'abonnement de %{USER} est arrivé à expiration." + notify_admin_subscription_will_expire_in_7_days: + USER_s_subscription_will_expire_in_7_days: "L'abonnement de %{USER} expire dans 7 jours." + notify_admin_user_group_changed: + user_NAME_changed_his_group_html: "L'utilisateur {NAME} a changé de groupe." # messageFormat interpolation + notify_admin_user_merged: + user_NAME_has_merged_his_account_with_the_one_imported_from_PROVIDER_(UID)_html: "L'utilisateur {NAME} a fusionné son compte avec le compte importé depuis {PROVIDER} ({UID})." # messageFormat interpolation + notify_admin_when_project_published: + project_NAME_has_been_published_html: "Le projet %{NAME} vient d'être publié." + notify_admin_when_user_is_created: + a_new_user_account_has_been_created_NAME_EMAIL_html: "Un nouveau compte utilisateur vient d'être créé : %{NAME} <%{EMAIL}>." + notify_admin_when_user_is_imported: + a_new_user_account_has_been_imported_from_PROVIDER_(UID)_html: "Un nouveau compte utilisateur vient d'être importé depuis : %{PROVIDER} (%{UID})." + notify_member_create_reservation: + your_reservation_RESERVABLE_was_successfully_saved_html: "Votre réservation %{RESERVABLE} a bien été enregistrée." + notify_member_slot_is_canceled: + your_reservation_RESERVABLE_of_DATE_was_successfully_cancelled: "Votre réservation %{RESERVABLE} du %{DATE} a bien été annulée." + notify_member_slot_is_modified: + your_reservation_slot_was_successfully_changed: "Votre créneau de réservation a bien été modifié." + notify_member_subscribed_plan: + you_have_subscribed_to_PLAN_html: "Vous avez souscrit à l'abonnement %{PLAN}." + notify_member_subscribed_plan_is_changed: + you_have_changed_your_subscription_to_PLAN_html: "Vous avez changé votre abonnement à %{PLAN}." + notify_member_subscription_canceled: + your_subscription_PLAN_was_successfully_cancelled_html: "Votre abonnement %{PLAN} est bien annulé." + notify_member_subscription_extended: + your_subscription_PLAN_has_been_extended_FREE_until_DATE_html: "Votre abonnement {PLAN} a été prolongé {FREE, select, true{gratuitement} other{}} jusqu'au {DATE}." # messageFormat interpolation + notify_member_subscription_is_expired: + your_subscription_has_expired: "Votre abonnement est arrivé à expiration." + notify_member_subscription_will_expire_in_7_days: + your_subscription_will_expire_in_7_days: "Votre abonnement arrive à échéance dans 7 jours." + notify_partner_subscribed_plan: + subscription_partner_PLAN_has_been_subscribed_by_USER_html: "L'abonnement partenaire %{PLAN} a été souscrit par %{USER}." + notify_project_author_when_collaborator_valid: + USER_became_collaborator_of_your_project_PROJECT: "Le membre %{USER} est devenu un collaborateur de votre projet :" + notify_project_collaborator_to_valid: + you_are_invited_to_collaborate_on_the_project: "Vous êtes invité à collaborer sur le projet suivant :" + notify_user_auth_migration: + your_account_was_migrated: "Votre compte a bien été migré vers le nouveau système d'authentification." + notify_user_profile_complete: + your_profile_was_completed: "Votre profil a bien été complété, vous avez désormais accès à l'ensemble de la plateforme." + notify_user_training_valid: + your_TRAINING_was_validated_html: "Votre formation %{TRAINING} a bien été validée." + notify_user_user_group_changed: + your_group_has_changed: "Vous avez changé de groupe" + notify_user_when_avoir_ready: + your_avoir_is_ready_html: "Votre facture d'avoir n°%{REFERENCE}, d'un montant de %{AMOUNT}, est prête. Cliquez ici pour la télécharger." + notify_user_when_invoice_ready: + your_invoice_is_ready_html: "Votre facture n°%{REFERENCE}, d'un montant de %{AMOUNT}, est prête. Cliquez ici pour la télécharger." + undefined_notification: + unknown_notification: "Notification inconnue" + notification_ID_wrong_type_TYPE_unknown: "Notification {ID} erronée (type {TYPE} inconnu)." \ No newline at end of file diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml new file mode 100644 index 000000000..f811d455b --- /dev/null +++ b/config/locales/mails.en.yml @@ -0,0 +1,244 @@ +en: + layouts: + notifications_mailer: + see_you_later: + sincerely: + signature: + do_not_reply: + + users_mailer: + notify_user_account_created: + subject: "Your FabLab account has been successfully created" + body: + hello: "Hello %{NAME}," + intro: "The FabLab team has just created an account for you, on the {FABLAB} site:" # messageFormat interpolation + connection_parameters: "Here are your connection parameters:" + account_name: "Account name:" + password: "Password:" + temporary_password: + keep_advantages: + thanks_to_: + logon_or_login: + token_if_link_problem: + + notifications_mailer: + notify_user_user_group_changed: + subject: "Your group has changed" + body: + warning: + + notify_admin_user_group_changed: + subject: "A member has changed his group" + body: + user_changed_group_html: + previous_group: + new_group: + + notify_admin_subscription_extended: + subject: "A subscription has been extended" + body: + subscription_extended_html: + + notify_member_subscription_extended: + subject: "Your subscription has been extended" + body: + your_plan: + has_been_extended: + free: + until: + + notify_partner_subscribed_plan: + subject: "A subscription has been purchased" + body: + a_plan: + was_purchased_by_member: + + notify_admin_when_project_published: + subject: "A project has been published" + body: + new_project_published: + + notify_project_collaborator_to_valid: + subject: "Invitation to take part in a project" + body: + your_are_invited_to_take_part_in_a_project: + to_accept_the_invitation_click_on_following_link: + + notify_project_author_when_collaborator_valid: + subject: "New collaborator in your project" + body: + the_member: + accepted_your_invitation_to_take_part_in_the_project: + + notify_user_training_valid: + subject: "Your training has been validated" + body: + your_training: + has_been_validated: + + notify_member_subscribed_plan: + subject: "Your subscription has been successfully purchased" + body: + plan_subscribed_html: + subscription_stops_on: + + notify_member_create_reservation: + subject: "Your réservation has been successfully saved" + body: + reservation_saved_html: + your_reserved_slots: + + notify_member_subscribed_plan_is_changed: + subject: "Your subscription has been updated" + body: + new_plan_html: + + notify_admin_member_create_reservation: + subject: "New reservation" + body: + member_reserved_html: + reserved_slots: + + notify_member_slot_is_modified: + subject: "Your reservation slot has been successfully changed" + body: + reservation_changed_to: + previous_date: + + notify_admin_slot_is_modified: + subject: + body: + slot_modified: + new_date: + old_date: + + notify_admin_when_user_is_created: + subject: "A user account has been created" + body: + new_account_created: + invoicing_disabled_html: + + notify_admin_subscribed_plan: + subject: "A subscription has been purchased" + body: + plan_subscribed_html: + + notify_member_invoice_ready: + subject: "Your FabLab's invoice" + body: + please_find_attached_html: + invoice_in_your_dashboard_html: + your_dashboard: + + notify_member_avoir_ready: + subject: "Your FabLab's refund invoice" + body: + please_find_attached_html: + invoice_in_your_dashboard_html: + your_dashboard: + + notify_member_subscription_will_expire_in_7_days: + subject: "Your subscription expires in 7 days" + body: + your_plan: + expires_in_7_days: + to_renew_your_plan_follow_the_link: + + notify_member_subscription_is_expired: + subject: "Your subscription has expired" + body: + your_plan: + has_expired: + you_can_go_to: + to_renew_your_plan: + + notify_admin_subscription_will_expire_in_7_days: + subject: "A member subscription expires in 7 days" + body: + subscription_will_expire_html: + + notify_admin_subscription_is_expired: + subject: "A member subscription has expired" + body: + subscription_expired_html: + + notify_admin_subscription_canceled: + subject: "A member subscription has been canceled" + body: + subscription_canceled_html: + + notify_member_subscription_canceled: + subject: "Your subscription has been canceled" + body: + your_plan_was_canceled: + your_plan: + end_at: + + notify_member_slot_is_canceled: + subject: "Your reservation has been canceled" + body: + reservation_canceled: + + notify_admin_slot_is_canceled: + subject: "A reservation has been canceled" + body: + member_cancelled: + item_details: + generate_refund: + + notify_admin_when_user_is_imported: + subject: "A user account has been imported from the SSO" + body: + new_account_imported: + provider_uid: + known_informations: + address_already_used: + no_more_info_available: + + notify_user_profile_complete: + subject: "You now have access to the entire platform" + body: + message: + + notify_user_auth_migration: + subject: "Important change to your FabLab account" + body: + the_platform: + is_changing_its_auth_system_and_will_now_use: + instead_of: + consequence_of_the_modification: + to_use_the_platform_thanks_for: + create_an_account_on: + or_use_an_existing_account_clicking_here: + in_case_of_problem_enter_the_following_code: + + notify_admin_user_merged: + subject: "An imported account has been merged with an existing account" + body: + imported_account_merged: + provider_uid: + + notify_admin_profile_complete: + subject: "An imported account has completed its profile" + body: + account_completed: + provider_id: + + notify_admin_abuse_reported: + subject: "An abusive content has been reported" + body: + intro: + signaled_content: + signaled_by: + signaled_on: + message: + + notify_admin_invoicing_changed: + subject: "An invoicing parameter has been changed" + body: + generation_status_html: + disabled: + enabled: + + shared: + hello: "Hello %{user_name}" diff --git a/config/locales/mails.fr.yml b/config/locales/mails.fr.yml new file mode 100644 index 000000000..2f18c1ac3 --- /dev/null +++ b/config/locales/mails.fr.yml @@ -0,0 +1,244 @@ +fr: + layouts: + notifications_mailer: + see_you_later: "A très bientôt sur {GENDER, select, male{le} female{la} other{les}}" # messageFormat interpolation + sincerely: "Cordialement," + signature: "L'équipe du Fab Lab." + do_not_reply: "Merci de ne pas répondre directement à cet email." + + users_mailer: + notify_user_account_created: + subject: "Votre compte Fab Lab a bien été créé" + body: + hello: "Bonjour %{NAME}," + intro: "L’équipe du Fab Lab vient de vous créer un compte sur le site {GENDER, select, male{du} female{de la} other{des}} {FABLAB} :" # messageFormat interpolation + connection_parameters: "Voici vos paramètres de connexion :" + account_name: "Nom de compte :" + password: "Mot de passe :" + temporary_password: "Ce mot de passe est temporaire, vous pourrez le modifier en accédant à l’espace « Mon compte »." + keep_advantages: "Avec ce compte, vous conservez bien entendu tous les avantages liés à votre profil utilisateur Fab Lab (abonnement, formations)." + thanks_to_: "Pour pouvoir utiliser la plateforme, merci de vous" + logon_or_login: "créer un compte sur %{PROVIDER} ou utiliser un compte pré-existant en cliquant ici" + token_if_link_problem: "En cas de problème avec le lien, vous pourrez saisir manuellement le code suivant lors de votre première connexion :" + + notifications_mailer: + notify_user_user_group_changed: + subject: "Vous avez changé de groupe" + body: + warning: "Vous venez de changer de groupe utilisateurs. Des contrôles pourront être opérés au Fab Lab afin de vérifier que ce changement est justifié." + + notify_admin_user_group_changed: + subject: "Un membre a changé de groupe" + body: + user_changed_group_html: "L'utilisateur %{NAME} a changé de groupe." + previous_group: "Ancien groupe :" + new_group: "Nouveau groupe :" + + notify_admin_subscription_extended: + subject: "Un abonnement a été prolongé" + body: + subscription_extended_html: "L'abonnement {PLAN} du membre {NAME} a été prolongé {FREE, select, true{gratuitment} other{}} jusqu'au {DATE}." # messageFormat interpolation + + notify_member_subscription_extended: + subject: "Votre abonnement a été prolongé" + body: + your_plan: "Votre abonnement" + has_been_extended: "a été prolongé" + free: "gratuitement" + until: "jusqu'au" + + notify_partner_subscribed_plan: + subject: "Un abonnement a été souscrit" + body: + a_plan: "Un abonnement" + was_purchased_by_member: "vient d'être souscrit par le membre" + + notify_admin_when_project_published: + subject: "Un projet a été publié." + body: + new_project_published: "Un nouveau projet vient d'être publié :" + + notify_project_collaborator_to_valid: + subject: "Invitation à collaborer sur un projet" + body: + your_are_invited_to_take_part_in_a_project: "Vous êtes invité à collaborer sur ce projet :" + to_accept_the_invitation_click_on_following_link: "Pour accepter cette invitation, il vous suffit de cliquer sur le lien suivant:" + + notify_project_author_when_collaborator_valid: + subject: "Un nouveau collaborateur dans votre projet" + body: + the_member: "Le membre" + accepted_your_invitation_to_take_part_in_the_project: "a accepté votre demande de collaboration sur votre projet :" + + notify_user_training_valid: + subject: "Votre formation a été validée" + body: + your_training: "Votre formation" + has_been_validated: "a bien été validée" + + notify_member_subscribed_plan: + subject: "Votre abonnement a bien été souscrit" + body: + plan_subscribed_html: "Vous avez souscrit à l'abonnement : %{PLAN}." + subscription_stops_on: "Votre abonnement s'arrêtera automatiquement le %{DATE}" + + notify_member_create_reservation: + subject: "Votre réservation a bien été enregistrée" + body: + reservation_saved_html: "Votre réservation %{RESERVATION} a bien été enregistrée." + your_reserved_slots: "Les créneaux que vous avez réservés sont :" + + notify_member_subscribed_plan_is_changed: + subject: "Votre abonnement a été mis à jour" + body: + new_plan_html: "Vous avez changé votre abonnement à %{PLAN}." + + notify_admin_member_create_reservation: + subject: "Nouvelle réservation" + body: + member_reserved_html: "Le membre %{NAME} a réservé %{RESERVABLE}." + reserved_slots: "Les créneaux réservés sont :" + + notify_member_slot_is_modified: + subject: "Votre créneau de réservation a bien été modifié" + body: + reservation_changed_to: "Votre créneau de réservation a bien été déplacé au :" + previous_date: "Ancienne date :" + + notify_admin_slot_is_modified: + subject: "Un créneau de réservation a été modifié" + body: + slot_modified: "Le membre %{NAME} a modifié son créneau de réservation." + new_date: "Nouvelle date :" + old_date: "Ancienne date :" + + notify_admin_when_user_is_created: + subject: "Un compte utilisateur a été créé" + body: + new_account_created: "Un nouveau compte utilisateur vient d'être créé sur la plateforme :" + invoicing_disabled_html: "La génération de factures est désactivée pour cet utilisateur." + + notify_admin_subscribed_plan: + subject: "Un abonnement a été souscrit" + body: + plan_subscribed_html: "Un abonnement %{PLAN} vient d'être souscrit par le membre %{NAME}." + + notify_member_invoice_ready: + subject: "Votre facture du FabLab" + body: + please_find_attached_html: "Vous trouverez en pièce jointe votre facture du {DATE}, d'un montant de {AMOUNT} concernant votre {TYPE, select, Reservation{réservation} other{abonnement}}." # messageFormat interpolation + invoice_in_your_dashboard_html: "Vous pouvez à tout moment retrouver votre facture dans %{DASHBOARD} sur le site du Fab Lab." + your_dashboard: "votre tableau de bord" + + notify_member_avoir_ready: + subject: "Votre facture d'avoir du FabLab" + body: + please_find_attached_html: "Vous trouverez en pièce jointe votre facture d'avoir du {DATE}, d'un montant de {AMOUNT} concernant votre {TYPE, select, Reservation{réservation} other{abonnement}}." # messageFormat interpolation + invoice_in_your_dashboard_html: "Vous pouvez à tout moment retrouver votre facture d'avoir dans %{DASHBOARD} sur le site du Fab Lab." + your_dashboard: "votre tableau de bord" + + notify_member_subscription_will_expire_in_7_days: + subject: "Votre abonnement expire dans 7 jours" + body: + your_plan: "Votre abonnement" + expires_in_7_days: "expire dans 7 jours" + to_renew_your_plan_follow_the_link: "Si vous souhaitez renouveler votre abonnement, vous pouvez vous rendre sur" + + notify_member_subscription_is_expired: + subject: "Votre abonnement est arrivé à expiration" + body: + your_plan: "Votre abonnement" + has_expired: "est arrivé à expiration" + you_can_go_to: "Vous pouvez vous rendre sur notre page" + to_renew_your_plan: "afin de renouveler votre abonnement" + + notify_admin_subscription_will_expire_in_7_days: + subject: "L'abonnement d'un membre expire dans 7 jours" + body: + subscription_will_expire_html: "L'abonnement du membre %{NAME} %{PLAN} expire dans 7 jours." + + notify_admin_subscription_is_expired: + subject: "L'abonnement d'un membre est arrivé à expiration" + body: + subscription_expired_html: "L'abonnement du membre %{NAME} %{PLAN} est arrivé à expiration." + + notify_admin_subscription_canceled: + subject: "L'abonnement d'un membre a été annulé" + body: + subscription_canceled_html: "L'abonnement %{PLAN} du membre %{NAME} vient d'être annulé." + + notify_member_subscription_canceled: + subject: "Votre abonnement a été annulé" + body: + your_plan_was_canceled: "Votre abonnement a bien été annulé" + your_plan: "votre abonnement" + end_at: "fin au" + + notify_member_slot_is_canceled: + subject: "Votre réservation a bien été annulée" + body: + reservation_canceled: "Votre réservation %{RESERVABLE} a bien été annulée :" + + notify_admin_slot_is_canceled: + subject: "Une réservation a été annulée" + body: + member_cancelled: "Le membre %{NAME} a annulé sa réservation :" + item_details: "%{START} - %{END}, concernant %{RESERVABLE}" + generate_refund: "Pensez à générer un avoir ou un remboursement pour cette annulation, le cas échéant." + + notify_admin_when_user_is_imported: + subject: "Un compte utilisateur a été importé depuis le SSO" + body: + new_account_imported: "Un nouveau compte utilisateur (ID: %{ID}) vient d'être importé sur la plate-forme via %{PROVIDER}." + provider_uid: "Son identifiant fournisseur est %{UID}." + known_informations: "Voici les informations connues à son propos :" + address_already_used: "Cette adresse est déjà associée à un autre utilisateur" + no_more_info_available: "Aucune autre information sur cet utilisateur n'est disponible tant que celui-ci n'aura pas complété son profil." + + notify_user_profile_complete: + subject: "Vous avez désormais accès à l'ensemble de la plate-forme" + body: + message: "Les informations de votre nouveau compte ont correctement été mises à jour, vous avez désormais accès à l'ensemble de la plateforme." + + notify_user_auth_migration: + subject: "Changement important pour votre compte FabLab" + body: + the_platform: "La plateforme" + is_changing_its_auth_system_and_will_now_use: "change actuellement son système d'authentification des utilisateurs et utilisera désormais" + instead_of: "au lieu de" + consequence_of_the_modification: "A cause de ce changement vous ne serez plus en mesure de vous connecter sur la plateforme avec vos identifiants habituels." + to_use_the_platform_thanks_for: "Pour pouvoir continuer à utiliser la plateforme, merci de vous" + create_an_account_on: "créer un compte sur" + or_use_an_existing_account_clicking_here: "ou utiliser un compte pré-existant en cliquant ici" + in_case_of_problem_enter_the_following_code: "En cas de problème avec le lien, vous pourrez saisir manuellement le code suivant lors de votre première connexion, pour migrer votre compte actuel vers le nouveau système d'authentification :" + + notify_admin_user_merged: + subject: "Un compte importé a été fusionné avec un compte existant" + body: + imported_account_merged: "Un compte utilisateur précédemment importé via %{PROVIDER} vient d'être fusionné avec le compte existant de %{NAME}" + provider_uid: "Son identifiant fournisseur est %{UID}" + + notify_admin_profile_complete: + subject: "Un compte importé a complété ses informations" + body: + account_completed: "Un compte utilisateur précédemment importé via %{PROVIDER} vient de compléter ses informations de profil :" + provider_id: "Son identifiant fournisseur est %{UID}" + + notify_admin_abuse_reported: + subject: "Un contenu abusif a été rapporté" + body: + intro: "Un visiteur vient de signaler le contenu suivant comme étant abusif." + signaled_content: "Contenu signalé :" + signaled_by: "Signalé par :" + signaled_on: "Signalé le :" + message: "Message :" + + notify_admin_invoicing_changed: + subject: "Un paramètre de facturation a été modifié" + body: + generation_status_html: "La génération de factures vient d'être {STATUS, select, true{désactivée} other{activée}} pour l'utilisateur {NAME}." # messageFormat interpolation + disabled: "Désormais, aucune facture ne sera générée pour les paiement de cet utilisateur effectués à l'accueil." + enabled: "Désormais, tous les paiement de cet utilisateur effectués à l'accueil, donneront lieu à la génération d'une facture." + + shared: + hello: "Bonjour %{user_name}" diff --git a/config/locales/rails.en.yml b/config/locales/rails.en.yml new file mode 100644 index 000000000..43dd86e3c --- /dev/null +++ b/config/locales/rails.en.yml @@ -0,0 +1,204 @@ +en: + date: + abbr_day_names: + - Sun + - Mon + - Tue + - Wed + - Thu + - Fri + - Sat + abbr_month_names: + - + - Jan + - Feb + - Mar + - Apr + - May + - Jun + - Jul + - Aug + - Sep + - Oct + - Nov + - Dec + day_names: + - Sunday + - Monday + - Tuesday + - Wednesday + - Thursday + - Friday + - Saturday + formats: + default: "%Y-%m-%d" + long: "%B %d, %Y" + short: "%b %d" + month_names: + - + - January + - February + - March + - April + - May + - June + - July + - August + - September + - October + - November + - December + order: + - :year + - :month + - :day + datetime: + distance_in_words: + about_x_hours: + one: about 1 hour + other: about %{count} hours + about_x_months: + one: about 1 month + other: about %{count} months + about_x_years: + one: about 1 year + other: about %{count} years + almost_x_years: + one: almost 1 year + other: almost %{count} years + half_a_minute: half a minute + less_than_x_minutes: + one: less than a minute + other: less than %{count} minutes + less_than_x_seconds: + one: less than 1 second + other: less than %{count} seconds + over_x_years: + one: over 1 year + other: over %{count} years + x_days: + one: 1 day + other: "%{count} days" + x_minutes: + one: 1 minute + other: "%{count} minutes" + x_months: + one: 1 month + other: "%{count} months" + x_seconds: + one: 1 second + other: "%{count} seconds" + prompts: + day: Day + hour: Hour + minute: Minute + month: Month + second: Seconds + year: Year + errors: + format: "%{attribute} %{message}" + messages: + accepted: must be accepted + blank: can't be blank + present: must be blank + confirmation: doesn't match %{attribute} + empty: can't be empty + equal_to: must be equal to %{count} + even: must be even + exclusion: is reserved + greater_than: must be greater than %{count} + greater_than_or_equal_to: must be greater than or equal to %{count} + inclusion: is not included in the list + invalid: is invalid + less_than: must be less than %{count} + less_than_or_equal_to: must be less than or equal to %{count} + not_a_number: is not a number + not_an_integer: must be an integer + odd: must be odd + record_invalid: 'Validation failed: %{errors}' + restrict_dependent_destroy: + one: Cannot delete record because a dependent %{record} exists + many: Cannot delete record because dependent %{record} exist + taken: has already been taken + too_long: + one: is too long (maximum is 1 character) + other: is too long (maximum is %{count} characters) + too_short: + one: is too short (minimum is 1 character) + other: is too short (minimum is %{count} characters) + wrong_length: + one: is the wrong length (should be 1 character) + other: is the wrong length (should be %{count} characters) + other_than: must be other than %{count} + template: + body: 'There were problems with the following fields:' + header: + one: 1 error prohibited this %{model} from being saved + other: "%{count} errors prohibited this %{model} from being saved" + helpers: + select: + prompt: Please select + submit: + create: Create %{model} + submit: Save %{model} + update: Update %{model} + number: + currency: + format: + delimiter: "," + format: "%u%n" + precision: 2 + separator: "." + significant: false + strip_insignificant_zeros: false + unit: "$" + format: + delimiter: "," + precision: 3 + separator: "." + significant: false + strip_insignificant_zeros: false + human: + decimal_units: + format: "%n %u" + units: + billion: Billion + million: Million + quadrillion: Quadrillion + thousand: Thousand + trillion: Trillion + unit: '' + format: + delimiter: '' + precision: 3 + significant: true + strip_insignificant_zeros: true + storage_units: + format: "%n %u" + units: + byte: + one: Byte + other: Bytes + gb: GB + kb: KB + mb: MB + tb: TB + percentage: + format: + delimiter: '' + format: "%n%" + precision: + format: + delimiter: '' + support: + array: + last_word_connector: ", and " + two_words_connector: " and " + words_connector: ", " + time: + am: am + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + long: "%B %d, %Y %H:%M" + short: "%d %b %H:%M" + pm: pm \ No newline at end of file diff --git a/config/locales/rails.fr.yml b/config/locales/rails.fr.yml new file mode 100644 index 000000000..26d2eb66f --- /dev/null +++ b/config/locales/rails.fr.yml @@ -0,0 +1,206 @@ +fr: + date: + abbr_day_names: + - dim + - lun + - mar + - mer + - jeu + - ven + - sam + abbr_month_names: + - + - jan. + - fév. + - mar. + - avr. + - mai + - juin + - juil. + - août + - sept. + - oct. + - nov. + - déc. + day_names: + - dimanche + - lundi + - mardi + - mercredi + - jeudi + - vendredi + - samedi + formats: + default: "%d/%m/%Y" + short: "%e %b" + long: "%e %B %Y" + month_names: + - + - janvier + - février + - mars + - avril + - mai + - juin + - juillet + - août + - septembre + - octobre + - novembre + - décembre + order: + - :day + - :month + - :year + datetime: + distance_in_words: + about_x_hours: + one: environ une heure + other: environ %{count} heures + about_x_months: + one: environ un mois + other: environ %{count} mois + about_x_years: + one: environ un an + other: environ %{count} ans + almost_x_years: + one: presqu'un an + other: presque %{count} ans + half_a_minute: une demi-minute + less_than_x_minutes: + zero: moins d'une minute + one: moins d'une minute + other: moins de %{count} minutes + less_than_x_seconds: + zero: moins d'une seconde + one: moins d'une seconde + other: moins de %{count} secondes + over_x_years: + one: plus d'un an + other: plus de %{count} ans + x_days: + one: 1 jour + other: "%{count} jours" + x_minutes: + one: 1 minute + other: "%{count} minutes" + x_months: + one: 1 mois + other: "%{count} mois" + x_seconds: + one: 1 seconde + other: "%{count} secondes" + prompts: + day: Jour + hour: Heure + minute: Minute + month: Mois + second: Seconde + year: Année + errors: + format: "%{attribute} %{message}" + messages: + accepted: doit être accepté(e) + blank: doit être rempli(e) + present: doit être vide + confirmation: ne concorde pas avec %{attribute} + empty: doit être rempli(e) + equal_to: doit être égal à %{count} + even: doit être pair + exclusion: n'est pas disponible + greater_than: doit être supérieur à %{count} + greater_than_or_equal_to: doit être supérieur ou égal à %{count} + inclusion: n'est pas inclus(e) dans la liste + invalid: n'est pas valide + less_than: doit être inférieur à %{count} + less_than_or_equal_to: doit être inférieur ou égal à %{count} + not_a_number: n'est pas un nombre + not_an_integer: doit être un nombre entier + odd: doit être impair + record_invalid: 'La validation a échoué : %{errors}' + restrict_dependent_destroy: + one: 'Suppression impossible: un autre enregistrement est lié' + many: 'Suppression impossible: d''autres enregistrements sont liés' + taken: n'est pas disponible + too_long: + one: est trop long (pas plus d'un caractère) + other: est trop long (pas plus de %{count} caractères) + too_short: + one: est trop court (au moins un caractère) + other: est trop court (au moins %{count} caractères) + wrong_length: + one: ne fait pas la bonne longueur (doit comporter un seul caractère) + other: ne fait pas la bonne longueur (doit comporter %{count} caractères) + other_than: doit être différent de %{count} + template: + body: 'Veuillez vérifier les champs suivants : ' + header: + one: 'Impossible d''enregistrer ce(tte) %{model} : 1 erreur' + other: 'Impossible d''enregistrer ce(tte) %{model} : %{count} erreurs' + helpers: + select: + prompt: Veuillez sélectionner + submit: + create: Créer un(e) %{model} + submit: Enregistrer ce(tte) %{model} + update: Modifier ce(tte) %{model} + number: + currency: + format: + delimiter: " " + format: "%n %u" + precision: 2 + separator: "," + significant: false + strip_insignificant_zeros: false + unit: "€" + format: + delimiter: " " + precision: 3 + separator: "," + significant: false + strip_insignificant_zeros: false + human: + decimal_units: + format: "%n %u" + units: + billion: milliard + million: million + quadrillion: million de milliards + thousand: millier + trillion: billion + unit: '' + format: + delimiter: '' + precision: 2 + significant: true + strip_insignificant_zeros: true + storage_units: + format: "%n %u" + units: + byte: + one: octet + other: octets + gb: Go + kb: ko + mb: Mo + tb: To + percentage: + format: + delimiter: '' + format: "%n%" + precision: + format: + delimiter: '' + support: + array: + last_word_connector: " et " + two_words_connector: " et " + words_connector: ", " + time: + am: am + formats: + default: "%d %B %Y %Hh %Mmin %Ss" + long: "%A %d %B %Y %Hh%M" + short: "%d %b %Hh%M" + pm: pm \ No newline at end of file diff --git a/config/newrelic.yml b/config/newrelic.yml new file mode 100644 index 000000000..352e764fa --- /dev/null +++ b/config/newrelic.yml @@ -0,0 +1,224 @@ +# +# This file configures the New Relic Agent. New Relic monitors Ruby, Java, +# .NET, PHP, Python and Node applications with deep visibility and low +# overhead. For more information, visit www.newrelic.com. +# +# Generated December 11, 2014 +# +# This configuration file is custom generated for sleede + + +# Here are the settings that are common to all environments +common: &default_settings + # ============================== LICENSE KEY =============================== + + # You must specify the license key associated with your New Relic + # account. This key binds your Agent's data to your account in the + # New Relic service. + license_key: '3394fafbd05532f2f3c4869fd8125f2da4177f72' + + # Agent Enabled (Ruby/Rails Only) + # Use this setting to force the agent to run or not run. + # Default is 'auto' which means the agent will install and run only + # if a valid dispatcher such as Mongrel is running. This prevents + # it from running with Rake or the console. Set to false to + # completely turn the agent off regardless of the other settings. + # Valid values are true, false and auto. + # + # agent_enabled: auto + + # Application Name Set this to be the name of your application as + # you'd like it show up in New Relic. The service will then auto-map + # instances of your application into an "application" on your + # dashboard page. If you want to map this instance into multiple + # apps, like "AJAX Requests" and "All UI" then specify a semicolon + # separated list of up to three distinct names, or a yaml list. + # Defaults to the capitalized RAILS_ENV or RACK_ENV (i.e., + # Production, Staging, etc) + # + # Example: + # + # app_name: + # - Ajax Service + # - All Services + # + # Caution: If you change this name, a new application will appear in the New + # Relic user interface with the new name, and data will stop reporting to the + # app with the old name. + # + # See https://newrelic.com/docs/site/renaming-applications for more details + # on renaming your New Relic applications. + # + app_name: FABLAB + + # When "true", the agent collects performance data about your + # application and reports this data to the New Relic service at + # newrelic.com. This global switch is normally overridden for each + # environment below. (formerly called 'enabled') + monitor_mode: true + + # Developer mode should be off in every environment but + # development as it has very high overhead in memory. + developer_mode: false + + # The newrelic agent generates its own log file to keep its logging + # information separate from that of your application. Specify its + # log level here. + log_level: info + + # Optionally set the path to the log file This is expanded from the + # root directory (may be relative or absolute, e.g. 'log/' or + # '/var/log/') The agent will attempt to create this directory if it + # does not exist. + # log_file_path: 'log' + + # Optionally set the name of the log file, defaults to 'newrelic_agent.log' + # log_file_name: 'newrelic_agent.log' + + # The newrelic agent communicates with the service via https by default. This + # prevents eavesdropping on the performance metrics transmitted by the agent. + # The encryption required by SSL introduces a nominal amount of CPU overhead, + # which is performed asynchronously in a background thread. If you'd prefer + # to send your metrics over http uncomment the following line. + # ssl: false + + #============================== Browser Monitoring =============================== + # New Relic Real User Monitoring gives you insight into the performance real users are + # experiencing with your website. This is accomplished by measuring the time it takes for + # your users' browsers to download and render your web pages by injecting a small amount + # of JavaScript code into the header and footer of each page. + browser_monitoring: + # By default the agent automatically injects the monitoring JavaScript + # into web pages. Set this attribute to false to turn off this behavior. + auto_instrument: true + + # Proxy settings for connecting to the New Relic server. + # + # If a proxy is used, the host setting is required. Other settings + # are optional. Default port is 8080. + # + # proxy_host: hostname + # proxy_port: 8080 + # proxy_user: + # proxy_pass: + + # The agent can optionally log all data it sends to New Relic servers to a + # separate log file for human inspection and auditing purposes. To enable this + # feature, change 'enabled' below to true. + # See: https://newrelic.com/docs/ruby/audit-log + audit_log: + enabled: false + + # Tells transaction tracer and error collector (when enabled) + # whether or not to capture HTTP params. When true, frameworks can + # exclude HTTP parameters from being captured. + # Rails: the RoR filter_parameter_logging excludes parameters + # Java: create a config setting called "ignored_params" and set it to + # a comma separated list of HTTP parameter names. + # ex: ignored_params: credit_card, ssn, password + capture_params: false + + # Transaction tracer captures deep information about slow + # transactions and sends this to the New Relic service once a + # minute. Included in the transaction is the exact call sequence of + # the transactions including any SQL statements issued. + transaction_tracer: + + # Transaction tracer is enabled by default. Set this to false to + # turn it off. This feature is only available at the Professional + # and above product levels. + enabled: true + + # Threshold in seconds for when to collect a transaction + # trace. When the response time of a controller action exceeds + # this threshold, a transaction trace will be recorded and sent to + # New Relic. Valid values are any float value, or (default) "apdex_f", + # which will use the threshold for an dissatisfying Apdex + # controller action - four times the Apdex T value. + transaction_threshold: apdex_f + + # When transaction tracer is on, SQL statements can optionally be + # recorded. The recorder has three modes, "off" which sends no + # SQL, "raw" which sends the SQL statement in its original form, + # and "obfuscated", which strips out numeric and string literals. + record_sql: obfuscated + + # Threshold in seconds for when to collect stack trace for a SQL + # call. In other words, when SQL statements exceed this threshold, + # then capture and send to New Relic the current stack trace. This is + # helpful for pinpointing where long SQL calls originate from. + stack_trace_threshold: 0.500 + + # Determines whether the agent will capture query plans for slow + # SQL queries. Only supported in mysql and postgres. Should be + # set to false when using other adapters. + # explain_enabled: true + + # Threshold for query execution time below which query plans will + # not be captured. Relevant only when `explain_enabled` is true. + # explain_threshold: 0.5 + + # Error collector captures information about uncaught exceptions and + # sends them to New Relic for viewing + error_collector: + + # Error collector is enabled by default. Set this to false to turn + # it off. This feature is only available at the Professional and above + # product levels. + enabled: true + + # To stop specific errors from reporting to New Relic, set this property + # to comma-separated values. Default is to ignore routing errors, + # which are how 404's get triggered. + ignore_errors: "ActionController::RoutingError,Sinatra::NotFound" + + # If you're interested in capturing memcache keys as though they + # were SQL uncomment this flag. Note that this does increase + # overhead slightly on every memcached call, and can have security + # implications if your memcached keys are sensitive + # capture_memcache_keys: true + +# Application Environments +# ------------------------------------------ +# Environment-specific settings are in this section. +# For Rails applications, RAILS_ENV is used to determine the environment. +# For Java applications, pass -Dnewrelic.environment to set +# the environment. + +# NOTE if your application has other named environments, you should +# provide newrelic configuration settings for these environments here. + +development: + <<: *default_settings + # Turn on communication to New Relic service in development mode + monitor_mode: true + app_name: FABLAB (Development) + + # Rails Only - when running in Developer Mode, the New Relic Agent will + # present performance information on the last 100 transactions you have + # executed since starting the mongrel. + # NOTE: There is substantial overhead when running in developer mode. + # Do not use for production or load testing. + developer_mode: true + +test: + <<: *default_settings + # It almost never makes sense to turn on the agent when running + # unit, functional or integration tests or the like. + monitor_mode: false + +# Turn on the agent in production for 24x7 monitoring. NewRelic +# testing shows an average performance impact of < 5 ms per +# transaction, you can leave this on all the time without +# incurring any user-visible performance degradation. +production: + <<: *default_settings + monitor_mode: true + +# Many applications have a staging environment which behaves +# identically to production. Support for that environment is provided +# here. By default, the staging environment has the agent turned on. +staging: + <<: *default_settings + monitor_mode: true + app_name: FABLAB (Staging) diff --git a/config/nginx.conf b/config/nginx.conf index 77ba630b1..a2e5fa91c 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -1,21 +1,30 @@ upstream unicorn { - server unix:/tmp/unicorn.fabmanager.sock fail_timeout=0; + server unix:/tmp/unicorn.fablab.sock fail_timeout=0; } server { listen 80; - server_name fab-manager.com www.fab-manager.com - rewrite ^ https://www.fab-manager.com$request_uri? permanent; + server_name fablab.lacasemate.fr fablab.ccsti-grenoble.org; + rewrite ^ https://fablab.lacasemate.fr$request_uri? permanent; } server { listen 443 ssl; - server_name fab-manager.com www.fab-manager.com; - root /home/admin/apps/fabmanager/current/public; + server_name fablab.lacasemate.fr; + root /home/sleede/apps/fablab/current/public; ssl on; - ssl_certificate /etc/nginx/ssl/certificate.fab-manager.com.crt; - ssl_certificate_key /etc/nginx/ssl/fab-manager.com.deprotected.key; + ssl_certificate /etc/nginx/ssl/certificate.fablab.lacasemate.fr.crt; + ssl_certificate_key /etc/nginx/ssl/fablab.lacasemate.fr.deprotected.key; + + #location / { + # auth_basic "Restricted"; + # auth_basic_user_file /home/sleede/apps/fablab/.htpasswd; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header Host $http_host; + # proxy_redirect off; + # proxy_pass http://unicorn; + #} location ^~ /assets/ { gzip_static on; @@ -31,7 +40,25 @@ server { proxy_pass http://unicorn; } - error_page 500 502 503 504 /500.html; client_max_body_size 4G; keepalive_timeout 10; + + error_page 500 502 504 /500.html; + error_page 503 @503; + + # Return a 503 error if the maintenance page exists. + if (-f /home/sleede/apps/fablab/shared/system/maintenance.html) { + return 503; + } + + location @503 { + # Serve static assets if found. + if (-f $request_filename) { + break; + } + + # Set root to the shared directory. + root /home/sleede/apps/fablab/shared; + rewrite ^(.*)$ /system/maintenance.html break; + } } diff --git a/config/nginx_puma.conf b/config/nginx_puma.conf new file mode 100644 index 000000000..a6317a4f8 --- /dev/null +++ b/config/nginx_puma.conf @@ -0,0 +1,44 @@ +upstream puma { + server unix:/usr/src/app/tmp/sockets/fablab.sock fail_timeout=0; +} + +server { + listen 80; + server_name capsciences.fab-manager.com; + root /usr/src/app/public; + location ^~ /assets/ { + gzip_static on; + expires max; + add_header Cache-Control public; + } + + try_files $uri/index.html $uri @puma; + location @puma { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://puma; + } + + client_max_body_size 4G; + keepalive_timeout 10; + + error_page 500 502 503 504 /500.html; + error_page 503 @503; + + # Return a 503 error if the maintenance page exists. + if (-f /usr/src/app/public/maintenance.html) { + return 503; + } + + location @503 { + # Serve static assets if found. + if (-f $request_filename) { + break; + } + + # Set root to the shared directory. + root /usr/src/app/public; + rewrite ^(.*)$ /maintenance.html break; + } +} diff --git a/config/nginx_staging.conf b/config/nginx_staging.conf index 61301ff7a..fed093974 100644 --- a/config/nginx_staging.conf +++ b/config/nginx_staging.conf @@ -1,11 +1,20 @@ -upstream unicorn_staging { - server unix:/tmp/unicorn.fabmanager_staging.sock fail_timeout=0; +upstream fablab_unicorn_staging { + server unix:/tmp/unicorn.fablab_staging.sock fail_timeout=0; } server { listen 80; - server_name demo.fab-manager.com; - root /home/admin/apps/fabmanager_staging/current/public; + server_name fablab.sleede.com capsciences.sleede.com relais-sciences.sleede.com demo-premium.fab-manager.com; + root /home/admin/apps/fablab_staging/current/public; + + #location / { + # auth_basic "Restricted"; + # auth_basic_user_file /home/admin/apps/fablab/.htpasswd; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header Host $http_host; + # proxy_redirect off; + # proxy_pass http://unicorn; + #} location ^~ /assets/ { gzip_static on; @@ -13,15 +22,33 @@ server { add_header Cache-Control public; } - try_files $uri/index.html $uri @unicorn_staging; - location @unicorn_staging { + try_files $uri/index.html $uri @fablab_unicorn_staging; + location @fablab_unicorn_staging { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; - proxy_pass http://unicorn_staging; + proxy_pass http://fablab_unicorn_staging; } - error_page 500 502 503 504 /500.html; client_max_body_size 4G; keepalive_timeout 10; + + error_page 500 502 504 /500.html; + error_page 503 @503; + + # Return a 503 error if the maintenance page exists. + if (-f /home/admin/apps/fablab_staging/shared/system/maintenance.html) { + return 503; + } + + location @503 { + # Serve static assets if found. + if (-f $request_filename) { + break; + } + + # Set root to the shared directory. + root /home/admin/apps/fablab_staging/shared; + rewrite ^(.*)$ /system/maintenance.html break; + } } diff --git a/config/routes.rb b/config/routes.rb index 665c1662b..5dfe3b618 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,8 +3,16 @@ require 'sidekiq/web' Rails.application.routes.draw do post 'webhooks' => 'webhooks#create' - devise_for :users, controllers: {registrations: "registrations", sessions: 'sessions', - confirmations: 'confirmations', passwords: 'passwords'} + if AuthProvider.active.providable_type == DatabaseProvider.name + # with local authentification we do not use omniAuth so we must differentiate the config + devise_for :users, controllers: {registrations: 'registrations', sessions: 'sessions', + confirmations: 'confirmations', passwords: 'passwords'} + else + devise_for :users, controllers: {registrations: 'registrations', sessions: 'sessions', + confirmations: 'confirmations', passwords: 'passwords', + :omniauth_callbacks => 'users/omniauth_callbacks'} + end + ## The priority is based upon order of creation: first created -> highest priority. ## See how all your routes lay out with "rake routes". @@ -16,15 +24,23 @@ Rails.application.routes.draw do resources :projects, only: [:index, :last_published, :show, :create, :update, :destroy] do collection do get :last_published + get :search end end resources :machines resources :components resources :themes resources :licences - resources :members, only: [:index, :show, :create, :update] do + resources :admins, only: [:index, :create, :destroy] + resources :settings, only: [:show, :update, :index], param: :name + resources :users, only: [:index, :create] + resources :members, only: [:index, :show, :create, :update, :destroy] do + get '/export_subscriptions', action: 'export_subscriptions', on: :collection + get '/export_reservations', action: 'export_reservations', on: :collection get '/export_members', action: 'export_members', on: :collection + put ':id/merge', action: 'merge', on: :collection end + resources :reservations, only: [:show, :create, :index, :update] resources :notifications, only: [:index, :show, :update] do match :update_all, path: '/', via: [:put, :patch], on: :collection end @@ -33,19 +49,61 @@ Rails.application.routes.draw do get '/last_subscribed/:last' => "members#last_subscribed" get '/feeds/twitter_timelines' => "feeds#twitter_timelines" - get 'groups' => "groups#index" + get 'pricing' => "pricing#index" + put 'pricing' => "pricing#update" + + resources :prices, only: [:index, :update] do + post 'compute', on: :collection + end + + resources :trainings_pricings, only: [:index, :update] + + resources :availabilities do + get 'machines/:machine_id', action: 'machine', on: :collection + get 'trainings', on: :collection + get 'reservations', on: :member + end + + resources :groups, only: [:index, :create, :update, :destroy] + resources :subscriptions, only: [:show, :create, :update] + resources :plans, only: [:index, :create, :update, :destroy, :show] + resources :slots, only: [:update] do + put 'cancel', on: :member + end resources :events do get 'upcoming/:limit', action: 'upcoming', on: :collection end + resources :invoices, only: [:index, :show, :create] do + get ':id/download', action: 'download', on: :collection + end + # for admin + resources :trainings + resources :credits resources :categories, only: [:index] + resources :statistics, only: [:index] + resources :custom_assets, only: [:show, :create, :update] + resources :tags + resources :stylesheets, only: [:show] + resources :auth_providers do + get 'mapping_fields', on: :collection + get 'active', action: 'active', on: :collection + end + resources :abuses, only: [:create] + + # i18n + get 'translations/:locale/:state' => 'translations#show', :constraints => { :state => /[^\/]+/ } # allow dots in URL for 'state' + end + + %w(account event machine project subscription training user).each do |path| + post "/stats/#{path}/_search", to: "api/statistics##{path}" end match '/project_collaborator/:valid_token', to: 'api/projects#collaborator_valid', via: :get - authenticate :user, lambda { |u| u.has_role? :admin } do + authenticate :user, lambda { |u| u.is_admin? } do mount Sidekiq::Web => '/admin/sidekiq' end diff --git a/config/schedule.yml b/config/schedule.yml new file mode 100644 index 000000000..09e819987 --- /dev/null +++ b/config/schedule.yml @@ -0,0 +1,16 @@ +subscription_expire_in_7_days: + cron: "0 0 * * *" + class: "SubscriptionExpireWorker" + queue: default + args: [7] + +subscription_is_expired: + cron: "0 23 * * *" + class: "SubscriptionExpireWorker" + queue: default + args: [0] + +generate_statistic: + cron: "0 1 * * *" + class: "StatisticWorker" + queue: default diff --git a/config/secrets.yml b/config/secrets.yml index 3f3a5fb1c..db40b5980 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -11,19 +11,90 @@ # if you're sharing your code publicly. development: - secret_key_base: 002f8bf3338bc1a4e7b8086186c7d9b34524ccfd6076a792d41057bd7448092c837a32373ac8953403fa2e302350ac10520508094c5d12b994e554708d780503 - disqus_shortname: fablab-sleede + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + stripe_api_key: <%= ENV["STRIPE_API_KEY"] %> + stripe_publishable_key: <%= ENV["STRIPE_PUBLISHABLE_KEY"] %> + stripe_currency: <%= ENV["STRIPE_CURRENCY"] %> + disqus_shortname: <%= ENV["DISQUS_SHORTNAME"] %> + fablab_without_plans: <%= ENV["FABLAB_WITHOUT_PLANS"] %> + time_zone: <%= ENV["TIME_ZONE"] %> + week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %> + d3_date_format: <%= ENV["D3_DATE_FORMAT"].dump %> # .dump is needed as the value may start by a '%', see https://github.com/tenderlove/psych/issues/75 + rails_locale: <%= ENV["RAILS_LOCALE"] %> + moment_locale: <%= ENV["MOMENT_LOCALE"] %> + summernote_locale: <%= ENV["SUMMERNOTE_LOCALE"] %> + angular_locale: <%= ENV["ANGULAR_LOCALE"] %> + messageformat_locale: <%= ENV["MESSAGEFORMAT_LOCALE"] %> + fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %> + elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %> test: - secret_key_base: fd566823b0f46e4d02b0a6ccd4f52dcbbe426c80cef5f85d8b2e942fddad718753fc025556cac694d15cff0010fb582c92df3283e2edb9e6d9ccf63db0ace744 - disqus_shortname: fabmanager-test + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + stripe_api_key: <%= ENV["STRIPE_API_KEY"] %> + stripe_publishable_key: <%= ENV["STRIPE_PUBLISHABLE_KEY"] %> + stripe_currency: <%= ENV["STRIPE_CURRENCY"] %> + disqus_shortname: <%= ENV["DISQUS_SHORTNAME"] %> + fablab_without_plans: <%= ENV["FABLAB_WITHOUT_PLANS"] %> + time_zone: <%= ENV["TIME_ZONE"] %> + week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %> + d3_date_format: <%= ENV["D3_DATE_FORMAT"].dump %> + rails_locale: <%= ENV["RAILS_LOCALE"] %> + moment_locale: <%= ENV["MOMENT_LOCALE"] %> + summernote_locale: <%= ENV["SUMMERNOTE_LOCALE"] %> + angular_locale: <%= ENV["ANGULAR_LOCALE"] %> + messageformat_locale: <%= ENV["MESSAGEFORMAT_LOCALE"] %> + fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %> + elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %> + staging: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> - disqus_shortname: fabmanager-staging + stripe_api_key: <%= ENV["STRIPE_API_KEY"] %> + stripe_publishable_key: <%= ENV["STRIPE_PUBLISHABLE_KEY"] %> + stripe_currency: <%= ENV["STRIPE_CURRENCY"] %> + disqus_shortname: <%= ENV["DISQUS_SHORTNAME"] %> + fablab_without_plans: <%= ENV["FABLAB_WITHOUT_PLANS"] %> + default_host: <%= ENV["DEFAULT_HOST"] %> + default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> + delivery_method: <%= ENV['DELIVERY_METHOD'] %> + smtp_address: <%= ENV["SMTP_ADDRESS"] %> + smtp_port: <%= ENV["SMTP_PORT"] %> + smtp_user_name: <%= ENV["SMTP_USER_NAME"] %> + smtp_password: <%= ENV["SMTP_PASSWORD"] %> + time_zone: <%= ENV["TIME_ZONE"] %> + week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %> + d3_date_format: <%= ENV["D3_DATE_FORMAT"].dump %> + rails_locale: <%= ENV["RAILS_LOCALE"] %> + moment_locale: <%= ENV["MOMENT_LOCALE"] %> + summernote_locale: <%= ENV["SUMMERNOTE_LOCALE"] %> + angular_locale: <%= ENV["ANGULAR_LOCALE"] %> + messageformat_locale: <%= ENV["MESSAGEFORMAT_LOCALE"] %> + fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %> + elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %> # Do not keep production secrets in the repository, # instead read values from the environment. production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> - disqus_shortname: fabmanager-production + stripe_api_key: <%= ENV["STRIPE_API_KEY"] %> + stripe_publishable_key: <%= ENV["STRIPE_PUBLISHABLE_KEY"] %> + stripe_currency: <%= ENV["STRIPE_CURRENCY"] %> + disqus_shortname: <%= ENV["DISQUS_SHORTNAME"] %> + fablab_without_plans: <%= ENV["FABLAB_WITHOUT_PLANS"] %> + default_host: <%= ENV["DEFAULT_HOST"] %> + default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %> + delivery_method: <%= ENV['DELIVERY_METHOD'] %> + smtp_address: <%= ENV["SMTP_ADDRESS"] %> + smtp_port: <%= ENV["SMTP_PORT"] %> + smtp_user_name: <%= ENV["SMTP_USER_NAME"] %> + smtp_password: <%= ENV["SMTP_PASSWORD"] %> + time_zone: <%= ENV["TIME_ZONE"] %> + week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %> + d3_date_format: <%= ENV["D3_DATE_FORMAT"].dump %> + rails_locale: <%= ENV["RAILS_LOCALE"] %> + moment_locale: <%= ENV["MOMENT_LOCALE"] %> + summernote_locale: <%= ENV["SUMMERNOTE_LOCALE"] %> + angular_locale: <%= ENV["ANGULAR_LOCALE"] %> + messageformat_locale: <%= ENV["MESSAGEFORMAT_LOCALE"] %> + fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %> + elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %> diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 16783e4a3..685a5835b 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -1,8 +1,10 @@ # configuration file for Sidekiq -:verbose: false -:pidfile: ./tmp/pids/sidekiq.pid +:verbose: true +:logfile: ./log/sidekiq.log :concurrency: 25 :queues: + - [stripe, 7] - [default, 5] - [devise_mailer, 3] - [mailers, 3] + - [elasticsearch, 2] \ No newline at end of file diff --git a/config/unicorn.rb b/config/unicorn.rb index 9e734f1df..db8e8c7d9 100644 --- a/config/unicorn.rb +++ b/config/unicorn.rb @@ -1,12 +1,12 @@ # config/unicorn.rb -root = "/home/sleede/apps/fabmanager/current" +root = "/home/sleede/apps/fablab/current" working_directory root pid "#{root}/tmp/pids/unicorn.pid" stderr_path "#{root}/log/unicorn.log" stdout_path "#{root}/log/unicorn.log" -listen "/tmp/unicorn.fabmanager.sock" +listen "/tmp/unicorn.fablab.sock" worker_processes 1 timeout 60 preload_app true diff --git a/config/unicorn_init.sh b/config/unicorn_init.sh index 3c68303a3..1c89f6b19 100755 --- a/config/unicorn_init.sh +++ b/config/unicorn_init.sh @@ -12,7 +12,7 @@ set -e # Feel free to change any of the following variables for your app: TIMEOUT=${TIMEOUT-60} -APP_ROOT=/home/admin/apps/fabmanager/current +APP_ROOT=/home/sleede/apps/fablab/current PID=$APP_ROOT/tmp/pids/unicorn.pid CMD="cd $APP_ROOT; bundle exec unicorn -D -c $APP_ROOT/config/unicorn.rb -E production" AS_USER=sleede diff --git a/config/unicorn_init_staging.sh b/config/unicorn_init_staging.sh index cd127fff3..e72875d80 100755 --- a/config/unicorn_init_staging.sh +++ b/config/unicorn_init_staging.sh @@ -12,7 +12,7 @@ set -e # Feel free to change any of the following variables for your app: TIMEOUT=${TIMEOUT-60} -APP_ROOT=/home/admin/apps/fabmanager_staging/current +APP_ROOT=/home/admin/apps/fablab_staging/current PID=$APP_ROOT/tmp/pids/unicorn.pid CMD="cd $APP_ROOT; bundle exec unicorn -D -c $APP_ROOT/config/unicorn_staging.rb -E staging" AS_USER=admin diff --git a/config/unicorn_staging.rb b/config/unicorn_staging.rb index 2a550a7b5..74fe0ab52 100644 --- a/config/unicorn_staging.rb +++ b/config/unicorn_staging.rb @@ -1,12 +1,12 @@ # config/unicorn_staging.rb -root = "/home/admin/apps/fabmanager_staging/current" +root = "/home/admin/apps/fablab_staging/current" working_directory root pid "#{root}/tmp/pids/unicorn.pid" stderr_path "#{root}/log/unicorn.log" stdout_path "#{root}/log/unicorn.log" -listen "/tmp/unicorn.fabmanager_staging.sock" +listen "/tmp/unicorn.fablab_staging.sock" worker_processes 1 timeout 60 preload_app true diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile deleted file mode 100644 index ee938ebcb..000000000 --- a/contrib/docker/Dockerfile +++ /dev/null @@ -1,85 +0,0 @@ -FROM ubuntu:latest -MAINTAINER David GUENAULT - -ENV GITBRANCH dev - -# base os upgrade -RUN apt-get -y update && \ - apt-get -y upgrade - -# prerequisites -RUN apt-get -y install git-core libpq-dev redis-server imagemagick npm vim-nox postgresql sudo curl - -RUN apt-get -y install build-essential openssl libreadline6 libreadline6-dev zlib1g zlib1g-dev \ - libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf \ - libc6-dev ncurses-dev automake libtool bison subversion - -# create a setup entry in sudo to gain root privilege without password -RUN cp /etc/sudoers /etc/sudoers.orig && \ - groupadd setup && \ - echo "%setup ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/setup - -# create user and add it to group setup (gain privilege for everything in setup mode) -RUN mkdir -p /home/fabmanager && \ - useradd fabmanager && \ - chown fabmanager:fabmanager /home/fabmanager && \ - usermod -a -G setup fabmanager - -# install rvm -RUN su - fabmanager -c "cd /home/fabmanager && \ - gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 && \ - curl -sSL https://get.rvm.io | bash -s stable --rails" - -# install dev env -RUN chsh -s /bin/bash fabmanager && \ - su - fabmanager -c "cd /home/fabmanager && \ - git clone https://github.com/LaCasemate/fab-manager.git && \ - git checkout $GITBRANCH - cd /home/fabmanager/fab-manager && \ - mkdir -p /home/fabmanager/fab-manager/tmp/pids && \ - rvm current && \ - bundle install" - -# remove privileges -RUN usermod -G fabmanager fabmanager - -# make default template unicode compatible -RUN /etc/init.d/postgresql start && \ - su - postgres -c "psql -c \"UPDATE pg_database SET datistemplate = FALSE WHERE datname = 'template1';\"" && \ - su - postgres -c "psql -c \"DROP DATABASE template1;\"" && \ - su - postgres -c "psql -c \"CREATE DATABASE template1 WITH TEMPLATE = template0 ENCODING = 'UNICODE';\"" && \ - su - postgres -c "psql -c \"UPDATE pg_database SET datistemplate = TRUE WHERE datname = 'template1';\"" && \ - su - postgres -c "psql -d template1 -c \"VACUUM FREEZE\"" && \ - /etc/init.d/postgresql stop - -# prepare database -RUN /etc/init.d/postgresql start && \ - /etc/init.d/redis-server start && \ - su - postgres -c "psql -c \"CREATE USER fabmanager WITH ENCRYPTED PASSWORD 'fabmanager';\"" && \ - su - postgres -c "psql -c \"ALTER ROLE fabmanager WITH CREATEDB;\"" && \ - su - postgres -c "psql -c \"CREATE DATABASE fabmanager_development OWNER fabmanager;\"" && \ - su - postgres -c "psql -c \"CREATE DATABASE fabmanager_production OWNER fabmanager;\"" && \ - cd /home/fabmanager/fab-manager/config && \ - cp database.yml.default database.yml && \ - sed -i "s/sleede/fabmanager/g" /home/fabmanager/fab-manager/config/database.yml && \ - /etc/init.d/postgresql stop - -# setup database -RUN /etc/init.d/postgresql start && \ - /etc/init.d/redis-server start && \ - su -l fabmanager -s /bin/bash -c "cd /home/fabmanager/fab-manager && \ - rake db:setup" && \ - /etc/init.d/postgresql stop && \ - /etc/init.d/redis-server stop - -# install supervisor -RUN apt-get -y install supervisor && \ - sed -i "s/\(\$PORT\)/3000 -b 0.0.0.0/g" /home/fabmanager/fab-manager/Procfile - -# make redis run foreground -RUN sed -i "s/daemonize yes/daemonize no/g" /etc/redis/redis.conf - -ADD fabmanager.conf /etc/supervisor/conf.d/fabmanager.conf - -CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"] - diff --git a/contrib/docker/Makefile b/contrib/docker/Makefile deleted file mode 100644 index 52f5f3586..000000000 --- a/contrib/docker/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -DOCKER=/usr/bin/docker -COMPOSE=/usr/local/bin/docker-compose -COMPOSEFILE=fabmanager.yml -REPOSITORY=fabmanager -IMAGE=fabmanager -TAG=latest -IMAGENAME=$(REPOSITORY)/$(IMAGE):$(TAG) -CONTAINERNAME=fabmanager - -build: - @$(DOCKER) build -t $(IMAGENAME) . - -interactive: - @$(DOCKER) run -ti --rm $(IMAGENAME) /bin/bash - -up: - @$(COMPOSE) -f $(COMPOSEFILE) up -d - -ps: - @$(COMPOSE) -f $(COMPOSEFILE) ps - -stop: - @$(COMPOSE) -f $(COMPOSEFILE) stop - -start: - @$(COMPOSE) -f $(COMPOSEFILE) start - -kill: - @$(COMPOSE) -f $(COMPOSEFILE) kill - -rm: - @$(COMPOSE) -f $(COMPOSEFILE) rm -v --force - diff --git a/contrib/docker/README.md b/contrib/docker/README.md deleted file mode 100644 index e0c406d18..000000000 --- a/contrib/docker/README.md +++ /dev/null @@ -1,118 +0,0 @@ -## Prérequis - -Les "outils" suivants sont un prérequis à l'utilisation de fab-manager en tant que conteneur. - -- docker (prérequis) -- docker-compose (option) -- docker-enter (option) -- make (option) - -## Installation de docker - -Voir la documentation docker : https://docs.docker.com/installation/ - -## Installation de docker-compose - -docker-compose permet de gérer l'orchestration de conteneurs docker. En soit et dans le cas de fabmanager il n'est pas "encore" utile mais apporte une souplesse par rapport à la ligne de commande. Il sera par contre utilisé quand on aura sorti redis et postgres du conteneur (travail en cours). - -L'installation de docker-compose peut se faire via pip. Cela est optionnel mais pratique pour gérer l'utilisation du conteneur au quotidien. - -``` -pip install --upgrade docker-compose -``` -## Installation de docker-enter - -docker-enter est un wrapper sur nsenter. Cet outil permet d'ouvrir un shell sur un conteneur en cours de fonctionnement. On évite ainsi d'avoir à installer un serveur ssh sur le conteneur. Il peut être remplacé par la commande exec (docker exec) depuis la version 1.3 de docker. Malgré tout je trouve docker-enter plus "stable" que docker exec. - -Le dépôt github de docker-exec est : https://github.com/jpetazzo/nsenter - -``` -git clone https://github.com/jpetazzo/nsenter -cd nsenter -docker exec -it CONTAINER_NAME /bin/bash -``` - -## Installation de make - -L'installation de make est optionnelle mais est pratique pour l'utilisation du conteneur au quotidiebn - -# Construction de l'image docker - -## La procédure classique - -``` -cd fab-manager/contrib/docker -docker build -t fabmanager:latest . -``` - -## La procédure make - -``` -cd fab-manager/contrib/docker -make build -``` - -Cela produira une image nommée fabmanager/fabmanager:latest. Vous pouvez surcharger le nomage (repository/name:tag) de la manière suivante: - -``` -make build REPOSITORY=toto IMAGE=fabmanager TAG=1.1 -``` - -Cela va produire une image nommée toto/fabmanager:1.1 - -# Utilisation de l'image - -## Classique - -``` -docker run -d --name fabmanager fabmanager/fabmanager:latest -``` - -## Makefile - -### Premier lancement - -``` -make up -``` - -### Arrêt du conteneur - -``` -make stop -``` - -### Lancer le conteneur (après premier lancement) - -``` -make start -``` - -### Etat du conteneur - -``` -make ps -``` - -### Tuer le conteneur - -``` -make kill -``` - -### Effacer le conteneur - -``` -make rm -``` - -### Sequence de commande - -On peut lancer une séquence de commande en une seule ligne. Par exemple tuer le conteneur, supprimer le conteneur, construire l'image, lancer le conteneur. -``` -make kill rm build up -``` - -### Notes - -- Lors du lancement avec make le port 3000 est mappé sur le port 3000 de l'hôte local. Cela permet de joindre fabmanager dans un navigateur à l'url "http://ipdelhotedocker:3000" diff --git a/contrib/docker/fabmanager.conf b/contrib/docker/fabmanager.conf deleted file mode 100644 index 2afe1a9fa..000000000 --- a/contrib/docker/fabmanager.conf +++ /dev/null @@ -1,23 +0,0 @@ -[program:redis] -command=/usr/bin/redis-server /etc/redis/redis.conf -autostart=true -autorestart=true -user=root -stdout_logfile=/var/log/supervisor/redis.log -stderr_logfile=/var/log/supervisor/rediserror.log - -[program:postgresql] -command=/usr/lib/postgresql/9.3/bin/postgres -D /var/lib/postgresql/9.3/main -c config_file=/etc/postgresql/9.3/main/postgresql.conf -autostart=true -autorestart=true -user=postgres -stdout_logfile=/var/log/supervisor/postgres.log -stderr_logfile=/var/log/supervisor/postgreserror.log - -[program:fabmanager] -command=su -l fabmanager -c "cd /home/fabmanager/fab-manager && foreman start" -directory=/home/fabmanager/fab-manager -autostart=true -autorestart=true -stdout_logfile=/var/log/supervisor/fabmanager.log -stderr_logfile=/var/log/supervisor/fabmanagererror.log \ No newline at end of file diff --git a/contrib/docker/fabmanager.yml b/contrib/docker/fabmanager.yml deleted file mode 100644 index e692bd735..000000000 --- a/contrib/docker/fabmanager.yml +++ /dev/null @@ -1,6 +0,0 @@ -fabmanager: - hostname: fabmanager - container_name: fabmanager - image: squaregolab/fabmanager:latest - ports: - - "3000:3000" diff --git a/db/migrate/20150429084753_devise_create_users.rb b/db/migrate/20140409083104_devise_create_users.rb similarity index 80% rename from db/migrate/20150429084753_devise_create_users.rb rename to db/migrate/20140409083104_devise_create_users.rb index ebdb39e08..d2b0d6484 100644 --- a/db/migrate/20150429084753_devise_create_users.rb +++ b/db/migrate/20140409083104_devise_create_users.rb @@ -16,8 +16,8 @@ class DeviseCreateUsers < ActiveRecord::Migration t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at - t.inet :current_sign_in_ip - t.inet :last_sign_in_ip + t.string :current_sign_in_ip + t.string :last_sign_in_ip ## Confirmable t.string :confirmation_token @@ -30,10 +30,6 @@ class DeviseCreateUsers < ActiveRecord::Migration t.string :unlock_token # Only if unlock strategy is :email or :both t.datetime :locked_at - ## FabManager properties - t.boolean :is_allow_contact, default: true - t.string :username - t.string :slug t.timestamps end @@ -42,7 +38,5 @@ class DeviseCreateUsers < ActiveRecord::Migration add_index :users, :reset_password_token, unique: true add_index :users, :confirmation_token, unique: true add_index :users, :unlock_token, unique: true - add_index :users, :username, unique: true - add_index :users, :slug, unique: true end end diff --git a/db/migrate/20150429085252_rolify_create_roles.rb b/db/migrate/20140409083610_rolify_create_roles.rb similarity index 100% rename from db/migrate/20150429085252_rolify_create_roles.rb rename to db/migrate/20140409083610_rolify_create_roles.rb diff --git a/db/migrate/20150429085948_create_profiles.rb b/db/migrate/20140409153915_create_profiles.rb similarity index 82% rename from db/migrate/20150429085948_create_profiles.rb rename to db/migrate/20140409153915_create_profiles.rb index 8f556dcb7..c668ba1b1 100644 --- a/db/migrate/20150429085948_create_profiles.rb +++ b/db/migrate/20140409153915_create_profiles.rb @@ -1,7 +1,8 @@ class CreateProfiles < ActiveRecord::Migration def change create_table :profiles do |t| - t.belongs_to :user, index: true, foreign_key: true + t.belongs_to :user, index: true + t.string :username t.string :first_name t.string :last_name t.boolean :gender @@ -9,7 +10,7 @@ class CreateProfiles < ActiveRecord::Migration t.string :phone t.text :interest t.text :software_mastered - + t.timestamps end end diff --git a/db/migrate/20150429090704_create_projects.rb b/db/migrate/20140410101026_create_projects.rb similarity index 52% rename from db/migrate/20150429090704_create_projects.rb rename to db/migrate/20140410101026_create_projects.rb index 813193d6b..d5d757c8c 100644 --- a/db/migrate/20150429090704_create_projects.rb +++ b/db/migrate/20140410101026_create_projects.rb @@ -3,14 +3,8 @@ class CreateProjects < ActiveRecord::Migration create_table :projects do |t| t.string :name t.text :description - t.string :slug - t.datetime :published_at - t.integer :author_id - t.text :tags - t.string :state t.timestamps end - add_index :projects, :slug, unique: true end end diff --git a/db/migrate/20150429091737_create_machines.rb b/db/migrate/20140410140516_create_machines.rb similarity index 67% rename from db/migrate/20150429091737_create_machines.rb rename to db/migrate/20140410140516_create_machines.rb index 3bb91a6d1..7767d320c 100644 --- a/db/migrate/20150429091737_create_machines.rb +++ b/db/migrate/20140410140516_create_machines.rb @@ -1,13 +1,11 @@ class CreateMachines < ActiveRecord::Migration def change create_table :machines do |t| - t.string :name + t.string :name, null: false t.text :description t.text :spec - t.string :slug t.timestamps end - add_index :machines, :slug, unique: true end end diff --git a/db/migrate/20150429092109_create_assets.rb b/db/migrate/20140410162151_create_assets.rb similarity index 78% rename from db/migrate/20150429092109_create_assets.rb rename to db/migrate/20140410162151_create_assets.rb index d309f942e..e7b6951dc 100644 --- a/db/migrate/20150429092109_create_assets.rb +++ b/db/migrate/20140410162151_create_assets.rb @@ -1,7 +1,7 @@ class CreateAssets < ActiveRecord::Migration def change create_table :assets do |t| - t.references :viewable, polymorphic: true + t.references :viewable, polymorphic: true t.string :attachment t.string :type diff --git a/db/migrate/20150429092624_create_projects_machines.rb b/db/migrate/20140411152729_create_projects_machines.rb similarity index 51% rename from db/migrate/20150429092624_create_projects_machines.rb rename to db/migrate/20140411152729_create_projects_machines.rb index ef060a051..63bf78825 100644 --- a/db/migrate/20150429092624_create_projects_machines.rb +++ b/db/migrate/20140411152729_create_projects_machines.rb @@ -1,8 +1,8 @@ class CreateProjectsMachines < ActiveRecord::Migration def change create_table :projects_machines do |t| - t.belongs_to :project, index: true, foreign_key: true - t.belongs_to :machine, index: true, foreign_key: true + t.belongs_to :project, index: true + t.belongs_to :machine, index: true end end end diff --git a/db/migrate/20140414141134_add_is_allow_contact_to_user.rb b/db/migrate/20140414141134_add_is_allow_contact_to_user.rb new file mode 100644 index 000000000..64cea0239 --- /dev/null +++ b/db/migrate/20140414141134_add_is_allow_contact_to_user.rb @@ -0,0 +1,5 @@ +class AddIsAllowContactToUser < ActiveRecord::Migration + def change + add_column :users, :is_allow_contact, :boolean, default: true + end +end diff --git a/db/migrate/20140415104151_create_project_user.rb b/db/migrate/20140415104151_create_project_user.rb new file mode 100644 index 000000000..de510eeec --- /dev/null +++ b/db/migrate/20140415104151_create_project_user.rb @@ -0,0 +1,10 @@ +class CreateProjectUser < ActiveRecord::Migration + def change + create_table :project_users do |t| + t.belongs_to :project, index: true + t.belongs_to :user, index: true + + t.timestamps + end + end +end diff --git a/db/migrate/20140415123625_add_author_id_to_project.rb b/db/migrate/20140415123625_add_author_id_to_project.rb new file mode 100644 index 000000000..854b20132 --- /dev/null +++ b/db/migrate/20140415123625_add_author_id_to_project.rb @@ -0,0 +1,5 @@ +class AddAuthorIdToProject < ActiveRecord::Migration + def change + add_column :projects, :author_id, :integer + end +end diff --git a/db/migrate/20150429093251_create_project_steps.rb b/db/migrate/20140416130838_create_project_steps.rb similarity index 67% rename from db/migrate/20150429093251_create_project_steps.rb rename to db/migrate/20140416130838_create_project_steps.rb index f43b8a80e..1b4b69cc9 100644 --- a/db/migrate/20150429093251_create_project_steps.rb +++ b/db/migrate/20140416130838_create_project_steps.rb @@ -2,8 +2,8 @@ class CreateProjectSteps < ActiveRecord::Migration def change create_table :project_steps do |t| t.text :description - t.string :title - t.belongs_to :project, index: true, foreign_key: true + t.string :picture + t.belongs_to :project, index: true t.timestamps end diff --git a/db/migrate/20150429093734_create_availabilities.rb b/db/migrate/20140422085949_create_availabilities.rb similarity index 87% rename from db/migrate/20150429093734_create_availabilities.rb rename to db/migrate/20140422085949_create_availabilities.rb index 967c8e525..2c1050068 100644 --- a/db/migrate/20150429093734_create_availabilities.rb +++ b/db/migrate/20140422085949_create_availabilities.rb @@ -4,7 +4,6 @@ class CreateAvailabilities < ActiveRecord::Migration t.datetime :start_at t.datetime :end_at t.string :available_type - t.integer :nb_total_places t.timestamps end diff --git a/db/migrate/20140422090412_create_machines_availabilities.rb b/db/migrate/20140422090412_create_machines_availabilities.rb new file mode 100644 index 000000000..b8796d694 --- /dev/null +++ b/db/migrate/20140422090412_create_machines_availabilities.rb @@ -0,0 +1,8 @@ +class CreateMachinesAvailabilities < ActiveRecord::Migration + def change + create_table :machines_availabilities do |t| + t.belongs_to :machine, index: true + t.belongs_to :availability, index: true + end + end +end diff --git a/db/migrate/20140513152025_add_title_to_project_step.rb b/db/migrate/20140513152025_add_title_to_project_step.rb new file mode 100644 index 000000000..5ce2d678d --- /dev/null +++ b/db/migrate/20140513152025_add_title_to_project_step.rb @@ -0,0 +1,6 @@ +class AddTitleToProjectStep < ActiveRecord::Migration + def change + add_column :project_steps, :title, :string + remove_column :project_steps, :picture, :string + end +end diff --git a/db/migrate/20140516083543_create_reservations.rb b/db/migrate/20140516083543_create_reservations.rb new file mode 100644 index 000000000..68ae623cd --- /dev/null +++ b/db/migrate/20140516083543_create_reservations.rb @@ -0,0 +1,10 @@ +class CreateReservations < ActiveRecord::Migration + def change + create_table :reservations do |t| + t.belongs_to :user, index: true + t.text :message + + t.timestamps + end + end +end diff --git a/db/migrate/20140516083909_create_slots.rb b/db/migrate/20140516083909_create_slots.rb new file mode 100644 index 000000000..17d1783a9 --- /dev/null +++ b/db/migrate/20140516083909_create_slots.rb @@ -0,0 +1,11 @@ +class CreateSlots < ActiveRecord::Migration + def change + create_table :slots do |t| + t.datetime :start_at + t.datetime :end_at + t.belongs_to :reservation, index: true + + t.timestamps + end + end +end diff --git a/db/migrate/20140516093335_add_reservable_to_reservation.rb b/db/migrate/20140516093335_add_reservable_to_reservation.rb new file mode 100644 index 000000000..a5c5d337b --- /dev/null +++ b/db/migrate/20140516093335_add_reservable_to_reservation.rb @@ -0,0 +1,5 @@ +class AddReservableToReservation < ActiveRecord::Migration + def change + add_reference :reservations, :reservable, index: true, polymorphic: true + end +end diff --git a/db/migrate/20150429094525_create_addresses.rb b/db/migrate/20140522115617_create_addresses.rb similarity index 100% rename from db/migrate/20150429094525_create_addresses.rb rename to db/migrate/20140522115617_create_addresses.rb diff --git a/db/migrate/20150429095139_create_components.rb b/db/migrate/20140522175539_create_components.rb similarity index 97% rename from db/migrate/20150429095139_create_components.rb rename to db/migrate/20140522175539_create_components.rb index 66f3b6283..37dd7f4ae 100644 --- a/db/migrate/20150429095139_create_components.rb +++ b/db/migrate/20140522175539_create_components.rb @@ -4,4 +4,4 @@ class CreateComponents < ActiveRecord::Migration t.string :name, null: false end end -end +end \ No newline at end of file diff --git a/db/migrate/20150429095313_create_themes.rb b/db/migrate/20140522175714_create_themes.rb similarity index 100% rename from db/migrate/20150429095313_create_themes.rb rename to db/migrate/20140522175714_create_themes.rb diff --git a/db/migrate/20150429095339_create_licences.rb b/db/migrate/20140522180032_create_licences.rb similarity index 100% rename from db/migrate/20150429095339_create_licences.rb rename to db/migrate/20140522180032_create_licences.rb diff --git a/db/migrate/20150429095632_create_projects_components.rb b/db/migrate/20140522180930_create_projects_components.rb similarity index 52% rename from db/migrate/20150429095632_create_projects_components.rb rename to db/migrate/20140522180930_create_projects_components.rb index 365f7238d..940f8015a 100644 --- a/db/migrate/20150429095632_create_projects_components.rb +++ b/db/migrate/20140522180930_create_projects_components.rb @@ -1,8 +1,8 @@ class CreateProjectsComponents < ActiveRecord::Migration def change create_table :projects_components do |t| - t.belongs_to :project, index: true, foreign_key: true - t.belongs_to :component, index: true, foreign_key: true + t.belongs_to :project, index: true + t.belongs_to :component, index: true end end end diff --git a/db/migrate/20150429100014_create_projects_themes.rb b/db/migrate/20140522181011_create_projects_themes.rb similarity index 51% rename from db/migrate/20150429100014_create_projects_themes.rb rename to db/migrate/20140522181011_create_projects_themes.rb index 5ddd36b98..ba24c5278 100644 --- a/db/migrate/20150429100014_create_projects_themes.rb +++ b/db/migrate/20140522181011_create_projects_themes.rb @@ -1,8 +1,8 @@ class CreateProjectsThemes < ActiveRecord::Migration def change create_table :projects_themes do |t| - t.belongs_to :project, index: true, foreign_key: true - t.belongs_to :theme, index: true, foreign_key: true + t.belongs_to :project, index: true + t.belongs_to :theme, index: true end end end diff --git a/db/migrate/20140522181148_add_tags_to_project.rb b/db/migrate/20140522181148_add_tags_to_project.rb new file mode 100644 index 000000000..b3d61a482 --- /dev/null +++ b/db/migrate/20140522181148_add_tags_to_project.rb @@ -0,0 +1,5 @@ +class AddTagsToProject < ActiveRecord::Migration + def change + add_column :projects, :tags, :text + end +end diff --git a/db/migrate/20150429100123_add_licence_id_to_projects.rb b/db/migrate/20140523083230_add_licence_id_to_project.rb similarity index 53% rename from db/migrate/20150429100123_add_licence_id_to_projects.rb rename to db/migrate/20140523083230_add_licence_id_to_project.rb index 204f8f47f..df32d323e 100644 --- a/db/migrate/20150429100123_add_licence_id_to_projects.rb +++ b/db/migrate/20140523083230_add_licence_id_to_project.rb @@ -1,5 +1,5 @@ -class AddLicenceIdToProjects < ActiveRecord::Migration +class AddLicenceIdToProject < ActiveRecord::Migration def change add_column :projects, :licence_id, :integer end -end +end \ No newline at end of file diff --git a/db/migrate/20140526144327_add_state_to_project.rb b/db/migrate/20140526144327_add_state_to_project.rb new file mode 100644 index 000000000..52cd3bf06 --- /dev/null +++ b/db/migrate/20140526144327_add_state_to_project.rb @@ -0,0 +1,8 @@ +class AddStateToProject < ActiveRecord::Migration + def change + add_column :projects, :state, :string + Project.all.each do |p| + p.update_columns(state: 'published') + end + end +end diff --git a/db/migrate/20150429100442_create_notifications.rb b/db/migrate/20140527092045_create_notifications.rb similarity index 84% rename from db/migrate/20150429100442_create_notifications.rb rename to db/migrate/20140527092045_create_notifications.rb index 8058508c2..15594374c 100644 --- a/db/migrate/20150429100442_create_notifications.rb +++ b/db/migrate/20140527092045_create_notifications.rb @@ -5,8 +5,6 @@ class CreateNotifications < ActiveRecord::Migration t.references :attached_object, polymorphic: true t.integer :notification_type_id t.boolean :is_read, default: false - t.string :receiver_type - t.boolean :is_send, default: false t.timestamps end diff --git a/db/migrate/20140528134944_add_is_valid_to_project_user.rb b/db/migrate/20140528134944_add_is_valid_to_project_user.rb new file mode 100644 index 000000000..dadabcde6 --- /dev/null +++ b/db/migrate/20140528134944_add_is_valid_to_project_user.rb @@ -0,0 +1,5 @@ +class AddIsValidToProjectUser < ActiveRecord::Migration + def change + add_column :project_users, :is_valid, :boolean, default: false + end +end diff --git a/db/migrate/20140528140257_add_valid_token_to_project_user.rb b/db/migrate/20140528140257_add_valid_token_to_project_user.rb new file mode 100644 index 000000000..98a85bdba --- /dev/null +++ b/db/migrate/20140528140257_add_valid_token_to_project_user.rb @@ -0,0 +1,5 @@ +class AddValidTokenToProjectUser < ActiveRecord::Migration + def change + add_column :project_users, :valid_token, :string + end +end diff --git a/db/migrate/20140529145140_create_user_trainings.rb b/db/migrate/20140529145140_create_user_trainings.rb new file mode 100644 index 000000000..77ba1bc67 --- /dev/null +++ b/db/migrate/20140529145140_create_user_trainings.rb @@ -0,0 +1,10 @@ +class CreateUserTrainings < ActiveRecord::Migration + def change + create_table :user_trainings do |t| + t.belongs_to :user, index: true + t.belongs_to :machine, index: true + + t.timestamps + end + end +end diff --git a/db/migrate/20150429100750_create_groups.rb b/db/migrate/20140603084413_create_groups.rb similarity index 100% rename from db/migrate/20150429100750_create_groups.rb rename to db/migrate/20140603084413_create_groups.rb diff --git a/db/migrate/20140603085817_create_plans.rb b/db/migrate/20140603085817_create_plans.rb new file mode 100644 index 000000000..519c3a230 --- /dev/null +++ b/db/migrate/20140603085817_create_plans.rb @@ -0,0 +1,13 @@ +class CreatePlans < ActiveRecord::Migration + def change + create_table :plans do |t| + t.string :name + t.integer :amount + t.string :interval + t.belongs_to :group, index: true + t.string :stp_plan_id + + t.timestamps + end + end +end diff --git a/db/migrate/20140603164215_create_trainings_pricings.rb b/db/migrate/20140603164215_create_trainings_pricings.rb new file mode 100644 index 000000000..c95a81922 --- /dev/null +++ b/db/migrate/20140603164215_create_trainings_pricings.rb @@ -0,0 +1,11 @@ +class CreateTrainingsPricings < ActiveRecord::Migration + def change + create_table :trainings_pricings do |t| + t.belongs_to :machine, index: true + t.belongs_to :group, index: true + t.integer :amount + + t.timestamps + end + end +end diff --git a/db/migrate/20150429100906_add_group_to_users.rb b/db/migrate/20140604094514_add_group_to_user.rb similarity index 58% rename from db/migrate/20150429100906_add_group_to_users.rb rename to db/migrate/20140604094514_add_group_to_user.rb index 1168f16a1..f518fbbe2 100644 --- a/db/migrate/20150429100906_add_group_to_users.rb +++ b/db/migrate/20140604094514_add_group_to_user.rb @@ -1,4 +1,4 @@ -class AddGroupToUsers < ActiveRecord::Migration +class AddGroupToUser < ActiveRecord::Migration def change add_reference :users, :group, index: true end diff --git a/db/migrate/20140604113611_create_trainings.rb b/db/migrate/20140604113611_create_trainings.rb new file mode 100644 index 000000000..43e788585 --- /dev/null +++ b/db/migrate/20140604113611_create_trainings.rb @@ -0,0 +1,9 @@ +class CreateTrainings < ActiveRecord::Migration + def change + create_table :trainings do |t| + t.string :name + + t.timestamps + end + end +end diff --git a/db/migrate/20140604113919_create_trainings_machine.rb b/db/migrate/20140604113919_create_trainings_machine.rb new file mode 100644 index 000000000..7d698bd40 --- /dev/null +++ b/db/migrate/20140604113919_create_trainings_machine.rb @@ -0,0 +1,8 @@ +class CreateTrainingsMachine < ActiveRecord::Migration + def change + create_table :trainings_machines do |t| + t.belongs_to :training, index: true + t.belongs_to :machine, index: true + end + end +end diff --git a/db/migrate/20140604132045_create_trainings_availabilities.rb b/db/migrate/20140604132045_create_trainings_availabilities.rb new file mode 100644 index 000000000..4514ff969 --- /dev/null +++ b/db/migrate/20140604132045_create_trainings_availabilities.rb @@ -0,0 +1,10 @@ +class CreateTrainingsAvailabilities < ActiveRecord::Migration + def change + create_table :trainings_availabilities do |t| + t.belongs_to :training, index: true + t.belongs_to :availability, index: true + + t.timestamps + end + end +end diff --git a/db/migrate/20140605125131_add_availability_id_to_reservation.rb b/db/migrate/20140605125131_add_availability_id_to_reservation.rb new file mode 100644 index 000000000..197ffc41f --- /dev/null +++ b/db/migrate/20140605125131_add_availability_id_to_reservation.rb @@ -0,0 +1,6 @@ +class AddAvailabilityIdToReservation < ActiveRecord::Migration + def change + add_column :reservations, :availability_id, :integer + add_index :reservations, :availability_id + end +end diff --git a/db/migrate/20140605142133_change_machine_id_to_training_id_from_user_training.rb b/db/migrate/20140605142133_change_machine_id_to_training_id_from_user_training.rb new file mode 100644 index 000000000..c3bbe89fc --- /dev/null +++ b/db/migrate/20140605142133_change_machine_id_to_training_id_from_user_training.rb @@ -0,0 +1,6 @@ +class ChangeMachineIdToTrainingIdFromUserTraining < ActiveRecord::Migration + def change + remove_belongs_to :user_trainings, :machine, index: true + add_belongs_to :user_trainings, :training, index: true + end +end diff --git a/db/migrate/20140605151442_change_machine_to_training_from_trainings_pricing.rb b/db/migrate/20140605151442_change_machine_to_training_from_trainings_pricing.rb new file mode 100644 index 000000000..6f6a7484e --- /dev/null +++ b/db/migrate/20140605151442_change_machine_to_training_from_trainings_pricing.rb @@ -0,0 +1,6 @@ +class ChangeMachineToTrainingFromTrainingsPricing < ActiveRecord::Migration + def change + remove_belongs_to :trainings_pricings, :machine, index: true + add_belongs_to :trainings_pricings, :training, index: true + end +end diff --git a/db/migrate/20140606133116_create_machines_pricings.rb b/db/migrate/20140606133116_create_machines_pricings.rb new file mode 100644 index 000000000..148705e10 --- /dev/null +++ b/db/migrate/20140606133116_create_machines_pricings.rb @@ -0,0 +1,13 @@ +class CreateMachinesPricings < ActiveRecord::Migration + def change + create_table :machines_pricings do |t| + t.belongs_to :machine, index: true + t.belongs_to :group, index: true + t.integer :not_subscribe_amount + t.integer :month_amount + t.integer :year_amount + + t.timestamps + end + end +end diff --git a/db/migrate/20140609092700_remove_availability_id_form_reservations.rb b/db/migrate/20140609092700_remove_availability_id_form_reservations.rb new file mode 100644 index 000000000..109d767e0 --- /dev/null +++ b/db/migrate/20140609092700_remove_availability_id_form_reservations.rb @@ -0,0 +1,5 @@ +class RemoveAvailabilityIdFormReservations < ActiveRecord::Migration + def change + remove_column :reservations, :availability_id, :integer + end +end diff --git a/db/migrate/20140609092827_add_availability_to_slot.rb b/db/migrate/20140609092827_add_availability_to_slot.rb new file mode 100644 index 000000000..30739c7d0 --- /dev/null +++ b/db/migrate/20140609092827_add_availability_to_slot.rb @@ -0,0 +1,5 @@ +class AddAvailabilityToSlot < ActiveRecord::Migration + def change + add_reference :slots, :availability, index: true + end +end diff --git a/db/migrate/20140610153123_add_stp_customer_id_to_users.rb b/db/migrate/20140610153123_add_stp_customer_id_to_users.rb new file mode 100644 index 000000000..d45c32f75 --- /dev/null +++ b/db/migrate/20140610153123_add_stp_customer_id_to_users.rb @@ -0,0 +1,14 @@ +class AddStpCustomerIdToUsers < ActiveRecord::Migration + def up + add_column :users, :stp_customer_id, :string + User.all.each do |user| + if user.stp_customer_id.blank? + user.send(:create_stripe_customer) + end + end + end + + def down + remove_column :users, :stp_customer_id + end +end diff --git a/db/migrate/20140610170446_create_subscriptions.rb b/db/migrate/20140610170446_create_subscriptions.rb new file mode 100644 index 000000000..f05da0af1 --- /dev/null +++ b/db/migrate/20140610170446_create_subscriptions.rb @@ -0,0 +1,11 @@ +class CreateSubscriptions < ActiveRecord::Migration + def change + create_table :subscriptions do |t| + t.belongs_to :plan, index: true + t.belongs_to :user, index: true + t.string :stp_subscription_id + + t.timestamps + end + end +end diff --git a/db/migrate/20140613150651_add_stp_invoice_id_to_reservation.rb b/db/migrate/20140613150651_add_stp_invoice_id_to_reservation.rb new file mode 100644 index 000000000..200d1ec13 --- /dev/null +++ b/db/migrate/20140613150651_add_stp_invoice_id_to_reservation.rb @@ -0,0 +1,6 @@ +class AddStpInvoiceIdToReservation < ActiveRecord::Migration + def change + add_column :reservations, :stp_invoice_id, :string + add_index :reservations, :stp_invoice_id + end +end diff --git a/db/migrate/20140620131525_add_start_at_to_subscription.rb b/db/migrate/20140620131525_add_start_at_to_subscription.rb new file mode 100644 index 000000000..963361e2c --- /dev/null +++ b/db/migrate/20140620131525_add_start_at_to_subscription.rb @@ -0,0 +1,8 @@ +class AddStartAtToSubscription < ActiveRecord::Migration + def change + add_column :subscriptions, :start_at, :datetime + Subscription.all.each do |s| + s.update_columns(start_at: s.created_at) + end + end +end diff --git a/db/migrate/20150429101220_create_friendly_id_slugs.rb b/db/migrate/20140622121724_create_friendly_id_slugs.rb similarity index 100% rename from db/migrate/20150429101220_create_friendly_id_slugs.rb rename to db/migrate/20140622121724_create_friendly_id_slugs.rb diff --git a/db/migrate/20140622122944_add_slug_to_projects.rb b/db/migrate/20140622122944_add_slug_to_projects.rb new file mode 100644 index 000000000..62284f770 --- /dev/null +++ b/db/migrate/20140622122944_add_slug_to_projects.rb @@ -0,0 +1,6 @@ +class AddSlugToProjects < ActiveRecord::Migration + def change + add_column :projects, :slug, :string + add_index :projects, :slug, unique: true + end +end diff --git a/db/migrate/20140622145648_add_strip_id_to_groups.rb b/db/migrate/20140622145648_add_strip_id_to_groups.rb new file mode 100644 index 000000000..61a796972 --- /dev/null +++ b/db/migrate/20140622145648_add_strip_id_to_groups.rb @@ -0,0 +1,5 @@ +class AddStripIdToGroups < ActiveRecord::Migration + def change + add_column :groups, :stripe_id, :string + end +end diff --git a/db/migrate/20140623023557_add_slug_to_machines.rb b/db/migrate/20140623023557_add_slug_to_machines.rb new file mode 100644 index 000000000..86ac01f31 --- /dev/null +++ b/db/migrate/20140623023557_add_slug_to_machines.rb @@ -0,0 +1,6 @@ +class AddSlugToMachines < ActiveRecord::Migration + def change + add_column :machines, :slug, :string + add_index :machines, :slug, unique: true + end +end diff --git a/db/migrate/20140624123359_create_credits.rb b/db/migrate/20140624123359_create_credits.rb new file mode 100644 index 000000000..5193eeaf8 --- /dev/null +++ b/db/migrate/20140624123359_create_credits.rb @@ -0,0 +1,11 @@ +class CreateCredits < ActiveRecord::Migration + def change + create_table :credits do |t| + t.references :creditable, polymorphic: true + t.belongs_to :plan, index: true + t.integer :hours + + t.timestamps + end + end +end diff --git a/db/migrate/20140624123814_create_users_credits.rb b/db/migrate/20140624123814_create_users_credits.rb new file mode 100644 index 000000000..95fa7aad8 --- /dev/null +++ b/db/migrate/20140624123814_create_users_credits.rb @@ -0,0 +1,11 @@ +class CreateUsersCredits < ActiveRecord::Migration + def change + create_table :users_credits do |t| + t.belongs_to :user, index: true + t.belongs_to :credit, index: true + t.integer :hours_used + + t.timestamps + end + end +end diff --git a/db/migrate/20140624124338_add_training_credit_nb_to_plan.rb b/db/migrate/20140624124338_add_training_credit_nb_to_plan.rb new file mode 100644 index 000000000..1fd702733 --- /dev/null +++ b/db/migrate/20140624124338_add_training_credit_nb_to_plan.rb @@ -0,0 +1,11 @@ +class AddTrainingCreditNbToPlan < ActiveRecord::Migration + def change + add_column :plans, :training_credit_nb, :integer + + if Plan.column_names.include? "training_credit_nb" + Plan.all.each do |p| + p.update_columns(training_credit_nb: (p.interval == 'month' ? 1 : 5)) + end + end + end +end diff --git a/db/migrate/20140703100457_change_start_at_to_expired_at_from_subscription.rb b/db/migrate/20140703100457_change_start_at_to_expired_at_from_subscription.rb new file mode 100644 index 000000000..054dcbbdb --- /dev/null +++ b/db/migrate/20140703100457_change_start_at_to_expired_at_from_subscription.rb @@ -0,0 +1,16 @@ +class ChangeStartAtToExpiredAtFromSubscription < ActiveRecord::Migration + def change + remove_column :subscriptions, :start_at, :datetime + add_column :subscriptions, :expired_at, :datetime + + Subscription.all.each do |s| + if s.respond_to? :expired_at and !s.expired_at? + if s.plan.interval == 'month' + s.update_columns(expired_at: s.created_at + 1.month) + else + s.update_columns(expired_at: s.created_at + 1.year) + end + end + end + end +end diff --git a/db/migrate/20140703231208_add_username_to_user.rb b/db/migrate/20140703231208_add_username_to_user.rb new file mode 100644 index 000000000..14ea52e97 --- /dev/null +++ b/db/migrate/20140703231208_add_username_to_user.rb @@ -0,0 +1,12 @@ +class AddUsernameToUser < ActiveRecord::Migration + def change + add_column :users, :username, :string, after: :id + + User.includes(:profile).each do |u| + if u.respond_to? :username and !u.username? + u.update_columns(username: u.profile.username) + end + end + + end +end diff --git a/db/migrate/20140703233420_add_index_username_to_user.rb b/db/migrate/20140703233420_add_index_username_to_user.rb new file mode 100644 index 000000000..425e3e896 --- /dev/null +++ b/db/migrate/20140703233420_add_index_username_to_user.rb @@ -0,0 +1,5 @@ +class AddIndexUsernameToUser < ActiveRecord::Migration + def change + add_index :users, :username, unique: true + end +end diff --git a/db/migrate/20140703233942_remove_username_from_profiles.rb b/db/migrate/20140703233942_remove_username_from_profiles.rb new file mode 100644 index 000000000..38b45e236 --- /dev/null +++ b/db/migrate/20140703233942_remove_username_from_profiles.rb @@ -0,0 +1,5 @@ +class RemoveUsernameFromProfiles < ActiveRecord::Migration + def change + remove_column :profiles, :username, :string + end +end diff --git a/db/migrate/20140703235739_add_slug_to_users.rb b/db/migrate/20140703235739_add_slug_to_users.rb new file mode 100644 index 000000000..5512fe9ef --- /dev/null +++ b/db/migrate/20140703235739_add_slug_to_users.rb @@ -0,0 +1,7 @@ +class AddSlugToUsers < ActiveRecord::Migration + def change + add_column :users, :slug, :string + add_index :users, :slug, unique: true + User.find_each(&:save) + end +end diff --git a/db/migrate/20140710144142_create_events.rb b/db/migrate/20140710144142_create_events.rb new file mode 100644 index 000000000..470fb7646 --- /dev/null +++ b/db/migrate/20140710144142_create_events.rb @@ -0,0 +1,10 @@ +class CreateEvents < ActiveRecord::Migration + def change + create_table :events do |t| + t.string :title + t.text :description + + t.timestamps + end + end +end diff --git a/db/migrate/20150429102704_create_categories.rb b/db/migrate/20140710144427_create_categories.rb similarity index 100% rename from db/migrate/20150429102704_create_categories.rb rename to db/migrate/20140710144427_create_categories.rb diff --git a/db/migrate/20150429102754_create_events_categories.rb b/db/migrate/20140710144610_create_events_categories.rb similarity index 55% rename from db/migrate/20150429102754_create_events_categories.rb rename to db/migrate/20140710144610_create_events_categories.rb index 755e9b315..6eb8bae77 100644 --- a/db/migrate/20150429102754_create_events_categories.rb +++ b/db/migrate/20140710144610_create_events_categories.rb @@ -1,8 +1,8 @@ class CreateEventsCategories < ActiveRecord::Migration def change create_table :events_categories do |t| - t.belongs_to :event, index: true, foreign_key: true - t.belongs_to :category, index: true, foreign_key: true + t.belongs_to :event, index: true + t.belongs_to :category, index: true t.timestamps end diff --git a/db/migrate/20140711084809_change_event.rb b/db/migrate/20140711084809_change_event.rb new file mode 100644 index 000000000..cb59d2bb3 --- /dev/null +++ b/db/migrate/20140711084809_change_event.rb @@ -0,0 +1,10 @@ +class ChangeEvent < ActiveRecord::Migration + def change + add_column :events, :availability_id, :integer + add_index :events, :availability_id + add_column :events, :amount, :integer + add_column :events, :reduced_amount, :integer + add_column :events, :nb_total_places, :integer + add_column :events, :nb_free_places, :integer + end +end diff --git a/db/migrate/20140715095503_add_nb_reserve_places_and_nb_reserve_reduced_places_to_reservation.rb b/db/migrate/20140715095503_add_nb_reserve_places_and_nb_reserve_reduced_places_to_reservation.rb new file mode 100644 index 000000000..48ba1e4ae --- /dev/null +++ b/db/migrate/20140715095503_add_nb_reserve_places_and_nb_reserve_reduced_places_to_reservation.rb @@ -0,0 +1,6 @@ +class AddNbReservePlacesAndNbReserveReducedPlacesToReservation < ActiveRecord::Migration + def change + add_column :reservations, :nb_reserve_places, :integer + add_column :reservations, :nb_reserve_reduced_places, :integer + end +end diff --git a/db/migrate/20140717143735_add_recurrence_id_to_event.rb b/db/migrate/20140717143735_add_recurrence_id_to_event.rb new file mode 100644 index 000000000..d6a1774d6 --- /dev/null +++ b/db/migrate/20140717143735_add_recurrence_id_to_event.rb @@ -0,0 +1,12 @@ +class AddRecurrenceIdToEvent < ActiveRecord::Migration + def change + add_column :events, :recurrence_id, :integer + add_index :events, :recurrence_id + + Event.all.each do |e| + if e.respond_to? :recurrence_id and !e.recurrence_id? + e.update_columns(recurrence_id: e.id) + end + end + end +end diff --git a/db/migrate/20140722162046_create_invoices.rb b/db/migrate/20140722162046_create_invoices.rb new file mode 100644 index 000000000..bdc4b7a86 --- /dev/null +++ b/db/migrate/20140722162046_create_invoices.rb @@ -0,0 +1,11 @@ +class CreateInvoices < ActiveRecord::Migration + def change + create_table :invoices do |t| + t.references :invoiced, polymorphic: true + t.string :stp_invoice_id + t.integer :total + + t.timestamps + end + end +end diff --git a/db/migrate/20140722162309_create_invoice_items.rb b/db/migrate/20140722162309_create_invoice_items.rb new file mode 100644 index 000000000..4c0ca5c7e --- /dev/null +++ b/db/migrate/20140722162309_create_invoice_items.rb @@ -0,0 +1,12 @@ +class CreateInvoiceItems < ActiveRecord::Migration + def change + create_table :invoice_items do |t| + t.text :description + t.belongs_to :invoice, index: true + t.string :stp_invoice_item_id + t.string :amount + + t.timestamps + end + end +end diff --git a/db/migrate/20140723075942_add_user_id_to_invoice.rb b/db/migrate/20140723075942_add_user_id_to_invoice.rb new file mode 100644 index 000000000..ab6cb4add --- /dev/null +++ b/db/migrate/20140723075942_add_user_id_to_invoice.rb @@ -0,0 +1,5 @@ +class AddUserIdToInvoice < ActiveRecord::Migration + def change + add_reference :invoices, :user, index: true + end +end diff --git a/db/migrate/20140723171547_remove_description_from_invoice_items.rb b/db/migrate/20140723171547_remove_description_from_invoice_items.rb new file mode 100644 index 000000000..09d85502b --- /dev/null +++ b/db/migrate/20140723171547_remove_description_from_invoice_items.rb @@ -0,0 +1,5 @@ +class RemoveDescriptionFromInvoiceItems < ActiveRecord::Migration + def change + remove_column :invoice_items, :description, :string + end +end diff --git a/db/migrate/20140723172610_add_invoiced_to_invoice_items.rb b/db/migrate/20140723172610_add_invoiced_to_invoice_items.rb new file mode 100644 index 000000000..8a7670e0f --- /dev/null +++ b/db/migrate/20140723172610_add_invoiced_to_invoice_items.rb @@ -0,0 +1,6 @@ +class AddInvoicedToInvoiceItems < ActiveRecord::Migration + def change + add_column :invoice_items, :invoiced_id, :integer + add_column :invoice_items, :invoiced_type, :string + end +end diff --git a/db/migrate/20140724125605_remove_invoiced_from_invoice_items.rb b/db/migrate/20140724125605_remove_invoiced_from_invoice_items.rb new file mode 100644 index 000000000..cfa71c143 --- /dev/null +++ b/db/migrate/20140724125605_remove_invoiced_from_invoice_items.rb @@ -0,0 +1,6 @@ +class RemoveInvoicedFromInvoiceItems < ActiveRecord::Migration + def change + remove_column :invoice_items, :invoiced_id, :integer + remove_column :invoice_items, :invoiced_type, :string + end +end diff --git a/db/migrate/20140724131808_add_description_to_invoice_items.rb b/db/migrate/20140724131808_add_description_to_invoice_items.rb new file mode 100644 index 000000000..bfe2f521f --- /dev/null +++ b/db/migrate/20140724131808_add_description_to_invoice_items.rb @@ -0,0 +1,5 @@ +class AddDescriptionToInvoiceItems < ActiveRecord::Migration + def change + add_column :invoice_items, :description, :text + end +end diff --git a/db/migrate/20140724132655_add_subscription_id_to_invoice_items.rb b/db/migrate/20140724132655_add_subscription_id_to_invoice_items.rb new file mode 100644 index 000000000..8163f0eba --- /dev/null +++ b/db/migrate/20140724132655_add_subscription_id_to_invoice_items.rb @@ -0,0 +1,5 @@ +class AddSubscriptionIdToInvoiceItems < ActiveRecord::Migration + def change + add_column :invoice_items, :subscription_id, :integer + end +end diff --git a/db/migrate/20140728110430_add_reference_to_invoice.rb b/db/migrate/20140728110430_add_reference_to_invoice.rb new file mode 100644 index 000000000..4cd0f0424 --- /dev/null +++ b/db/migrate/20140728110430_add_reference_to_invoice.rb @@ -0,0 +1,5 @@ +class AddReferenceToInvoice < ActiveRecord::Migration + def change + add_column :invoices, :reference, :string + end +end diff --git a/db/migrate/20141002111736_add_canceled_at_to_subscription.rb b/db/migrate/20141002111736_add_canceled_at_to_subscription.rb new file mode 100644 index 000000000..d4d98d232 --- /dev/null +++ b/db/migrate/20141002111736_add_canceled_at_to_subscription.rb @@ -0,0 +1,5 @@ +class AddCanceledAtToSubscription < ActiveRecord::Migration + def change + add_column :subscriptions, :canceled_at, :datetime + end +end diff --git a/db/migrate/20141110131407_add_to_cancel_to_subscription.rb b/db/migrate/20141110131407_add_to_cancel_to_subscription.rb new file mode 100644 index 000000000..a131465af --- /dev/null +++ b/db/migrate/20141110131407_add_to_cancel_to_subscription.rb @@ -0,0 +1,5 @@ +class AddToCancelToSubscription < ActiveRecord::Migration + def change + add_column :subscriptions, :to_cancel, :boolean, default: true + end +end diff --git a/db/migrate/20141215142044_remove_to_cancel_from_subscription.rb b/db/migrate/20141215142044_remove_to_cancel_from_subscription.rb new file mode 100644 index 000000000..8ab09c071 --- /dev/null +++ b/db/migrate/20141215142044_remove_to_cancel_from_subscription.rb @@ -0,0 +1,5 @@ +class RemoveToCancelFromSubscription < ActiveRecord::Migration + def change + remove_column :subscriptions, :to_cancel, :string + end +end diff --git a/db/migrate/20141215153643_create_offer_days.rb b/db/migrate/20141215153643_create_offer_days.rb new file mode 100644 index 000000000..8f8b66f0a --- /dev/null +++ b/db/migrate/20141215153643_create_offer_days.rb @@ -0,0 +1,11 @@ +class CreateOfferDays < ActiveRecord::Migration + def change + create_table :offer_days do |t| + t.belongs_to :subscription, index: true + t.datetime :start_at + t.datetime :end_at + + t.timestamps + end + end +end diff --git a/db/migrate/20141217141648_add_nb_total_places_to_trainings.rb b/db/migrate/20141217141648_add_nb_total_places_to_trainings.rb new file mode 100644 index 000000000..31ae4141e --- /dev/null +++ b/db/migrate/20141217141648_add_nb_total_places_to_trainings.rb @@ -0,0 +1,5 @@ +class AddNbTotalPlacesToTrainings < ActiveRecord::Migration + def change + add_column :trainings, :nb_total_places, :integer + end +end diff --git a/db/migrate/20141217172843_add_nb_total_places_to_availability.rb b/db/migrate/20141217172843_add_nb_total_places_to_availability.rb new file mode 100644 index 000000000..cf0c67df2 --- /dev/null +++ b/db/migrate/20141217172843_add_nb_total_places_to_availability.rb @@ -0,0 +1,5 @@ +class AddNbTotalPlacesToAvailability < ActiveRecord::Migration + def change + add_column :availabilities, :nb_total_places, :integer + end +end diff --git a/db/migrate/20150107103903_add_slug_to_trainings.rb b/db/migrate/20150107103903_add_slug_to_trainings.rb new file mode 100644 index 000000000..69e0bec22 --- /dev/null +++ b/db/migrate/20150107103903_add_slug_to_trainings.rb @@ -0,0 +1,16 @@ +class AddSlugToTrainings < ActiveRecord::Migration + def up + add_column :trainings, :slug, :string + add_index :trainings, :slug, unique: true + + Training.all.each do |t| + slug = t.send(:set_slug) || t.slug + t.update_columns(slug: slug) + end + end + + def down + remove_column :trainings, :slug, :string + remove_index :trainings, :slug, unique: true + end +end diff --git a/db/migrate/20150108082541_add_published_at_to_projects.rb b/db/migrate/20150108082541_add_published_at_to_projects.rb new file mode 100644 index 000000000..17d6143a4 --- /dev/null +++ b/db/migrate/20150108082541_add_published_at_to_projects.rb @@ -0,0 +1,13 @@ +class AddPublishedAtToProjects < ActiveRecord::Migration + def up + add_column :projects, :published_at, :datetime + + Project.find_each do |p| + p.update_columns(published_at: p.updated_at) + end + end + + def down + remove_column :projects, :published_at, :datetime + end +end diff --git a/db/migrate/20150112160349_create_statistic_indices.rb b/db/migrate/20150112160349_create_statistic_indices.rb new file mode 100644 index 000000000..5d606669f --- /dev/null +++ b/db/migrate/20150112160349_create_statistic_indices.rb @@ -0,0 +1,11 @@ +class CreateStatisticIndices < ActiveRecord::Migration + def change + create_table :statistic_indices do |t| + t.string :es_type_key + t.string :additional_field + t.string :label + + t.timestamps + end + end +end diff --git a/db/migrate/20150112160405_create_statistic_types.rb b/db/migrate/20150112160405_create_statistic_types.rb new file mode 100644 index 000000000..75af6bdd2 --- /dev/null +++ b/db/migrate/20150112160405_create_statistic_types.rb @@ -0,0 +1,12 @@ +class CreateStatisticTypes < ActiveRecord::Migration + def change + create_table :statistic_types do |t| + t.belongs_to :statistic_index, index: true + t.string :key + t.string :label + t.boolean :graph + + t.timestamps + end + end +end diff --git a/db/migrate/20150112160425_create_statistic_sub_types.rb b/db/migrate/20150112160425_create_statistic_sub_types.rb new file mode 100644 index 000000000..a3550a0f3 --- /dev/null +++ b/db/migrate/20150112160425_create_statistic_sub_types.rb @@ -0,0 +1,11 @@ +class CreateStatisticSubTypes < ActiveRecord::Migration + def change + create_table :statistic_sub_types do |t| + t.belongs_to :statistic_type, index: true + t.string :key + t.string :label + + t.timestamps + end + end +end diff --git a/db/migrate/20150113112757_add_simple_to_statistic_sub_type.rb b/db/migrate/20150113112757_add_simple_to_statistic_sub_type.rb new file mode 100644 index 000000000..2ef0c83bd --- /dev/null +++ b/db/migrate/20150113112757_add_simple_to_statistic_sub_type.rb @@ -0,0 +1,5 @@ +class AddSimpleToStatisticSubType < ActiveRecord::Migration + def change + add_column :statistic_sub_types, :simple, :boolean + end +end diff --git a/db/migrate/20150114111132_remove_simple_from_statistic_sub_type.rb b/db/migrate/20150114111132_remove_simple_from_statistic_sub_type.rb new file mode 100644 index 000000000..88b2fed56 --- /dev/null +++ b/db/migrate/20150114111132_remove_simple_from_statistic_sub_type.rb @@ -0,0 +1,5 @@ +class RemoveSimpleFromStatisticSubType < ActiveRecord::Migration + def change + remove_column :statistic_sub_types, :simple, :boolean + end +end diff --git a/db/migrate/20150114111243_add_simple_to_statistic_type.rb b/db/migrate/20150114111243_add_simple_to_statistic_type.rb new file mode 100644 index 000000000..977e99148 --- /dev/null +++ b/db/migrate/20150114111243_add_simple_to_statistic_type.rb @@ -0,0 +1,5 @@ +class AddSimpleToStatisticType < ActiveRecord::Migration + def change + add_column :statistic_types, :simple, :boolean + end +end diff --git a/db/migrate/20150114141926_create_statistic_fields.rb b/db/migrate/20150114141926_create_statistic_fields.rb new file mode 100644 index 000000000..2298a2aca --- /dev/null +++ b/db/migrate/20150114141926_create_statistic_fields.rb @@ -0,0 +1,11 @@ +class CreateStatisticFields < ActiveRecord::Migration + def change + create_table :statistic_fields do |t| + t.belongs_to :statistic_index, index: true + t.string :key + t.string :label + + t.timestamps + end + end +end diff --git a/db/migrate/20150114142032_remove_additional_field_from_statistic_index.rb b/db/migrate/20150114142032_remove_additional_field_from_statistic_index.rb new file mode 100644 index 000000000..e1acacc45 --- /dev/null +++ b/db/migrate/20150114142032_remove_additional_field_from_statistic_index.rb @@ -0,0 +1,5 @@ +class RemoveAdditionalFieldFromStatisticIndex < ActiveRecord::Migration + def change + remove_column :statistic_indices, :additional_field, :string + end +end diff --git a/db/migrate/20150115143750_add_type_to_statistic_field.rb b/db/migrate/20150115143750_add_type_to_statistic_field.rb new file mode 100644 index 000000000..476350089 --- /dev/null +++ b/db/migrate/20150115143750_add_type_to_statistic_field.rb @@ -0,0 +1,5 @@ +class AddTypeToStatisticField < ActiveRecord::Migration + def change + add_column :statistic_fields, :type, :string + end +end diff --git a/db/migrate/20150119082931_rename_statistic_field_type_to_data_type.rb b/db/migrate/20150119082931_rename_statistic_field_type_to_data_type.rb new file mode 100644 index 000000000..07a2906bb --- /dev/null +++ b/db/migrate/20150119082931_rename_statistic_field_type_to_data_type.rb @@ -0,0 +1,5 @@ +class RenameStatisticFieldTypeToDataType < ActiveRecord::Migration + def change + rename_column :statistic_fields, :type, :data_type + end +end diff --git a/db/migrate/20150119092557_remove_statistic_type_id_from_statistic_sub_type.rb b/db/migrate/20150119092557_remove_statistic_type_id_from_statistic_sub_type.rb new file mode 100644 index 000000000..7aec61396 --- /dev/null +++ b/db/migrate/20150119092557_remove_statistic_type_id_from_statistic_sub_type.rb @@ -0,0 +1,5 @@ +class RemoveStatisticTypeIdFromStatisticSubType < ActiveRecord::Migration + def change + remove_column :statistic_sub_types, :statistic_type_id, :integer + end +end diff --git a/db/migrate/20150119093811_create_statistic_type_sub_types.rb b/db/migrate/20150119093811_create_statistic_type_sub_types.rb new file mode 100644 index 000000000..e9612f6eb --- /dev/null +++ b/db/migrate/20150119093811_create_statistic_type_sub_types.rb @@ -0,0 +1,10 @@ +class CreateStatisticTypeSubTypes < ActiveRecord::Migration + def change + create_table :statistic_type_sub_types do |t| + t.belongs_to :statistic_type, index: true + t.belongs_to :statistic_sub_type, index: true + + t.timestamps + end + end +end diff --git a/db/migrate/20150119160758_add_table_to_statistic_index.rb b/db/migrate/20150119160758_add_table_to_statistic_index.rb new file mode 100644 index 000000000..113ff17d8 --- /dev/null +++ b/db/migrate/20150119160758_add_table_to_statistic_index.rb @@ -0,0 +1,5 @@ +class AddTableToStatisticIndex < ActiveRecord::Migration + def change + add_column :statistic_indices, :table, :boolean, default: true + end +end diff --git a/db/migrate/20150119161004_create_statistic_graphs.rb b/db/migrate/20150119161004_create_statistic_graphs.rb new file mode 100644 index 000000000..1f628d8a0 --- /dev/null +++ b/db/migrate/20150119161004_create_statistic_graphs.rb @@ -0,0 +1,11 @@ +class CreateStatisticGraphs < ActiveRecord::Migration + def change + create_table :statistic_graphs do |t| + t.belongs_to :statistic_index, index: true + t.string :chart_type + t.integer :limit + + t.timestamps + end + end +end diff --git a/db/migrate/20150127101521_add_ca_to_statistic_index.rb b/db/migrate/20150127101521_add_ca_to_statistic_index.rb new file mode 100644 index 000000000..fd08b81c0 --- /dev/null +++ b/db/migrate/20150127101521_add_ca_to_statistic_index.rb @@ -0,0 +1,5 @@ +class AddCaToStatisticIndex < ActiveRecord::Migration + def change + add_column :statistic_indices, :ca, :boolean, default: true + end +end diff --git a/db/migrate/20150127155141_add_avoir_mode_to_invoice.rb b/db/migrate/20150127155141_add_avoir_mode_to_invoice.rb new file mode 100644 index 000000000..c2a5b15ee --- /dev/null +++ b/db/migrate/20150127155141_add_avoir_mode_to_invoice.rb @@ -0,0 +1,5 @@ +class AddAvoirModeToInvoice < ActiveRecord::Migration + def change + add_column :invoices, :avoir_mode, :string + end +end diff --git a/db/migrate/20150127161235_add_avoir_date_to_invoice.rb b/db/migrate/20150127161235_add_avoir_date_to_invoice.rb new file mode 100644 index 000000000..8f494b65e --- /dev/null +++ b/db/migrate/20150127161235_add_avoir_date_to_invoice.rb @@ -0,0 +1,5 @@ +class AddAvoirDateToInvoice < ActiveRecord::Migration + def change + add_column :invoices, :avoir_date, :datetime + end +end diff --git a/db/migrate/20150127172510_add_invoice_id_to_invoice.rb b/db/migrate/20150127172510_add_invoice_id_to_invoice.rb new file mode 100644 index 000000000..5a63d75d5 --- /dev/null +++ b/db/migrate/20150127172510_add_invoice_id_to_invoice.rb @@ -0,0 +1,6 @@ +class AddInvoiceIdToInvoice < ActiveRecord::Migration + def change + add_reference :invoices, :invoice, index: true + add_column :invoices, :type, :string + end +end diff --git a/db/migrate/20150128132219_add_subscription_to_expire_to_invoices.rb b/db/migrate/20150128132219_add_subscription_to_expire_to_invoices.rb new file mode 100644 index 000000000..5e1251099 --- /dev/null +++ b/db/migrate/20150128132219_add_subscription_to_expire_to_invoices.rb @@ -0,0 +1,5 @@ +class AddSubscriptionToExpireToInvoices < ActiveRecord::Migration + def change + add_column :invoices, :subscription_to_expire, :boolean + end +end diff --git a/db/migrate/20150218154032_add_receiver_type_and_is_send_to_notification.rb b/db/migrate/20150218154032_add_receiver_type_and_is_send_to_notification.rb new file mode 100644 index 000000000..8d8f65a11 --- /dev/null +++ b/db/migrate/20150218154032_add_receiver_type_and_is_send_to_notification.rb @@ -0,0 +1,8 @@ +class AddReceiverTypeAndIsSendToNotification < ActiveRecord::Migration + def change + add_column :notifications, :receiver_type, :string + add_column :notifications, :is_send, :boolean, default: false + + Notification.update_all(receiver_type: 'User', is_send: true) + end +end diff --git a/db/migrate/20150428075148_add_is_active_attribute_to_users.rb b/db/migrate/20150428075148_add_is_active_attribute_to_users.rb new file mode 100644 index 000000000..57280e8f5 --- /dev/null +++ b/db/migrate/20150428075148_add_is_active_attribute_to_users.rb @@ -0,0 +1,5 @@ +class AddIsActiveAttributeToUsers < ActiveRecord::Migration + def change + add_column :users, :is_active, :boolean, default: true + end +end diff --git a/db/migrate/20150428091057_create_settings.rb b/db/migrate/20150428091057_create_settings.rb new file mode 100644 index 000000000..7aaa9c467 --- /dev/null +++ b/db/migrate/20150428091057_create_settings.rb @@ -0,0 +1,12 @@ +class CreateSettings < ActiveRecord::Migration + def change + create_table :settings do |t| + t.string :name, null: false + t.text :value + + t.timestamps null: false + end + + add_index :settings, :name, unique: true + end +end diff --git a/db/migrate/20150429092847_create_project_users.rb b/db/migrate/20150429092847_create_project_users.rb deleted file mode 100644 index 65ec1b0b9..000000000 --- a/db/migrate/20150429092847_create_project_users.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateProjectUsers < ActiveRecord::Migration - def change - create_table :project_users do |t| - t.belongs_to :project, index: true, foreign_key: true - t.belongs_to :user, index: true, foreign_key: true - t.boolean :is_valid, default: false - t.string :valid_token - - t.timestamps - end - end -end diff --git a/db/migrate/20150429102505_create_events.rb b/db/migrate/20150429102505_create_events.rb deleted file mode 100644 index 58f790774..000000000 --- a/db/migrate/20150429102505_create_events.rb +++ /dev/null @@ -1,17 +0,0 @@ -class CreateEvents < ActiveRecord::Migration - def change - create_table :events do |t| - t.string :title - t.text :description - t.integer :availability_id - t.integer :amount - t.integer :reduced_amount - t.integer :nb_total_places - t.integer :recurrence_id - - t.timestamps - end - add_index :events, :availability_id - add_index :events, :recurrence_id - end -end diff --git a/db/migrate/20150506090921_add_description_to_trainings.rb b/db/migrate/20150506090921_add_description_to_trainings.rb new file mode 100644 index 000000000..852afa19e --- /dev/null +++ b/db/migrate/20150506090921_add_description_to_trainings.rb @@ -0,0 +1,5 @@ +class AddDescriptionToTrainings < ActiveRecord::Migration + def change + add_column :trainings, :description, :text + end +end diff --git a/db/migrate/20150507075506_add_ex_start_at_to_slots.rb b/db/migrate/20150507075506_add_ex_start_at_to_slots.rb new file mode 100644 index 000000000..6fcd992b8 --- /dev/null +++ b/db/migrate/20150507075506_add_ex_start_at_to_slots.rb @@ -0,0 +1,5 @@ +class AddExStartAtToSlots < ActiveRecord::Migration + def change + add_column :slots, :ex_start_at, :datetime + end +end diff --git a/db/migrate/20150507075620_add_ex_end_at_to_slots.rb b/db/migrate/20150507075620_add_ex_end_at_to_slots.rb new file mode 100644 index 000000000..cfdb303e9 --- /dev/null +++ b/db/migrate/20150507075620_add_ex_end_at_to_slots.rb @@ -0,0 +1,5 @@ +class AddExEndAtToSlots < ActiveRecord::Migration + def change + add_column :slots, :ex_end_at, :datetime + end +end diff --git a/db/migrate/20150512123546_add_attributes_to_plan.rb b/db/migrate/20150512123546_add_attributes_to_plan.rb new file mode 100644 index 000000000..cdb7aa605 --- /dev/null +++ b/db/migrate/20150512123546_add_attributes_to_plan.rb @@ -0,0 +1,7 @@ +class AddAttributesToPlan < ActiveRecord::Migration + def change + add_column :plans, :is_rolling, :boolean, default: true + add_column :plans, :description, :text + add_column :plans, :type, :string + end +end diff --git a/db/migrate/20150520132030_create_prices.rb b/db/migrate/20150520132030_create_prices.rb new file mode 100644 index 000000000..52b9e9f56 --- /dev/null +++ b/db/migrate/20150520132030_create_prices.rb @@ -0,0 +1,12 @@ +class CreatePrices < ActiveRecord::Migration + def change + create_table :prices do |t| + t.belongs_to :group, index: true, foreign_key: true + t.belongs_to :plan, index: true, foreign_key: true + t.belongs_to :priceable, polymorphic: true, index: true + t.integer :amount + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150520133409_migrate_data_from_machines_pricings_to_prices.rb b/db/migrate/20150520133409_migrate_data_from_machines_pricings_to_prices.rb new file mode 100644 index 000000000..d9c71c79c --- /dev/null +++ b/db/migrate/20150520133409_migrate_data_from_machines_pricings_to_prices.rb @@ -0,0 +1,35 @@ +class MigrateDataFromMachinesPricingsToPrices < ActiveRecord::Migration + def up + insert <<-SQL + DELETE FROM machines_pricings WHERE machine_id NOT IN ( select distinct machines.id from machines ) + SQL + + machines_pricings = select_all("SELECT * FROM machines_pricings") + machines_pricings.each do |m_pricing| + time = "'#{Time.now.to_s(:db)}'" + + plan_year = select_one("SELECT id FROM plans where plans.group_id = #{m_pricing['group_id']} and plans.interval = 'year'") + insert <<-SQL + INSERT INTO prices (group_id, plan_id, priceable_id, priceable_type, amount, created_at, updated_at) + VALUES (#{m_pricing['group_id']}, #{plan_year['id']}, #{m_pricing['machine_id']}, 'Machine', #{m_pricing['year_amount']}, #{time}, #{time}) + SQL + + plan_month = select_one("SELECT id FROM plans where plans.group_id = #{m_pricing['group_id']} and plans.interval = 'month'") + insert <<-SQL + INSERT INTO prices (group_id, plan_id, priceable_id, priceable_type, amount, created_at, updated_at) + VALUES (#{m_pricing['group_id']}, #{plan_month['id']}, #{m_pricing['machine_id']}, 'Machine', #{m_pricing['month_amount']}, #{time}, #{time}) + SQL + + insert <<-SQL + INSERT INTO prices (group_id, plan_id, priceable_id, priceable_type, amount, created_at, updated_at) + VALUES (#{m_pricing['group_id']}, NULL, #{m_pricing['machine_id']}, 'Machine', #{m_pricing['not_subscribe_amount']}, #{time}, #{time}) + SQL + end + end + + def down + insert <<-SQL + DELETE FROM prices + SQL + end +end diff --git a/db/migrate/20150526130729_add_base_name_to_plans.rb b/db/migrate/20150526130729_add_base_name_to_plans.rb new file mode 100644 index 000000000..1b3f93900 --- /dev/null +++ b/db/migrate/20150526130729_add_base_name_to_plans.rb @@ -0,0 +1,5 @@ +class AddBaseNameToPlans < ActiveRecord::Migration + def change + add_column :plans, :base_name, :string + end +end diff --git a/db/migrate/20150527153312_add_canceled_at_to_slot.rb b/db/migrate/20150527153312_add_canceled_at_to_slot.rb new file mode 100644 index 000000000..36ee50562 --- /dev/null +++ b/db/migrate/20150527153312_add_canceled_at_to_slot.rb @@ -0,0 +1,5 @@ +class AddCanceledAtToSlot < ActiveRecord::Migration + def change + add_column :slots, :canceled_at, :datetime, default: nil + end +end diff --git a/db/migrate/20150529113555_add_default_for_training_nb_credit_to_plans.rb b/db/migrate/20150529113555_add_default_for_training_nb_credit_to_plans.rb new file mode 100644 index 000000000..ff7a42ada --- /dev/null +++ b/db/migrate/20150529113555_add_default_for_training_nb_credit_to_plans.rb @@ -0,0 +1,17 @@ +class AddDefaultForTrainingNbCreditToPlans < ActiveRecord::Migration + def up + change_column_default :plans, :training_credit_nb, 0 + + execute <<-SQL + UPDATE plans SET training_credit_nb = 0 WHERE training_credit_nb is NULL; + SQL + end + + def down + change_column_default :plans, :training_credit_nb, nil + + execute <<-SQL + UPDATE plans SET training_credit_nb = NULL WHERE training_credit_nb = 0; + SQL + end +end diff --git a/db/migrate/20150601125944_change_stripe_id_to_short_name_from_groups.rb b/db/migrate/20150601125944_change_stripe_id_to_short_name_from_groups.rb new file mode 100644 index 000000000..a01e392ef --- /dev/null +++ b/db/migrate/20150601125944_change_stripe_id_to_short_name_from_groups.rb @@ -0,0 +1,5 @@ +class ChangeStripeIdToShortNameFromGroups < ActiveRecord::Migration + def change + rename_column :groups, :stripe_id, :short_name + end +end diff --git a/db/migrate/20150603104502_change_short_name_to_slug_from_group.rb b/db/migrate/20150603104502_change_short_name_to_slug_from_group.rb new file mode 100644 index 000000000..19a46467c --- /dev/null +++ b/db/migrate/20150603104502_change_short_name_to_slug_from_group.rb @@ -0,0 +1,5 @@ +class ChangeShortNameToSlugFromGroup < ActiveRecord::Migration + def change + rename_column :groups, :short_name, :slug + end +end diff --git a/db/migrate/20150603104658_add_unique_index_to_slug_from_group.rb b/db/migrate/20150603104658_add_unique_index_to_slug_from_group.rb new file mode 100644 index 000000000..0ec431e10 --- /dev/null +++ b/db/migrate/20150603104658_add_unique_index_to_slug_from_group.rb @@ -0,0 +1,5 @@ +class AddUniqueIndexToSlugFromGroup < ActiveRecord::Migration + def change + add_index :groups, :slug, unique: true + end +end diff --git a/db/migrate/20150603133050_drop_machines_pricings.rb b/db/migrate/20150603133050_drop_machines_pricings.rb new file mode 100644 index 000000000..f1502b997 --- /dev/null +++ b/db/migrate/20150603133050_drop_machines_pricings.rb @@ -0,0 +1,5 @@ +class DropMachinesPricings < ActiveRecord::Migration + def up + drop_table :machines_pricings + end +end diff --git a/db/migrate/20150604081757_add_ui_weight_to_plans.rb b/db/migrate/20150604081757_add_ui_weight_to_plans.rb new file mode 100644 index 000000000..ac9df21ef --- /dev/null +++ b/db/migrate/20150604081757_add_ui_weight_to_plans.rb @@ -0,0 +1,5 @@ +class AddUiWeightToPlans < ActiveRecord::Migration + def change + add_column :plans, :ui_weight, :integer, default: 0 + end +end diff --git a/db/migrate/20150604131525_add_meta_data_to_notifications.rb b/db/migrate/20150604131525_add_meta_data_to_notifications.rb new file mode 100644 index 000000000..4333870b0 --- /dev/null +++ b/db/migrate/20150604131525_add_meta_data_to_notifications.rb @@ -0,0 +1,5 @@ +class AddMetaDataToNotifications < ActiveRecord::Migration + def change + add_column :notifications, :meta_data, :jsonb, default: '{}' + end +end diff --git a/db/migrate/20150608142234_add_invoicing_disabled_to_users.rb b/db/migrate/20150608142234_add_invoicing_disabled_to_users.rb new file mode 100644 index 000000000..5e8fcbc1d --- /dev/null +++ b/db/migrate/20150608142234_add_invoicing_disabled_to_users.rb @@ -0,0 +1,5 @@ +class AddInvoicingDisabledToUsers < ActiveRecord::Migration + def change + add_column :users, :invoicing_disabled, :boolean, default: false + end +end diff --git a/db/migrate/20150609094336_add_interval_count_to_plans.rb b/db/migrate/20150609094336_add_interval_count_to_plans.rb new file mode 100644 index 000000000..7f1f957f0 --- /dev/null +++ b/db/migrate/20150609094336_add_interval_count_to_plans.rb @@ -0,0 +1,5 @@ +class AddIntervalCountToPlans < ActiveRecord::Migration + def change + add_column :plans, :interval_count, :integer, default: 1 + end +end diff --git a/db/migrate/20150615135539_migrate_plan_stats.rb b/db/migrate/20150615135539_migrate_plan_stats.rb new file mode 100644 index 000000000..cecd8edcb --- /dev/null +++ b/db/migrate/20150615135539_migrate_plan_stats.rb @@ -0,0 +1,22 @@ +class MigratePlanStats < ActiveRecord::Migration + def up + index = StatisticIndex.where({es_type_key: 'subscription'}).first + if index + StatisticType.where({statistic_index_id: index.id}).destroy_all + + Plan.all.each do |p| + p.create_statistic_type + end + end + end + + def down + index = StatisticIndex.where({es_type_key: 'subscription'}).first + if index + StatisticType.where({statistic_index_id: index.id}).destroy_all + + StatisticType.create!({statistic_index_id: index.id, key: 'month', label: 'Abonnements mensuels', graph: true, simple: true}) + StatisticType.create!({statistic_index_id: index.id, key: 'year', label: 'Abonnements annuels', graph: true, simple: true}) + end + end +end diff --git a/db/migrate/20150617085623_create_custom_assets.rb b/db/migrate/20150617085623_create_custom_assets.rb new file mode 100644 index 000000000..46554dea7 --- /dev/null +++ b/db/migrate/20150617085623_create_custom_assets.rb @@ -0,0 +1,9 @@ +class CreateCustomAssets < ActiveRecord::Migration + def change + create_table :custom_assets do |t| + t.string :name + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150701090642_create_stylesheets.rb b/db/migrate/20150701090642_create_stylesheets.rb new file mode 100644 index 000000000..289ae391c --- /dev/null +++ b/db/migrate/20150701090642_create_stylesheets.rb @@ -0,0 +1,9 @@ +class CreateStylesheets < ActiveRecord::Migration + def change + create_table :stylesheets do |t| + t.text :contents + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150702150754_create_tags.rb b/db/migrate/20150702150754_create_tags.rb new file mode 100644 index 000000000..69d6a7735 --- /dev/null +++ b/db/migrate/20150702150754_create_tags.rb @@ -0,0 +1,11 @@ +class CreateTags < ActiveRecord::Migration + def change + create_table :tags do |t| + t.string :name + + t.timestamps null: false + end + + add_index :tags, :name, unique: true + end +end diff --git a/db/migrate/20150702151009_create_user_tags.rb b/db/migrate/20150702151009_create_user_tags.rb new file mode 100644 index 000000000..374c42b48 --- /dev/null +++ b/db/migrate/20150702151009_create_user_tags.rb @@ -0,0 +1,10 @@ +class CreateUserTags < ActiveRecord::Migration + def change + create_table :user_tags do |t| + t.belongs_to :user, index: true, foreign_key: true + t.belongs_to :tag, index: true, foreign_key: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150706102547_create_availability_tags.rb b/db/migrate/20150706102547_create_availability_tags.rb new file mode 100644 index 000000000..4efb165a9 --- /dev/null +++ b/db/migrate/20150706102547_create_availability_tags.rb @@ -0,0 +1,10 @@ +class CreateAvailabilityTags < ActiveRecord::Migration + def change + create_table :availability_tags do |t| + t.belongs_to :availability, index: true, foreign_key: true + t.belongs_to :tag, index: true, foreign_key: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150707135343_add_offered_to_slots.rb b/db/migrate/20150707135343_add_offered_to_slots.rb new file mode 100644 index 000000000..32d1a0013 --- /dev/null +++ b/db/migrate/20150707135343_add_offered_to_slots.rb @@ -0,0 +1,5 @@ +class AddOfferedToSlots < ActiveRecord::Migration + def change + add_column :slots, :offered, :boolean, :default => false + end +end diff --git a/db/migrate/20150713090542_change_amount_type_in_invoice_items.rb b/db/migrate/20150713090542_change_amount_type_in_invoice_items.rb new file mode 100644 index 000000000..b24f69c63 --- /dev/null +++ b/db/migrate/20150713090542_change_amount_type_in_invoice_items.rb @@ -0,0 +1,9 @@ +class ChangeAmountTypeInInvoiceItems < ActiveRecord::Migration + def up + change_column :invoice_items, :amount, 'integer USING CAST(amount AS integer)' + end + + def down + change_column :invoice_items, :amount, :string + end +end diff --git a/db/migrate/20150713151115_add_invoice_item_id_to_invoice_item.rb b/db/migrate/20150713151115_add_invoice_item_id_to_invoice_item.rb new file mode 100644 index 000000000..76e0427d3 --- /dev/null +++ b/db/migrate/20150713151115_add_invoice_item_id_to_invoice_item.rb @@ -0,0 +1,5 @@ +class AddInvoiceItemIdToInvoiceItem < ActiveRecord::Migration + def change + add_column :invoice_items, :invoice_item_id, :integer + end +end diff --git a/db/migrate/20150715135751_add_description_to_invoices.rb b/db/migrate/20150715135751_add_description_to_invoices.rb new file mode 100644 index 000000000..f459407d0 --- /dev/null +++ b/db/migrate/20150715135751_add_description_to_invoices.rb @@ -0,0 +1,5 @@ +class AddDescriptionToInvoices < ActiveRecord::Migration + def change + add_column :invoices, :description, :text + end +end diff --git a/db/migrate/20150915144448_create_auth_providers.rb b/db/migrate/20150915144448_create_auth_providers.rb new file mode 100644 index 000000000..234ca1fa9 --- /dev/null +++ b/db/migrate/20150915144448_create_auth_providers.rb @@ -0,0 +1,11 @@ +class CreateAuthProviders < ActiveRecord::Migration + def change + create_table :auth_providers do |t| + t.string :name + t.string :type + t.string :status + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150915144939_create_o_auth2_providers.rb b/db/migrate/20150915144939_create_o_auth2_providers.rb new file mode 100644 index 000000000..9e1588c9b --- /dev/null +++ b/db/migrate/20150915144939_create_o_auth2_providers.rb @@ -0,0 +1,14 @@ +class CreateOAuth2Providers < ActiveRecord::Migration + def change + create_table :o_auth2_providers do |t| + t.string :base_url + t.string :token_endpoint + t.string :authorization_endpoint + t.string :client_id + t.string :client_secret + t.belongs_to :auth_provider, index: true, foreign_key: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150915152943_create_o_auth2_mappings.rb b/db/migrate/20150915152943_create_o_auth2_mappings.rb new file mode 100644 index 000000000..f323c718c --- /dev/null +++ b/db/migrate/20150915152943_create_o_auth2_mappings.rb @@ -0,0 +1,13 @@ +class CreateOAuth2Mappings < ActiveRecord::Migration + def change + create_table :o_auth2_mappings do |t| + t.belongs_to :o_auth2_provider, index: true, foreign_key: true + t.string :resource_url + t.string :local_field + t.string :api_field + t.string :data_type + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150916091131_change_auth_provider_to_polymorphic.rb b/db/migrate/20150916091131_change_auth_provider_to_polymorphic.rb new file mode 100644 index 000000000..ace750794 --- /dev/null +++ b/db/migrate/20150916091131_change_auth_provider_to_polymorphic.rb @@ -0,0 +1,9 @@ +class ChangeAuthProviderToPolymorphic < ActiveRecord::Migration + def change + remove_column :o_auth2_providers, :auth_provider_id, :integer + remove_column :auth_providers, :type, :string + + add_column :auth_providers, :providable_type, :string + add_column :auth_providers, :providable_id, :integer + end +end diff --git a/db/migrate/20150916093159_create_database_providers.rb b/db/migrate/20150916093159_create_database_providers.rb new file mode 100644 index 000000000..781e3b7ad --- /dev/null +++ b/db/migrate/20150916093159_create_database_providers.rb @@ -0,0 +1,8 @@ +class CreateDatabaseProviders < ActiveRecord::Migration + def change + create_table :database_providers do |t| + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150921135557_add_sso_id_to_user.rb b/db/migrate/20150921135557_add_sso_id_to_user.rb new file mode 100644 index 000000000..12df03f25 --- /dev/null +++ b/db/migrate/20150921135557_add_sso_id_to_user.rb @@ -0,0 +1,5 @@ +class AddSsoIdToUser < ActiveRecord::Migration + def change + add_column :users, :sso_id, :integer + end +end diff --git a/db/migrate/20150921135817_add_local_model_to_o_auth2_mapping.rb b/db/migrate/20150921135817_add_local_model_to_o_auth2_mapping.rb new file mode 100644 index 000000000..d0001c1d6 --- /dev/null +++ b/db/migrate/20150921135817_add_local_model_to_o_auth2_mapping.rb @@ -0,0 +1,5 @@ +class AddLocalModelToOAuth2Mapping < ActiveRecord::Migration + def change + add_column :o_auth2_mappings, :local_model, :string + end +end diff --git a/db/migrate/20150922095921_migrate_endpoints_to_urls_from_o_auth2_providers.rb b/db/migrate/20150922095921_migrate_endpoints_to_urls_from_o_auth2_providers.rb new file mode 100644 index 000000000..83dba5625 --- /dev/null +++ b/db/migrate/20150922095921_migrate_endpoints_to_urls_from_o_auth2_providers.rb @@ -0,0 +1,8 @@ +class MigrateEndpointsToUrlsFromOAuth2Providers < ActiveRecord::Migration + def change + rename_column :o_auth2_providers, :base_url, :api_url + rename_column :o_auth2_providers, :token_endpoint, :token_url + rename_column :o_auth2_providers, :authorization_endpoint, :authorization_url + add_column :o_auth2_providers, :api_data_type, :string + end +end diff --git a/db/migrate/20150922100528_remove_resource_url_from_o_auth2_mappings.rb b/db/migrate/20150922100528_remove_resource_url_from_o_auth2_mappings.rb new file mode 100644 index 000000000..f4d1c1c3f --- /dev/null +++ b/db/migrate/20150922100528_remove_resource_url_from_o_auth2_mappings.rb @@ -0,0 +1,6 @@ +class RemoveResourceUrlFromOAuth2Mappings < ActiveRecord::Migration + def change + remove_column :o_auth2_mappings, :resource_url, :string + remove_column :o_auth2_mappings, :data_type, :string + end +end diff --git a/db/migrate/20150924093917_migrate_url_to_endpoints_from_o_auth2_providers.rb b/db/migrate/20150924093917_migrate_url_to_endpoints_from_o_auth2_providers.rb new file mode 100644 index 000000000..6abbaf374 --- /dev/null +++ b/db/migrate/20150924093917_migrate_url_to_endpoints_from_o_auth2_providers.rb @@ -0,0 +1,9 @@ +class MigrateUrlToEndpointsFromOAuth2Providers < ActiveRecord::Migration + def change + rename_column :o_auth2_providers, :api_url, :base_url + rename_column :o_auth2_providers, :token_url, :token_endpoint + rename_column :o_auth2_providers, :authorization_url, :authorization_endpoint + + remove_column :o_auth2_providers, :api_data_type, :string + end +end diff --git a/db/migrate/20150924094138_add_resource_endpoint_to_o_auth2_mappings.rb b/db/migrate/20150924094138_add_resource_endpoint_to_o_auth2_mappings.rb new file mode 100644 index 000000000..c804aaf0a --- /dev/null +++ b/db/migrate/20150924094138_add_resource_endpoint_to_o_auth2_mappings.rb @@ -0,0 +1,6 @@ +class AddResourceEndpointToOAuth2Mappings < ActiveRecord::Migration + def change + add_column :o_auth2_mappings, :resource_endpoint, :string + add_column :o_auth2_mappings, :api_data_type, :string + end +end diff --git a/db/migrate/20150924094427_rename_resource_endpoint_from_o_auth2_mappings.rb b/db/migrate/20150924094427_rename_resource_endpoint_from_o_auth2_mappings.rb new file mode 100644 index 000000000..4b293cea6 --- /dev/null +++ b/db/migrate/20150924094427_rename_resource_endpoint_from_o_auth2_mappings.rb @@ -0,0 +1,5 @@ +class RenameResourceEndpointFromOAuth2Mappings < ActiveRecord::Migration + def change + rename_column :o_auth2_mappings, :resource_endpoint, :api_endpoint + end +end diff --git a/db/migrate/20150924141714_add_omniauth_to_users.rb b/db/migrate/20150924141714_add_omniauth_to_users.rb new file mode 100644 index 000000000..0952435e2 --- /dev/null +++ b/db/migrate/20150924141714_add_omniauth_to_users.rb @@ -0,0 +1,9 @@ +class AddOmniauthToUsers < ActiveRecord::Migration + def change + add_column :users, :provider, :string + add_index :users, :provider + add_column :users, :uid, :string + add_index :users, :uid + remove_column :users, :sso_id, :integer + end +end diff --git a/db/migrate/20151005133841_add_profile_url_to_o_auth2_providers.rb b/db/migrate/20151005133841_add_profile_url_to_o_auth2_providers.rb new file mode 100644 index 000000000..6a93636ae --- /dev/null +++ b/db/migrate/20151005133841_add_profile_url_to_o_auth2_providers.rb @@ -0,0 +1,5 @@ +class AddProfileUrlToOAuth2Providers < ActiveRecord::Migration + def change + add_column :o_auth2_providers, :profile_url, :string + end +end diff --git a/db/migrate/20151008152219_add_auth_token_to_users.rb b/db/migrate/20151008152219_add_auth_token_to_users.rb new file mode 100644 index 000000000..e88427a29 --- /dev/null +++ b/db/migrate/20151008152219_add_auth_token_to_users.rb @@ -0,0 +1,6 @@ +class AddAuthTokenToUsers < ActiveRecord::Migration + def change + add_column :users, :auth_token, :string + add_index :users, :auth_token + end +end diff --git a/db/migrate/20151105125623_create_abuses.rb b/db/migrate/20151105125623_create_abuses.rb new file mode 100644 index 000000000..888c2061f --- /dev/null +++ b/db/migrate/20151105125623_create_abuses.rb @@ -0,0 +1,13 @@ +class CreateAbuses < ActiveRecord::Migration + def change + create_table :abuses do |t| + t.references :signaled, polymorphic: true, index: true + t.string :first_name + t.string :last_name + t.string :email + t.text :message + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20151210113548_add_merged_at_to_users.rb b/db/migrate/20151210113548_add_merged_at_to_users.rb new file mode 100644 index 000000000..851eeb444 --- /dev/null +++ b/db/migrate/20151210113548_add_merged_at_to_users.rb @@ -0,0 +1,5 @@ +class AddMergedAtToUsers < ActiveRecord::Migration + def change + add_column :users, :merged_at, :datetime + end +end diff --git a/db/migrate/20160119131623_add_destroying_to_availability.rb b/db/migrate/20160119131623_add_destroying_to_availability.rb new file mode 100644 index 000000000..04451d998 --- /dev/null +++ b/db/migrate/20160119131623_add_destroying_to_availability.rb @@ -0,0 +1,7 @@ +class AddDestroyingToAvailability < ActiveRecord::Migration + def change + # this allow to prevent conflicts of 'delete cascade' by marking an availability + # as 'currently being destroyed' + add_column :availabilities, :destroying, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 74f13bbe3..162b94922 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,62 +11,118 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150429102754) do +ActiveRecord::Schema.define(version: 20160119131623) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "abuses", force: :cascade do |t| + t.integer "signaled_id" + t.string "signaled_type" + t.string "first_name" + t.string "last_name" + t.string "email" + t.text "message" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "abuses", ["signaled_type", "signaled_id"], name: "index_abuses_on_signaled_type_and_signaled_id", using: :btree + create_table "addresses", force: :cascade do |t| - t.string "address" - t.string "street_number" - t.string "route" - t.string "locality" - t.string "country" - t.string "postal_code" + t.string "address", limit: 255 + t.string "street_number", limit: 255 + t.string "route", limit: 255 + t.string "locality", limit: 255 + t.string "country", limit: 255 + t.string "postal_code", limit: 255 t.integer "placeable_id" - t.string "placeable_type" + t.string "placeable_type", limit: 255 t.datetime "created_at" t.datetime "updated_at" end create_table "assets", force: :cascade do |t| t.integer "viewable_id" - t.string "viewable_type" - t.string "attachment" - t.string "type" + t.string "viewable_type", limit: 255 + t.string "attachment", limit: 255 + t.string "type", limit: 255 t.datetime "created_at" t.datetime "updated_at" end + create_table "auth_providers", force: :cascade do |t| + t.string "name" + t.string "status" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "providable_type" + t.integer "providable_id" + end + create_table "availabilities", force: :cascade do |t| t.datetime "start_at" t.datetime "end_at" - t.string "available_type" - t.integer "nb_total_places" + t.string "available_type", limit: 255 t.datetime "created_at" t.datetime "updated_at" + t.integer "nb_total_places" + t.boolean "destroying", default: false end + create_table "availability_tags", force: :cascade do |t| + t.integer "availability_id" + t.integer "tag_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "availability_tags", ["availability_id"], name: "index_availability_tags_on_availability_id", using: :btree + add_index "availability_tags", ["tag_id"], name: "index_availability_tags_on_tag_id", using: :btree + create_table "categories", force: :cascade do |t| - t.string "name" + t.string "name", limit: 255 t.datetime "created_at" t.datetime "updated_at" end create_table "components", force: :cascade do |t| - t.string "name", null: false + t.string "name", limit: 255, null: false + end + + create_table "credits", force: :cascade do |t| + t.integer "creditable_id" + t.string "creditable_type", limit: 255 + t.integer "plan_id" + t.integer "hours" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "credits", ["plan_id"], name: "index_credits_on_plan_id", using: :btree + + create_table "custom_assets", force: :cascade do |t| + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "database_providers", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "events", force: :cascade do |t| - t.string "title" + t.string "title", limit: 255 t.text "description" + t.datetime "created_at" + t.datetime "updated_at" t.integer "availability_id" t.integer "amount" t.integer "reduced_amount" t.integer "nb_total_places" + t.integer "nb_free_places" t.integer "recurrence_id" - t.datetime "created_at" - t.datetime "updated_at" end add_index "events", ["availability_id"], name: "index_events_on_availability_id", using: :btree @@ -83,10 +139,10 @@ ActiveRecord::Schema.define(version: 20150429102754) do add_index "events_categories", ["event_id"], name: "index_events_categories_on_event_id", using: :btree create_table "friendly_id_slugs", force: :cascade do |t| - t.string "slug", null: false - t.integer "sluggable_id", null: false + t.string "slug", limit: 255, null: false + t.integer "sluggable_id", null: false t.string "sluggable_type", limit: 50 - t.string "scope" + t.string "scope", limit: 255 t.datetime "created_at" end @@ -96,49 +152,161 @@ ActiveRecord::Schema.define(version: 20150429102754) do add_index "friendly_id_slugs", ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree create_table "groups", force: :cascade do |t| - t.string "name" + t.string "name", limit: 255 t.datetime "created_at" t.datetime "updated_at" + t.string "slug", limit: 255 end + add_index "groups", ["slug"], name: "index_groups_on_slug", unique: true, using: :btree + + create_table "invoice_items", force: :cascade do |t| + t.integer "invoice_id" + t.string "stp_invoice_item_id", limit: 255 + t.integer "amount" + t.datetime "created_at" + t.datetime "updated_at" + t.text "description" + t.integer "subscription_id" + t.integer "invoice_item_id" + end + + add_index "invoice_items", ["invoice_id"], name: "index_invoice_items_on_invoice_id", using: :btree + + create_table "invoices", force: :cascade do |t| + t.integer "invoiced_id" + t.string "invoiced_type", limit: 255 + t.string "stp_invoice_id", limit: 255 + t.integer "total" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "user_id" + t.string "reference", limit: 255 + t.string "avoir_mode", limit: 255 + t.datetime "avoir_date" + t.integer "invoice_id" + t.string "type", limit: 255 + t.boolean "subscription_to_expire" + t.text "description" + end + + add_index "invoices", ["invoice_id"], name: "index_invoices_on_invoice_id", using: :btree + add_index "invoices", ["user_id"], name: "index_invoices_on_user_id", using: :btree + create_table "licences", force: :cascade do |t| - t.string "name", null: false + t.string "name", limit: 255, null: false t.text "description" end create_table "machines", force: :cascade do |t| - t.string "name" + t.string "name", limit: 255, null: false t.text "description" t.text "spec" - t.string "slug" t.datetime "created_at" t.datetime "updated_at" + t.string "slug", limit: 255 end add_index "machines", ["slug"], name: "index_machines_on_slug", unique: true, using: :btree + create_table "machines_availabilities", force: :cascade do |t| + t.integer "machine_id" + t.integer "availability_id" + end + + add_index "machines_availabilities", ["availability_id"], name: "index_machines_availabilities_on_availability_id", using: :btree + add_index "machines_availabilities", ["machine_id"], name: "index_machines_availabilities_on_machine_id", using: :btree + create_table "notifications", force: :cascade do |t| t.integer "receiver_id" t.integer "attached_object_id" - t.string "attached_object_type" + t.string "attached_object_type", limit: 255 t.integer "notification_type_id" - t.boolean "is_read", default: false - t.string "receiver_type" - t.boolean "is_send", default: false + t.boolean "is_read", default: false t.datetime "created_at" t.datetime "updated_at" + t.string "receiver_type", limit: 255 + t.boolean "is_send", default: false + t.jsonb "meta_data", default: {} end add_index "notifications", ["notification_type_id"], name: "index_notifications_on_notification_type_id", using: :btree add_index "notifications", ["receiver_id"], name: "index_notifications_on_receiver_id", using: :btree + create_table "o_auth2_mappings", force: :cascade do |t| + t.integer "o_auth2_provider_id" + t.string "local_field" + t.string "api_field" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "local_model" + t.string "api_endpoint" + t.string "api_data_type" + end + + add_index "o_auth2_mappings", ["o_auth2_provider_id"], name: "index_o_auth2_mappings_on_o_auth2_provider_id", using: :btree + + create_table "o_auth2_providers", force: :cascade do |t| + t.string "base_url" + t.string "token_endpoint" + t.string "authorization_endpoint" + t.string "client_id" + t.string "client_secret" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "profile_url" + end + + create_table "offer_days", force: :cascade do |t| + t.integer "subscription_id" + t.datetime "start_at" + t.datetime "end_at" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "offer_days", ["subscription_id"], name: "index_offer_days_on_subscription_id", using: :btree + + create_table "plans", force: :cascade do |t| + t.string "name", limit: 255 + t.integer "amount" + t.string "interval", limit: 255 + t.integer "group_id" + t.string "stp_plan_id", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.integer "training_credit_nb", default: 0 + t.boolean "is_rolling", default: true + t.text "description" + t.string "type" + t.string "base_name" + t.integer "ui_weight", default: 0 + t.integer "interval_count", default: 1 + end + + add_index "plans", ["group_id"], name: "index_plans_on_group_id", using: :btree + + create_table "prices", force: :cascade do |t| + t.integer "group_id" + t.integer "plan_id" + t.integer "priceable_id" + t.string "priceable_type" + t.integer "amount" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "prices", ["group_id"], name: "index_prices_on_group_id", using: :btree + add_index "prices", ["plan_id"], name: "index_prices_on_plan_id", using: :btree + add_index "prices", ["priceable_type", "priceable_id"], name: "index_prices_on_priceable_type_and_priceable_id", using: :btree + create_table "profiles", force: :cascade do |t| t.integer "user_id" - t.string "first_name" - t.string "last_name" + t.string "first_name", limit: 255 + t.string "last_name", limit: 255 t.boolean "gender" t.date "birthday" - t.string "phone" + t.string "phone", limit: 255 t.text "interest" t.text "software_mastered" t.datetime "created_at" @@ -149,10 +317,10 @@ ActiveRecord::Schema.define(version: 20150429102754) do create_table "project_steps", force: :cascade do |t| t.text "description" - t.string "title" t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" + t.string "title", limit: 255 end add_index "project_steps", ["project_id"], name: "index_project_steps_on_project_id", using: :btree @@ -160,26 +328,26 @@ ActiveRecord::Schema.define(version: 20150429102754) do create_table "project_users", force: :cascade do |t| t.integer "project_id" t.integer "user_id" - t.boolean "is_valid", default: false - t.string "valid_token" t.datetime "created_at" t.datetime "updated_at" + t.boolean "is_valid", default: false + t.string "valid_token", limit: 255 end add_index "project_users", ["project_id"], name: "index_project_users_on_project_id", using: :btree add_index "project_users", ["user_id"], name: "index_project_users_on_user_id", using: :btree create_table "projects", force: :cascade do |t| - t.string "name" + t.string "name", limit: 255 t.text "description" - t.string "slug" - t.datetime "published_at" - t.integer "author_id" - t.text "tags" - t.string "state" t.datetime "created_at" t.datetime "updated_at" + t.integer "author_id" + t.text "tags" t.integer "licence_id" + t.string "state", limit: 255 + t.string "slug", limit: 255 + t.datetime "published_at" end add_index "projects", ["slug"], name: "index_projects_on_slug", unique: true, using: :btree @@ -208,10 +376,26 @@ ActiveRecord::Schema.define(version: 20150429102754) do add_index "projects_themes", ["project_id"], name: "index_projects_themes_on_project_id", using: :btree add_index "projects_themes", ["theme_id"], name: "index_projects_themes_on_theme_id", using: :btree + create_table "reservations", force: :cascade do |t| + t.integer "user_id" + t.text "message" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "reservable_id" + t.string "reservable_type", limit: 255 + t.string "stp_invoice_id", limit: 255 + t.integer "nb_reserve_places" + t.integer "nb_reserve_reduced_places" + end + + add_index "reservations", ["reservable_id", "reservable_type"], name: "index_reservations_on_reservable_id_and_reservable_type", using: :btree + add_index "reservations", ["stp_invoice_id"], name: "index_reservations_on_stp_invoice_id", using: :btree + add_index "reservations", ["user_id"], name: "index_reservations_on_user_id", using: :btree + create_table "roles", force: :cascade do |t| - t.string "name" + t.string "name", limit: 255 t.integer "resource_id" - t.string "resource_type" + t.string "resource_type", limit: 255 t.datetime "created_at" t.datetime "updated_at" end @@ -219,44 +403,236 @@ ActiveRecord::Schema.define(version: 20150429102754) do add_index "roles", ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id", using: :btree add_index "roles", ["name"], name: "index_roles_on_name", using: :btree - create_table "themes", force: :cascade do |t| - t.string "name", null: false + create_table "settings", force: :cascade do |t| + t.string "name", null: false + t.text "value" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at" - t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false - t.datetime "current_sign_in_at" - t.datetime "last_sign_in_at" - t.inet "current_sign_in_ip" - t.inet "last_sign_in_ip" - t.string "confirmation_token" - t.datetime "confirmed_at" - t.datetime "confirmation_sent_at" - t.string "unconfirmed_email" - t.integer "failed_attempts", default: 0, null: false - t.string "unlock_token" - t.datetime "locked_at" - t.boolean "is_allow_contact", default: true - t.string "username" - t.string "slug" + add_index "settings", ["name"], name: "index_settings_on_name", unique: true, using: :btree + + create_table "slots", force: :cascade do |t| + t.datetime "start_at" + t.datetime "end_at" + t.integer "reservation_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "group_id" + t.integer "availability_id" + t.datetime "ex_start_at" + t.datetime "canceled_at" + t.datetime "ex_end_at" + t.boolean "offered", default: false end + add_index "slots", ["availability_id"], name: "index_slots_on_availability_id", using: :btree + add_index "slots", ["reservation_id"], name: "index_slots_on_reservation_id", using: :btree + + create_table "statistic_fields", force: :cascade do |t| + t.integer "statistic_index_id" + t.string "key", limit: 255 + t.string "label", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.string "data_type", limit: 255 + end + + add_index "statistic_fields", ["statistic_index_id"], name: "index_statistic_fields_on_statistic_index_id", using: :btree + + create_table "statistic_graphs", force: :cascade do |t| + t.integer "statistic_index_id" + t.string "chart_type", limit: 255 + t.integer "limit" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "statistic_graphs", ["statistic_index_id"], name: "index_statistic_graphs_on_statistic_index_id", using: :btree + + create_table "statistic_indices", force: :cascade do |t| + t.string "es_type_key", limit: 255 + t.string "label", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "table", default: true + t.boolean "ca", default: true + end + + create_table "statistic_sub_types", force: :cascade do |t| + t.string "key", limit: 255 + t.string "label", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "statistic_type_sub_types", force: :cascade do |t| + t.integer "statistic_type_id" + t.integer "statistic_sub_type_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "statistic_type_sub_types", ["statistic_sub_type_id"], name: "index_statistic_type_sub_types_on_statistic_sub_type_id", using: :btree + add_index "statistic_type_sub_types", ["statistic_type_id"], name: "index_statistic_type_sub_types_on_statistic_type_id", using: :btree + + create_table "statistic_types", force: :cascade do |t| + t.integer "statistic_index_id" + t.string "key", limit: 255 + t.string "label", limit: 255 + t.boolean "graph" + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "simple" + end + + add_index "statistic_types", ["statistic_index_id"], name: "index_statistic_types_on_statistic_index_id", using: :btree + + create_table "stylesheets", force: :cascade do |t| + t.text "contents" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "subscriptions", force: :cascade do |t| + t.integer "plan_id" + t.integer "user_id" + t.string "stp_subscription_id", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.datetime "expired_at" + t.datetime "canceled_at" + end + + add_index "subscriptions", ["plan_id"], name: "index_subscriptions_on_plan_id", using: :btree + add_index "subscriptions", ["user_id"], name: "index_subscriptions_on_user_id", using: :btree + + create_table "tags", force: :cascade do |t| + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree + + create_table "themes", force: :cascade do |t| + t.string "name", limit: 255, null: false + end + + create_table "trainings", force: :cascade do |t| + t.string "name", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.integer "nb_total_places" + t.string "slug", limit: 255 + t.text "description" + end + + add_index "trainings", ["slug"], name: "index_trainings_on_slug", unique: true, using: :btree + + create_table "trainings_availabilities", force: :cascade do |t| + t.integer "training_id" + t.integer "availability_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "trainings_availabilities", ["availability_id"], name: "index_trainings_availabilities_on_availability_id", using: :btree + add_index "trainings_availabilities", ["training_id"], name: "index_trainings_availabilities_on_training_id", using: :btree + + create_table "trainings_machines", force: :cascade do |t| + t.integer "training_id" + t.integer "machine_id" + end + + add_index "trainings_machines", ["machine_id"], name: "index_trainings_machines_on_machine_id", using: :btree + add_index "trainings_machines", ["training_id"], name: "index_trainings_machines_on_training_id", using: :btree + + create_table "trainings_pricings", force: :cascade do |t| + t.integer "group_id" + t.integer "amount" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "training_id" + end + + add_index "trainings_pricings", ["group_id"], name: "index_trainings_pricings_on_group_id", using: :btree + add_index "trainings_pricings", ["training_id"], name: "index_trainings_pricings_on_training_id", using: :btree + + create_table "user_tags", force: :cascade do |t| + t.integer "user_id" + t.integer "tag_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "user_tags", ["tag_id"], name: "index_user_tags_on_tag_id", using: :btree + add_index "user_tags", ["user_id"], name: "index_user_tags_on_user_id", using: :btree + + create_table "user_trainings", force: :cascade do |t| + t.integer "user_id" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "training_id" + end + + add_index "user_trainings", ["training_id"], name: "index_user_trainings_on_training_id", using: :btree + add_index "user_trainings", ["user_id"], name: "index_user_trainings_on_user_id", using: :btree + + create_table "users", force: :cascade do |t| + t.string "username", limit: 255 + t.string "email", limit: 255, default: "", null: false + t.string "encrypted_password", limit: 255, default: "", null: false + t.string "reset_password_token", limit: 255 + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.integer "sign_in_count", default: 0, null: false + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.string "current_sign_in_ip", limit: 255 + t.string "last_sign_in_ip", limit: 255 + t.string "confirmation_token", limit: 255 + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" + t.string "unconfirmed_email", limit: 255 + t.integer "failed_attempts", default: 0, null: false + t.string "unlock_token", limit: 255 + t.datetime "locked_at" + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "is_allow_contact", default: true + t.integer "group_id" + t.string "stp_customer_id", limit: 255 + t.string "slug", limit: 255 + t.boolean "is_active", default: true + t.boolean "invoicing_disabled", default: false + t.string "provider" + t.string "uid" + t.string "auth_token" + t.datetime "merged_at" + end + + add_index "users", ["auth_token"], name: "index_users_on_auth_token", using: :btree add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["group_id"], name: "index_users_on_group_id", using: :btree + add_index "users", ["provider"], name: "index_users_on_provider", using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["slug"], name: "index_users_on_slug", unique: true, using: :btree + add_index "users", ["uid"], name: "index_users_on_uid", using: :btree add_index "users", ["unlock_token"], name: "index_users_on_unlock_token", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", unique: true, using: :btree + create_table "users_credits", force: :cascade do |t| + t.integer "user_id" + t.integer "credit_id" + t.integer "hours_used" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "users_credits", ["credit_id"], name: "index_users_credits_on_credit_id", using: :btree + add_index "users_credits", ["user_id"], name: "index_users_credits_on_user_id", using: :btree + create_table "users_roles", id: false, force: :cascade do |t| t.integer "user_id" t.integer "role_id" @@ -264,16 +640,11 @@ ActiveRecord::Schema.define(version: 20150429102754) do add_index "users_roles", ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id", using: :btree - add_foreign_key "events_categories", "categories" - add_foreign_key "events_categories", "events" - add_foreign_key "profiles", "users" - add_foreign_key "project_steps", "projects" - add_foreign_key "project_users", "projects" - add_foreign_key "project_users", "users" - add_foreign_key "projects_components", "components" - add_foreign_key "projects_components", "projects" - add_foreign_key "projects_machines", "machines" - add_foreign_key "projects_machines", "projects" - add_foreign_key "projects_themes", "projects" - add_foreign_key "projects_themes", "themes" + add_foreign_key "availability_tags", "availabilities" + add_foreign_key "availability_tags", "tags" + add_foreign_key "o_auth2_mappings", "o_auth2_providers" + add_foreign_key "prices", "groups" + add_foreign_key "prices", "plans" + add_foreign_key "user_tags", "tags" + add_foreign_key "user_tags", "users" end diff --git a/db/seeds.rb b/db/seeds.rb index 0dc75ab81..51c5bc81e 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,76 +1,397 @@ -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). -# -# Encoding: UTF-8 +#encoding: utf-8 + +if StatisticIndex.count == 0 + StatisticIndex.create!([ + {id:1, es_type_key:'subscription', label:'Abonnements'}, + {id:2, es_type_key:'machine', label:'Heures machines'}, + {id:3, es_type_key:'training', label:'Formations'}, + {id:4, es_type_key:'event', label:'Ateliers/Stages'}, + {id:5, es_type_key:'account', label:'Inscriptions', ca: false}, + {id:6, es_type_key:'project', label:'Projets', ca: false}, + {id:7, es_type_key:'user', label:'Utilisateurs', table: false, ca: false} + ]) + connection = ActiveRecord::Base.connection + if connection.instance_values["config"][:adapter] == 'postgresql' + connection.execute("SELECT setval('statistic_indices_id_seq', 7);") + end +end + +if StatisticField.count == 0 + StatisticField.create!([ + # available data_types : index, number, date, text, list + {key:'trainingId', label:'ID Formation', statistic_index_id: 3, data_type: 'index'}, + {key:'trainingDate', label:'Date Formation', statistic_index_id: 3, data_type: 'date'}, + {key:'eventId', label:'ID Évènement', statistic_index_id: 4, data_type: 'index'}, + {key:'eventDate', label:'Date Évènement', statistic_index_id: 4, data_type: 'date'}, + {key:'themes', label:'Thèmes', statistic_index_id: 6, data_type: 'list'}, + {key:'components', label:'Composants', statistic_index_id: 6, data_type: 'list'}, + {key:'machines', label:'Machines', statistic_index_id: 6, data_type: 'list'}, + {key:'name', label:'Nom Évènement', statistic_index_id: 4, data_type: 'text'}, + {key:'userId', label:'ID Utilisateur', statistic_index_id: 7, data_type: 'index'} + ]) +end + +unless StatisticField.find_by(key:'groupName').try(:label) + field = StatisticField.find_or_initialize_by(key: 'groupName') + field.label = 'Groupe' + field.statistic_index_id = 1 + field.data_type = 'text' + field.save! +end + +if StatisticType.count == 0 + StatisticType.create!([ + {statistic_index_id: 2, key: 'booking', label:'Réservations', graph: true, simple: true}, + {statistic_index_id: 2, key: 'hour', label:"Nombre d'heures", graph: true, simple: false}, + {statistic_index_id: 3, key: 'booking', label:'Réservations', graph: false, simple: true}, + {statistic_index_id: 3, key: 'hour', label:"Nombre d'heures", graph: false, simple: false}, + {statistic_index_id: 4, key: 'booking', label:'Nombre de places', graph: false, simple: false}, + {statistic_index_id: 4, key: 'hour', label:"Nombre d'heures", graph: false, simple: false}, + {statistic_index_id: 5, key: 'member', label:'Utilisateurs', graph: true, simple: true}, + {statistic_index_id: 6, key: 'project', label:'Projets', graph: false, simple: true}, + {statistic_index_id: 7, key: 'revenue', label:"Chiffre d'affaires", graph: false, simple: false} + ]) +end + +if StatisticSubType.count == 0 + StatisticSubType.create!([ + {key: 'Stage', label:'Stage', statistic_types: StatisticIndex.find_by(es_type_key: 'event').statistic_types}, + {key: 'Atelier', label:'Atelier', statistic_types: StatisticIndex.find_by(es_type_key: 'event').statistic_types}, + + {key: 'created', label:'Création de compte', statistic_types: StatisticIndex.find_by(es_type_key: 'account').statistic_types}, + {key: 'published', label:'Publication de projet', statistic_types: StatisticIndex.find_by(es_type_key: 'project').statistic_types} + ]) +end + +if StatisticGraph.count == 0 + StatisticGraph.create!([ + {statistic_index_id:1, chart_type:'stackedAreaChart', limit:0}, + {statistic_index_id:2, chart_type:'stackedAreaChart', limit:0}, + {statistic_index_id:3, chart_type:'discreteBarChart', limit:10}, + {statistic_index_id:4, chart_type:'discreteBarChart', limit:10}, + {statistic_index_id:5, chart_type:'lineChart', limit:0}, + {statistic_index_id:7, chart_type:'discreteBarChart', limit:10} + ]) +end if Group.count == 0 Group.create!([ - {name: "standard, association"}, - {name: "étudiant, - de 25 ans, enseignant, demandeur d'emploi"}, - {name: "artisan, commerçant, chercheur, auto-entrepreneur"}, - {name: "PME, PMI, SARL, SA"} - ]) + {name: "standard, association", slug: "standard"}, + {name: "étudiant, - de 25 ans, enseignant, demandeur d'emploi", slug: "student"}, + {name: "artisan, commerçant, chercheur, auto-entrepreneur", slug: "merchant"}, + {name: "PME, PMI, SARL, SA", slug: "business"} + ]) end -if User.find_by(email: "admin@fabmanager.com").nil? - admin = User.new(username: 'admin', email: 'admin@fabmanager.com', password: 'adminadmin', password_confirmation: 'adminadmin', group_id: Group.first.id, - profile_attributes: {first_name: 'Admin', last_name: 'Admin', gender: true, phone: '0000000000', birthday: Time.now}) - #admin.skip_confirmation! - admin.add_role "admin" - admin.save +# Create the admin if it does not exist yet +if User.find_by(email: "admin@fab-manager.com").nil? + admin = User.new(username: 'admin', email: 'admin@fab-manager.com', password: 'adminadmin', password_confirmation: 'adminadmin', group_id: Group.first.id, + profile_attributes: {first_name: 'admin', last_name: 'admin', gender: true, phone: '0123456789', birthday: Time.now}) + admin.add_role "admin" + admin.save end if Component.count == 0 Component.create!([ - {name: "Silicone"}, - {name: "Vinyle"}, - {name: "Bois Contre plaqué"}, - {name: "Bois Medium"}, - {name: "Plexi / PMMA"}, - {name: "Flex"}, - {name: "Vinyle"}, - {name: "Parafine"}, - {name: "Fibre de verre"}, - {name: "Résine"} - ]) + {name: "Silicone"}, + {name: "Vinyle"}, + {name: "Bois Contre plaqué"}, + {name: "Bois Medium"}, + {name: "Plexi / PMMA"}, + {name: "Flex"}, + {name: "Vinyle"}, + {name: "Parafine"}, + {name: "Fibre de verre"}, + {name: "Résine"} + ]) end if Licence.count == 0 Licence.create!([ - {name: "Attribution (BY)", description: "Le titulaire des droits autorise toute exploitation de l’œuvre, y compris à des fins commerciales, ainsi que la création d’œuvres dérivées, dont la distribution est également autorisé sans restriction, à condition de l’attribuer à son l’auteur en citant son nom. Cette licence est recommandée pour la diffusion et l’utilisation maximale des œuvres."}, - {name: "Attribution + Pas de modification (BY ND)", description: "Le titulaire des droits autorise toute utilisation de l’œuvre originale (y compris à des fins commerciales), mais n’autorise pas la création d’œuvres dérivées."}, - {name: "Attribution + Pas d'Utilisation Commerciale + Pas de Modification (BY NC ND)", description: "Le titulaire des droits autorise l’utilisation de l’œuvre originale à des fins non commerciales, mais n’autorise pas la création d’œuvres dérivés."}, - {name: "Attribution + Pas d'Utilisation Commerciale (BY NC)", description: "Le titulaire des droits autorise l’exploitation de l’œuvre, ainsi que la création d’œuvres dérivées, à condition qu’il ne s’agisse pas d’une utilisation commerciale (les utilisations commerciales restant soumises à son autorisation)."}, - {name: "Attribution + Pas d'Utilisation Commerciale + Partage dans les mêmes conditions (BY NC SA)", description: "Le titulaire des droits autorise l’exploitation de l’œuvre originale à des fins non commerciales, ainsi que la création d’œuvres dérivées, à condition qu’elles soient distribuées sous une licence identique à celle qui régit l’œuvre originale."}, - {name: "Attribution + Partage dans les mêmes conditions (BY SA)", description: "Le titulaire des droits autorise toute utilisation de l’œuvre originale (y compris à des fins commerciales) ainsi que la création d’œuvres dérivées, à condition qu’elles soient distribuées sous une licence identique à celle qui régit l’œuvre originale. Cette licence est souvent comparée aux licences « copyleft » des logiciels libres. C’est la licence utilisée par Wikipedia."} - ]) + {name: "Attribution (BY)", description: "Le titulaire des droits autorise toute exploitation de l’œuvre, y compris à des fins commerciales, ainsi que la création d’œuvres dérivées, dont la distribution est également autorisé sans restriction, à condition de l’attribuer à son l’auteur en citant son nom. Cette licence est recommandée pour la diffusion et l’utilisation maximale des œuvres."}, + {name: "Attribution + Pas de modification (BY ND)", description: "Le titulaire des droits autorise toute utilisation de l’œuvre originale (y compris à des fins commerciales), mais n’autorise pas la création d’œuvres dérivées."}, + {name: "Attribution + Pas d'Utilisation Commerciale + Pas de Modification (BY NC ND)", description: "Le titulaire des droits autorise l’utilisation de l’œuvre originale à des fins non commerciales, mais n’autorise pas la création d’œuvres dérivés."}, + {name: "Attribution + Pas d'Utilisation Commerciale (BY NC)", description: "Le titulaire des droits autorise l’exploitation de l’œuvre, ainsi que la création d’œuvres dérivées, à condition qu’il ne s’agisse pas d’une utilisation commerciale (les utilisations commerciales restant soumises à son autorisation)."}, + {name: "Attribution + Pas d'Utilisation Commerciale + Partage dans les mêmes conditions (BY NC SA)", description: "Le titulaire des droits autorise l’exploitation de l’œuvre originale à des fins non commerciales, ainsi que la création d’œuvres dérivées, à condition qu’elles soient distribuées sous une licence identique à celle qui régit l’œuvre originale."}, + {name: "Attribution + Partage dans les mêmes conditions (BY SA)", description: "Le titulaire des droits autorise toute utilisation de l’œuvre originale (y compris à des fins commerciales) ainsi que la création d’œuvres dérivées, à condition qu’elles soient distribuées sous une licence identique à celle qui régit l’œuvre originale. Cette licence est souvent comparée aux licences « copyleft » des logiciels libres. C’est la licence utilisée par Wikipedia."} + ]) end if Theme.count == 0 Theme.create!([ - {name: "Vie quotidienne"}, - {name: "Robotique"}, - {name: "Arduine"}, - {name: "Capteurs"}, - {name: "Musique"}, - {name: "Sport"}, - {name: "Autre"} - ]) + {name: "Vie quotidienne"}, + {name: "Robotique"}, + {name: "Arduine"}, + {name: "Capteurs"}, + {name: "Musique"}, + {name: "Sport"}, + {name: "Autre"} + ]) +end + +if Training.count == 0 + Training.create!([ + {name: "Formation Imprimante 3D"}, + {name: "Formation Laser / Vinyle"}, + {name: "Formation Petite fraiseuse numerique"}, + {name: "Formation Shopbot Grande Fraiseuse"}, + {name: "Formation logiciel 2D"} + ]) + + TrainingsPricing.all.each do |p| + p.update_columns(amount: (rand()*50+5).floor*100) + end + + Training.create({name: "Pas de réservation"}) end if Machine.count == 0 Machine.create!([ - {name: "Découpeuse laser", description: "Préparation à l'utilisation de l'EPILOG Legend 36EXT\r\nInformations générales \r\n Pour la découpe, il suffit d'apporter votre fichier vectorisé type illustrator, svg ou dxf avec des \"lignes de coupe\" d'une épaisseur inférieur à 0,01 mm et la machine s'occupera du reste!\r\n La gravure est basée sur le spectre noir et blanc. Les nuances sont obtenues par différentes profondeurs de gravure correspondant aux niveaux de gris de votre image. Il suffit pour cela d'apporter une image scannée ou un fichier photo en noir et blanc pour pouvoir reproduire celle-ci sur votre support! \r\nQuels types de matériaux pouvons nous graver/découper?\r\n Du bois au tissu, du plexiglass au cuir, cette machine permet de découper et graver la plupart des matériaux sauf les métaux. La gravure est néanmoins possible sur les métaux recouverts d'une couche de peinture ou les aluminiums anodisés. \r\n Concernant l'épaisseur des matériaux découpés, il est préférable de ne pas dépasser 5 mm pour le bois et 6 mm pour le plexiglass.\r\n", spec: "Puissance: 40W\r\nSurface de travail: 914x609 mm \r\nEpaisseur maximale de la matière: 305mm\r\nSource laser: tube laser type CO2\r\nContrôles de vitesse et de puissance: ces deux paramètres sont ajustables en fonction du matériau (de 1% à 100%) .\r\n", slug: "decoupeuse-laser"}, - {name: "Découpeuse vinyle", description: "Préparation à l'utilisation de la Roland CAMM-1 GX24\r\nInformations générales \r\n Envie de réaliser un tee shirt personnalisé ? Un sticker à l'effigie votre groupe préféré? Un masque pour la réalisation d'un circuit imprimé? Pour cela, il suffit simplement de venir avec votre fichier vectorisé (ne pas oublier de vectoriser les textes) type illustrator svg ou dxf.\r\n \r\nMatériaux utilisés:\r\n Cette machine permet de découper principalement du vinyle,vinyle réfléchissant, flex.\r\n", spec: "Largeurs de support acceptées: de 50 mm à 700 mm\r\nVitesse de découpe: 50 cm/sec\r\nRésolution mécanique: 0,0125 mm/pas\r\n", slug: "decoupeuse-vinyle"}, - {name: "Shopbot / Grande fraiseuse", description: "La fraiseuse numérique ShopBot PRS standard\r\nInformations générales\r\nCette machine est un fraiseuse 3 axes idéale pour l'usinage de pièces de grandes dimensions. De la réalisation d'une chaise ou d'un meuble jusqu'à la construction d'une maison ou d'un assemblage immense, le ShopBot ouvre de nombreuses portes à votre imagination! \r\nMatériaux usinables\r\nLes principaux matériaux usinables sont le bois, le plastique, le laiton et bien d'autres.\r\nCette machine n'usine pas les métaux.\r\n", spec: "Surface maximale de travail: 2440x1220x150 (Z) mm\r\nLogiciel utilisé: Partworks 2D & 3D\r\nRésolution mécanique: 0,015 mm\r\nPrécision de la position: +/- 0,127mm\r\nFormats acceptés: DXF, STL \r\n", slug: "shopbot-grande-fraiseuse"}, - {name: "Imprimante 3D", description: "L'utimaker est une imprimante 3D low cost utilisant une technologie FFF (Fused Filament Fabrication) avec extrusion thermoplastique.\r\nC'est une machine idéale pour réaliser rapidement des prototypes 3D dans des couleurs différentes.\r\n", spec: "Surface maximale de travail: 210x210x220mm \r\nRésolution méchanique: 0,02 mm \r\nPrécision de position: +/- 0,05 \r\nLogiciel utilisé: Cura\r\nFormats de fichier acceptés: STL \r\nMatériaux utilisés: PLA (en stock).", slug: "imprimante-3d"}, - {name: "Petite Fraiseuse", description: "La fraiseuse numérique Roland Modela MDX-20\r\nInformations générales\r\nCette machine est utilisée pour l'usinage et le scannage 3D de précision. Elle permet principalement d'usiner des circuits imprimés et des moules de petite taille. Le faible diamètre des fraises utilisées (Ø 0,3 mm à Ø 6mm) induit que certains temps d'usinages peuvent êtres long (> 12h), c'est pourquoi cette fraiseuse peut être laissée en autonomie toute une nuit afin d'obtenir le plus précis des usinages au FabLab.\r\nMatériaux usinables:\r\nLes principaux matériaux usinables sont le bois, plâtre, résine, cire usinable, cuivre.\r\n", spec: "Taille du plateau X/Y : 220 mm x 160 mm\r\nVolume maximal de travail: 203,2 mm (X), 152,4 mm (Y), 60,5 mm (Z)\r\nPrécision usinage: 0,00625 mm\r\nPrécision scannage: réglable de 0,05 à 5 mm (axes X,Y) et 0,025 mm (axe Z)\r\nVitesse d'analyse (scannage): 4-15 mm/sec\r\n \r\n \r\nLogiciel utilisé pour le fraisage: Roland Modela player 4 \r\nLogiciel utilisé pour l'usinage de circuits imprimés: Cad.py (linux)\r\nFormats acceptés: STL,PNG 3D\r\nFormat d'exportation des données scannées: DXF, VRML, STL, 3DMF, IGES, Grayscale, Point Group et BMP\r\n", slug: "petite-fraiseuse"} - ]) + {name: "Découpeuse laser", description: "Préparation à l'utilisation de l'EPILOG Legend 36EXT\r\nInformations générales \r\n Pour la découpe, il suffit d'apporter votre fichier vectorisé type illustrator, svg ou dxf avec des \"lignes de coupe\" d'une épaisseur inférieur à 0,01 mm et la machine s'occupera du reste!\r\n La gravure est basée sur le spectre noir et blanc. Les nuances sont obtenues par différentes profondeurs de gravure correspondant aux niveaux de gris de votre image. Il suffit pour cela d'apporter une image scannée ou un fichier photo en noir et blanc pour pouvoir reproduire celle-ci sur votre support! \r\nQuels types de matériaux pouvons nous graver/découper?\r\n Du bois au tissu, du plexiglass au cuir, cette machine permet de découper et graver la plupart des matériaux sauf les métaux. La gravure est néanmoins possible sur les métaux recouverts d'une couche de peinture ou les aluminiums anodisés. \r\n Concernant l'épaisseur des matériaux découpés, il est préférable de ne pas dépasser 5 mm pour le bois et 6 mm pour le plexiglass.\r\n", spec: "Puissance: 40W\r\nSurface de travail: 914x609 mm \r\nEpaisseur maximale de la matière: 305mm\r\nSource laser: tube laser type CO2\r\nContrôles de vitesse et de puissance: ces deux paramètres sont ajustables en fonction du matériau (de 1% à 100%) .\r\n", slug: "decoupeuse-laser"}, + {name: "Découpeuse vinyle", description: "Préparation à l'utilisation de la Roland CAMM-1 GX24\r\nInformations générales \r\n Envie de réaliser un tee shirt personnalisé ? Un sticker à l'effigie votre groupe préféré? Un masque pour la réalisation d'un circuit imprimé? Pour cela, il suffit simplement de venir avec votre fichier vectorisé (ne pas oublier de vectoriser les textes) type illustrator svg ou dxf.\r\n \r\nMatériaux utilisés:\r\n Cette machine permet de découper principalement du vinyle,vinyle réfléchissant, flex.\r\n", spec: "Largeurs de support acceptées: de 50 mm à 700 mm\r\nVitesse de découpe: 50 cm/sec\r\nRésolution mécanique: 0,0125 mm/pas\r\n", slug: "decoupeuse-vinyle"}, + {name: "Shopbot / Grande fraiseuse", description: "La fraiseuse numérique ShopBot PRS standard\r\nInformations générales\r\nCette machine est un fraiseuse 3 axes idéale pour l'usinage de pièces de grandes dimensions. De la réalisation d'une chaise ou d'un meuble jusqu'à la construction d'une maison ou d'un assemblage immense, le ShopBot ouvre de nombreuses portes à votre imagination! \r\nMatériaux usinables\r\nLes principaux matériaux usinables sont le bois, le plastique, le laiton et bien d'autres.\r\nCette machine n'usine pas les métaux.\r\n", spec: "Surface maximale de travail: 2440x1220x150 (Z) mm\r\nLogiciel utilisé: Partworks 2D & 3D\r\nRésolution mécanique: 0,015 mm\r\nPrécision de la position: +/- 0,127mm\r\nFormats acceptés: DXF, STL \r\n", slug: "shopbot-grande-fraiseuse"}, + {name: "Imprimante 3D", description: "L'utimaker est une imprimante 3D low cost utilisant une technologie FFF (Fused Filament Fabrication) avec extrusion thermoplastique.\r\nC'est une machine idéale pour réaliser rapidement des prototypes 3D dans des couleurs différentes.\r\n", spec: "Surface maximale de travail: 210x210x220mm \r\nRésolution méchanique: 0,02 mm \r\nPrécision de position: +/- 0,05 \r\nLogiciel utilisé: Cura\r\nFormats de fichier acceptés: STL \r\nMatériaux utilisés: PLA (en stock).", slug: "imprimante-3d"}, + {name: "Petite Fraiseuse", description: "La fraiseuse numérique Roland Modela MDX-20\r\nInformations générales\r\nCette machine est utilisée pour l'usinage et le scannage 3D de précision. Elle permet principalement d'usiner des circuits imprimés et des moules de petite taille. Le faible diamètre des fraises utilisées (Ø 0,3 mm à Ø 6mm) induit que certains temps d'usinages peuvent êtres long (> 12h), c'est pourquoi cette fraiseuse peut être laissée en autonomie toute une nuit afin d'obtenir le plus précis des usinages au FabLab.\r\nMatériaux usinables:\r\nLes principaux matériaux usinables sont le bois, plâtre, résine, cire usinable, cuivre.\r\n", spec: "Taille du plateau X/Y : 220 mm x 160 mm\r\nVolume maximal de travail: 203,2 mm (X), 152,4 mm (Y), 60,5 mm (Z)\r\nPrécision usinage: 0,00625 mm\r\nPrécision scannage: réglable de 0,05 à 5 mm (axes X,Y) et 0,025 mm (axe Z)\r\nVitesse d'analyse (scannage): 4-15 mm/sec\r\n \r\n \r\nLogiciel utilisé pour le fraisage: Roland Modela player 4 \r\nLogiciel utilisé pour l'usinage de circuits imprimés: Cad.py (linux)\r\nFormats acceptés: STL,PNG 3D\r\nFormat d'exportation des données scannées: DXF, VRML, STL, 3DMF, IGES, Grayscale, Point Group et BMP\r\n", slug: "petite-fraiseuse"}, + {name: "FORM1+ imprimante 3D", description: "Form 1+, imprimante 3D stéréolithographie.\n\nLa photopolymérisation est le premier procédé de prototypage rapide à avoir été développé dans les années 1980. Le nom de SLA (pour StereoLithography Apparatus) lui a été donné. Il repose sur les propriétés qu'ont certaines résines à se polymériser sous l'effet de la lumière et de la chaleur. (Source : wikipédia)\n\nPossibilité d'utiliser 3 résines de couleurs différentes au Lab : noir, blanc et translucide.\n\nPlus d'infos sur le site web de Formlab", spec: "Imprimante :\n- Dimensions : 30 × 28 × 45 cm\n- Poids : 8 kg\n- Température d'utilisation : 18–28° C\n- Alimentation : 100–240 V ; 1.5 A 50/60 Hz ; 60 W.\n\nCaractéristiques du laser :\n- EN 60825-1:2007 certifié\n- Class 1 Laser Product\n- 405nm violet laser\n\nPropriétés d'impression :\n- Technologie stéréolithographie (SLA)\n- Volume d'impression : 125 × 125 × 165 mm\n- Dimension minimale : 300 microns\n- Épaisseur des couches (Résolution verticale) : 25, 50, 100 microns\n- Ressources Générées automatiquement\n- Facilement amovible", slug: "form1-imprimante-3d"} + ]) + + Price.all.each do |p| + p.update_columns(amount: (rand()*50+5).floor*100) + end end +# if Plan.count == 0 +# Group.all.each do |group| +# %w(month year).each do |interval| +# Plan.create!(base_name: "plan #{SecureRandom.hex(4)}", amount: (rand()*200+50).floor*100, interval: interval, group: group) +# end +# end +# end + if Category.count == 0 Category.create!([ - {name: "Stage"}, - {name: "Atelier"} - ]) + {name: "Stage"}, + {name: "Atelier"} + ]) +end + +unless Setting.find_by(name: 'about_body').try(:value) + setting = Setting.find_or_initialize_by(name: 'about_body') + setting.value = "

    Le Fab Lab de La Casemate est un"+ + " atelier de fabrication numérique où l’on peut utiliser des machines de découpe, des imprimantes 3D,… permettant"+ + " de travailler sur des matériaux variés : plastique, bois, carton, vinyle, … afin de créer toute sorte d’objet grâce"+ + " à la conception assistée par ordinateur ou à l’électronique. Mais le Fab Lab est aussi un lieu d’échange de"+ + " compétences technique.

    "+ + "

    Le Fab Lab de La Casemate est un espace"+ + " permanent : ouvert à tous, il offre la possibilité de réaliser des objets soi-même, de partager ses"+ + " compétences et d’apprendre au contact des médiateurs du Fab Lab et des autres usagers.

    "+ + "

    La formation au Fab Lab s’appuie sur des projets et le partage de connaissances : vous devez prendre"+ + " part à la capitalisation des connaissances et à l’instruction des autres utilisateurs.

    " + setting.save +end + +unless Setting.find_by(name: 'about_title').try(:value) + setting = Setting.find_or_initialize_by(name: 'about_title') + setting.value = "Imaginer, Fabriquer,
    Partager au Fab Lab
    de La Casemate" + setting.save +end + +unless Setting.find_by(name: 'about_contacts').try(:value) + setting = Setting.find_or_initialize_by(name: 'about_contacts') + setting.value = "
    "+ + "
    Manager Fab Lab :
    "+ + "
    jean-michel.molenaar@lacasemate.fr
    "+ + "
    Responsable médiation :
    "+ + "
    catherine.demarcq@lacasemate.fr
    "+ + "
    Animateur scientifique :
    "+ + "
    diego.scharager@lacasemate.fr
    "+ + "
    "+ + "

    "+ + "

    Visitez le site de La Casemate

    " + setting.save +end + +unless Setting.find_by(name: 'twitter_name').try(:value) + setting = Setting.find_or_initialize_by(name: 'twitter_name') + setting.value = "fablabgrenoble" + setting.save +end + +unless Setting.find_by(name: 'machine_explications_alert').try(:value) + setting = Setting.find_or_initialize_by(name: 'machine_explications_alert') + setting.value = "Tout achat d'heure machine est définitif. Aucune"+ + " annulation ne pourra être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez en"+ + " modifier la date et l'horaire à votre convenance et en fonction du calendrier proposé. Passé ce délais,"+ + " aucun changement ne pourra être effectué." + setting.save +end + +unless Setting.find_by(name: 'training_explications_alert').try(:value) + setting = Setting.find_or_initialize_by(name: 'training_explications_alert') + setting.value = "Toute réservation de formation est définitive."+ + " Aucune annulation ne pourra être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez"+ + " en modifier la date et l'horaire à votre convenance et en fonction du calendrier proposé. Passé ce délais,"+ + " aucun changement ne pourra être effectué." + setting.save +end + +unless Setting.find_by(name: 'subscription_explications_alert').try(:value) + setting = Setting.find_or_initialize_by(name: 'subscription_explications_alert') + setting.value = "

    Règle sur la date de début des abonnements

    • "+ + " Si vous êtes un nouvel utilisateur - i.e aucune "+ + " formation d'enregistrée sur le site - votre abonnement débutera à la date de réservation de votre première "+ + " formation.
    • Si vous avez déjà une "+ + " formation ou plus de validée, votre abonnement débutera à la date de votre achat d'abonnement.
    • "+ + "

    Merci de bien prendre ses informations en compte, et merci de votre compréhension. L'équipe du Fab Lab.
    "+ + "

    " + setting.save +end + +unless Setting.find_by(name: 'event_reduced_amount_alert').try(:value) + setting = Setting.find_or_initialize_by(name: 'event_reduced_amount_alert') + setting.value = "* Tarif réduit si vous avez moins de 25 ans, que vous êtes étudiant ou demandeur d'emploi." + setting.save +end + + +unless Setting.find_by(name: 'invoice_logo').try(:value) + setting = Setting.find_or_initialize_by(name: 'invoice_logo') + setting.value = "iVBORw0KGgoAAAANSUhEUgAAAyAAAABNCAYAAABe8gBxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA/RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ1dWlkOjVEMjA4OTI0OTNCRkRCMTE5MTRBODU5MEQzMTUwOEM4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjAzODFDRjYwMEE1RTExRTQ5NzJDRkFDOTI4MTJEOEM1IiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjAzODFDRjVGMEE1RTExRTQ5NzJDRkFDOTI4MTJEOEM1IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIElsbHVzdHJhdG9yIENTNCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ1dWlkOjI2NzQ5N2UwLTgyODEtNDg4Ny1iOGZlLTExMzA0ODhhZjRhOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3RDc4OUVFODZFRjBFMzEx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" + setting.save +end + +unless Setting.find_by(name: 'invoice_reference').try(:value) + setting = Setting.find_or_initialize_by(name: 'invoice_reference') + setting.value = "YYMMmmmX[/VL]R[/A]" + setting.save +end + +unless Setting.find_by(name: 'invoice_code-active').try(:value) + setting = Setting.find_or_initialize_by(name: 'invoice_code-active') + setting.value = "true" + setting.save +end + +unless Setting.find_by(name: 'invoice_code-value').try(:value) + setting = Setting.find_or_initialize_by(name: 'invoice_code-value') + setting.value = "INMEDFABLAB" + setting.save +end + +unless Setting.find_by(name: 'invoice_order-nb').try(:value) + setting = Setting.find_or_initialize_by(name: 'invoice_order-nb') + setting.value = "nnnnnn-MM-YY" + setting.save +end + +unless Setting.find_by(name: 'invoice_VAT-active').try(:value) + setting = Setting.find_or_initialize_by(name: 'invoice_VAT-active') + setting.value = "false" + setting.save +end + +unless Setting.find_by(name: 'invoice_VAT-rate').try(:value) + setting = Setting.find_or_initialize_by(name: 'invoice_VAT-rate') + setting.value = "20.0" + setting.save +end + +unless Setting.find_by(name: 'invoice_text').try(:value) + setting = Setting.find_or_initialize_by(name: 'invoice_text') + setting.value = "Notre association n'est pas assujettie à la TVA" + setting.save +end + +unless Setting.find_by(name: 'invoice_legals').try(:value) + setting = Setting.find_or_initialize_by(name: 'invoice_legals') + setting.value = "La Casemate
    "+ + "2 Place St Laurent 38000 GRENOBLE France
    "+ + "Tél. Administration : +33 4 76 44 88 80
    "+ + "Fax. : +33 4 76 42 76 66
    "+ + "SIRET : 317 270 593 00013 - APE 913 E" + setting.save +end + +unless Setting.find_by(name: 'booking_window_start').try(:value) + setting = Setting.find_or_initialize_by(name: 'booking_window_start') + setting.value = "1970-01-01 08:00:00" + setting.save +end + +unless Setting.find_by(name: 'booking_window_end').try(:value) + setting = Setting.find_or_initialize_by(name: 'booking_window_end') + setting.value = "1970-01-01 23:59:59" + setting.save +end + +unless Setting.find_by(name: 'booking_move_enable').try(:value) + setting = Setting.find_or_initialize_by(name: 'booking_move_enable') + setting.value = 'true' + setting.save +end + +unless Setting.find_by(name: 'booking_move_delay').try(:value) + setting = Setting.find_or_initialize_by(name: 'booking_move_delay') + setting.value = '24' + setting.save +end + +unless Setting.find_by(name: 'booking_cancel_enable').try(:value) + setting = Setting.find_or_initialize_by(name: 'booking_cancel_enable') + setting.value = 'false' + setting.save +end + +unless Setting.find_by(name: 'booking_cancel_delay').try(:value) + setting = Setting.find_or_initialize_by(name: 'booking_cancel_delay') + setting.value = '24' + setting.save +end + +unless Setting.find_by(name: 'main_color').try(:value) + setting = Setting.find_or_initialize_by(name: 'main_color') + setting.value = '#cb1117' + setting.save +end + +unless Setting.find_by(name: 'secondary_color').try(:value) + setting = Setting.find_or_initialize_by(name: 'secondary_color') + setting.value = '#ffdd00' + setting.save +end + +Stylesheet.build_sheet! + +unless Setting.find_by(name: 'training_information_message').try(:value) + setting = Setting.find_or_initialize_by(name: 'training_information_message') + setting.value = "Avant de réserver une formation, nous vous conseillons de consulter nos offres d'abonnement qui"+ + " proposent des conditions avantageuses sur le prix des formations et les heures machines." + setting.save +end + + +unless Setting.find_by(name: 'fablab_name').try(:value) + setting = Setting.find_or_initialize_by(name: 'fablab_name') + setting.value = 'Fab Lab de La Casemate' + setting.save +end + +unless Setting.find_by(name: 'name_genre').try(:value) + setting = Setting.find_or_initialize_by(name: 'name_genre') + setting.value = 'male' + setting.save +end + + +unless DatabaseProvider.count > 0 + db_provider = DatabaseProvider.new + db_provider.save + + unless AuthProvider.find_by(providable_type: DatabaseProvider.name) + provider = AuthProvider.new + provider.name = 'Fablab' + provider.providable = db_provider + provider.status = 'active' + provider.save + end end diff --git a/db/test_seeds.rb b/db/test_seeds.rb new file mode 100644 index 000000000..1b28fc80c --- /dev/null +++ b/db/test_seeds.rb @@ -0,0 +1,64 @@ +Group.create!([ + {name: "standard, association", slug: "standard"}, + {name: "étudiant, - de 25 ans, enseignant, demandeur d'emploi", slug: "student"}, + {name: "artisan, commerçant, chercheur, auto-entrepreneur", slug: "merchant"}, + {name: "PME, PMI, SARL, SA", slug: "business"} +]) + +if Category.count == 0 + Category.create!([ + {name: "Stage"}, + {name: "Atelier"} + ]) +end + +if StatisticIndex.count == 0 + StatisticIndex.create!([ + {id:1, es_type_key:'subscription', label:'Abonnements'}, + {id:2, es_type_key:'machine', label:'Heures machines'}, + {id:3, es_type_key:'training', label:'Formations'}, + {id:4, es_type_key:'event', label:'Ateliers/Stages'}, + {id:5, es_type_key:'account', label:'Inscriptions', ca: false}, + {id:6, es_type_key:'project', label:'Projets', ca: false}, + {id:7, es_type_key:'user', label:'Utilisateurs', table: false, ca: false} + ]) + connection = ActiveRecord::Base.connection + if connection.instance_values["config"][:adapter] == 'postgresql' + connection.execute("SELECT setval('statistic_indices_id_seq', 7);") + end +end + +if StatisticField.count == 0 + StatisticField.create!([ + # available data_types : index, number, date, text, list + {key:'trainingId', label:'ID Formation', statistic_index_id: 3, data_type: 'index'}, + {key:'trainingDate', label:'Date Formation', statistic_index_id: 3, data_type: 'date'}, + {key:'eventId', label:'ID Évènement', statistic_index_id: 4, data_type: 'index'}, + {key:'eventDate', label:'Date Évènement', statistic_index_id: 4, data_type: 'date'}, + {key:'themes', label:'Thèmes', statistic_index_id: 6, data_type: 'list'}, + {key:'components', label:'Composants', statistic_index_id: 6, data_type: 'list'}, + {key:'machines', label:'Machines', statistic_index_id: 6, data_type: 'list'}, + {key:'name', label:'Nom Évènement', statistic_index_id: 4, data_type: 'text'}, + {key:'userId', label:'ID Utilisateur', statistic_index_id: 7, data_type: 'index'} + ]) +end + +if StatisticType.count == 0 + StatisticType.create!([ + {id:1, statistic_index_id: 1, key: 'month', label:'Abonnements mensuels', graph: true, simple: true}, + {id:2, statistic_index_id: 1, key: 'year', label:'Abonnements annuels', graph: true, simple: true}, + {id:3, statistic_index_id: 2, key: 'booking', label:'Réservations', graph: true, simple: true}, + {id:4, statistic_index_id: 2, key: 'hour', label:"Nombre d'heures", graph: true, simple: false}, + {id:5, statistic_index_id: 3, key: 'booking', label:'Réservations', graph: false, simple: true}, + {id:6, statistic_index_id: 3, key: 'hour', label:"Nombre d'heures", graph: false, simple: false}, + {id:7, statistic_index_id: 4, key: 'booking', label:'Nombre de places', graph: false, simple: false}, + {id:8, statistic_index_id: 4, key: 'hour', label:"Nombre d'heures", graph: false, simple: false}, + {id:9, statistic_index_id: 5, key: 'member', label:'Utilisateurs', graph: true, simple: true}, + {id:10, statistic_index_id: 6, key: 'project', label:'Projets', graph: false, simple: true}, + {id:11, statistic_index_id: 7, key: 'revenue', label:"Chiffre d'affaires", graph: false, simple: false} + ]) + connection = ActiveRecord::Base.connection + if connection.instance_values["config"][:adapter] == 'postgresql' + connection.execute("SELECT setval('statistic_types_id_seq', 11);") + end +end diff --git a/doc/controllers_brief.svg b/doc/controllers_brief.svg new file mode 100644 index 000000000..b60203108 --- /dev/null +++ b/doc/controllers_brief.svg @@ -0,0 +1,206 @@ + + + + + + +controllers_diagram + + +_diagram_info +Controllers diagram +Date: Oct 26 2015 - 13:23 +Migration version: 20151008152219 +Generated by RailRoady 1.4.0 +http://railroady.prestonlee.com + + +SessionsController + +SessionsController + + +RegistrationsController + +RegistrationsController + + +API::TagsController + +API::TagsController + + +API::StatisticsController + +API::StatisticsController + + +API::TrainingsPricingsController + +API::TrainingsPricingsController + + +API::PlansController + +API::PlansController + + +API::AuthProvidersController + +API::AuthProvidersController + + +API::CreditsController + +API::CreditsController + + +API::ComponentsController + +API::ComponentsController + + +API::CustomAssetsController + +API::CustomAssetsController + + +API::PricingController + +API::PricingController + + +API::FeedsController + +API::FeedsController + + +API::MembersController + +API::MembersController + + +API::PricesController + +API::PricesController + + +API::ReservationsController + +API::ReservationsController + + +API::EventsController + +API::EventsController + + +API::MachinesController + +API::MachinesController + + +API::ThemesController + +API::ThemesController + + +API::CategoriesController + +API::CategoriesController + + +API::SubscriptionsController + +API::SubscriptionsController + + +API::StylesheetsController + +API::StylesheetsController + + +API::SlotsController + +API::SlotsController + + +API::AdminsController + +API::AdminsController + + +API::GroupsController + +API::GroupsController + + +API::AvailabilitiesController + +API::AvailabilitiesController + + +API::UsersController + +API::UsersController + + +API::ProjectsController + +API::ProjectsController + + +API::NotificationsController + +API::NotificationsController + + +API::TrainingsController + +API::TrainingsController + + +API::SettingsController + +API::SettingsController + + +API::InvoicesController + +API::InvoicesController + + +API::LicencesController + +API::LicencesController + + +PasswordsController + +PasswordsController + + +ApplicationController + +ApplicationController + + +Users::OmniauthCallbacksController + +Users::OmniauthCallbacksController + + +WebhooksController + +WebhooksController + + +ConfirmationsController + +ConfirmationsController + + + diff --git a/doc/controllers_complete.svg b/doc/controllers_complete.svg new file mode 100644 index 000000000..a8034b87e --- /dev/null +++ b/doc/controllers_complete.svg @@ -0,0 +1,551 @@ + + + + + + +controllers_diagram + + +_diagram_info +Controllers diagram +Date: Oct 26 2015 - 13:23 +Migration version: 20151008152219 +Generated by RailRoady 1.4.0 +http://railroady.prestonlee.com + + +SessionsController + +SessionsController + +new + +set_csrf_headers + +_layout + + +RegistrationsController + +RegistrationsController + +create + + +_layout + + +API::TagsController + +API::TagsController + +create +destroy +index +show +update + + +_layout +set_tag +tag_params + + +API::StatisticsController + +API::StatisticsController + +account +event +index +machine +project +subscription +training +user + + +_layout + + +API::TrainingsPricingsController + +API::TrainingsPricingsController + +index +trainings_pricing_params +update + + +_layout + + +API::PlansController + +API::PlansController + +create +destroy +index +show +update + + +_layout +plan_params + + +API::AuthProvidersController + +API::AuthProvidersController + +active +create +destroy +index +mapping_fields +show +update + + +_layout +provider_params +set_provider + + +API::CreditsController + +API::CreditsController + +create +destroy +index +update + + +_layout +credit_params +set_credit + + +API::ComponentsController + +API::ComponentsController + +create +destroy +index +show +update + + +_layout +component_params +set_component + + +API::CustomAssetsController + +API::CustomAssetsController + +create +destroy +index +show +update + + +_layout +custom_asset_params +set_custom_asset + + +API::PricingController + +API::PricingController + +index +update + + +_layout + + +API::FeedsController + +API::FeedsController + +twitter_timelines + + +_layout + + +API::MembersController + +API::MembersController + +create +destroy +export_members +export_reservations +export_subscriptions +index +last_subscribed +merge +show +update + + +_layout +set_member +user_params + + +API::PricesController + +API::PricesController + +index +price_params +update + + +_layout + + +API::ReservationsController + +API::ReservationsController + +create +index +show +update + + +_layout +is_first_training_and_subscription +reservation_params +set_reservation + + +API::EventsController + +API::EventsController + +create +destroy +index +show +upcoming +update + + +_layout +event_params +set_event + + +API::MachinesController + +API::MachinesController + +create +destroy +index +show +update + + +_layout +is_reserved +machine_params +set_machine + + +API::ThemesController + +API::ThemesController + +create +destroy +index +show +update + + +_layout +set_theme +theme_params + + +API::CategoriesController + +API::CategoriesController + +index + + +_layout + + +API::SubscriptionsController + +API::SubscriptionsController + +create +show +update + + +_layout +set_subscription +subscription_params +subscription_update_params + + +API::StylesheetsController + +API::StylesheetsController + +show + + +_layout + + +API::SlotsController + +API::SlotsController + +cancel +update + + +_layout +is_first_training_and_subscription +set_slot +slot_params + + +API::AdminsController + +API::AdminsController + +create +destroy +index + + +_layout +admin_params + + +API::GroupsController + +API::GroupsController + +create +destroy +index +update + + +_layout +group_params + + +API::AvailabilitiesController + +API::AvailabilitiesController + +create +destroy +index +machine +reservations +show +trainings +update + + +_layout +availability_params +can_show_slot_plus_three_months +is_reserved +is_subscription_year +set_availability +verify_machine_is_reserved +verify_training_is_reserved + + +API::UsersController + +API::UsersController + +create +index + + +_layout +partner_params + + +API::ProjectsController + +API::ProjectsController + +collaborator_valid +create +destroy +index +last_published +search +show +update + + +_layout +project_params +set_project + + +API::NotificationsController + +API::NotificationsController + +index +show +update +update_all + + +_layout +set_notification + + +API::TrainingsController + +API::TrainingsController + +create +destroy +index +show +update + + +_layout +set_training +training_params +valid_training_params + + +API::SettingsController + +API::SettingsController + +index +show +update + + +_layout +names_as_string_to_array +setting_params + + +API::InvoicesController + +API::InvoicesController + +create +download +index + + +_layout +avoir_params +set_invoice + + +API::LicencesController + +API::LicencesController + +create +destroy +index +show +update + + +_layout +licence_params +set_licence + + +PasswordsController + +PasswordsController + +create + + +_layout + + +ApplicationController + +ApplicationController + +index + +configure_permitted_parameters +default_url_options +permission_denied +set_csrf_cookie +verified_request? + +_layout + + +Users::OmniauthCallbacksController + +Users::OmniauthCallbacksController + +database-fablab + + +_layout +email_exists? +generate_unique_username +username_exists? + + +WebhooksController + +WebhooksController + +create + + +_layout + + +ConfirmationsController + +ConfirmationsController + +after_confirmation_path_for + + +_layout + + + diff --git a/doc/diagram.mwb b/doc/diagram.mwb new file mode 100644 index 0000000000000000000000000000000000000000..92ca61721fc7bbc0e4c3a734742011d0d821d8d5 GIT binary patch literal 47116 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 literal 0 HcmV?d00001 diff --git a/doc/diagram.png b/doc/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..fcb0f80a99fe6e048e3bb2ca8eb895f6b466b791 GIT binary patch literal 1200977 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

    |GhJ~yS{hpcsz+` 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 {!C2ALEY>rmfbS% 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%0DxH`>B(QN}66mF2pdkN7+KJj4tg0 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%0A3fzv zTFZUDO*6X36S{x8zKO(%fTg7Uh}p}?`kvcTmB_g(qb?tP7AA_pDDT%{Vja@6wWDs^ zKHM?&jeg|GmpLQ1r0rI;HQ~C73I#SYjbBKXCV)#Tc64hWfB#Z)jCIu49urA@tGSzj zYgzpAf9^~vm~5=;dmj;-{&EQ~;YW;U|BbvcSIYN`&1#ZD6MjIsPX+vxxp2X3Hj5=D z8eV&rc{$EqGlG!W!dxjZIF-)>-ZtGIh${TyW3W;5JN9C*99&S)%%!wvA^1jq%;Y_;&gPT zf)u=TEZPy1WpcQU5rv5SPb-nikBaK{xWY`)CcBH^1%0h|MY*}*7adZiOS0U39U_(_ z20U$VZ1_}{m6Qt6s_1^4k5^=$*}3GtQ&3Sii_5hy*G=H^yCLIOrbyM%G=2U;O*qQI zTXN%5Fnqm(T49h3wh^Q^@#AM)A!4qgWJkICsk;J{koTRy^3Jvi<>07od!!D=;WVhj z>w+Vq*h@tBtVXonfDB!eJ^{FmW%z3mw%3T&ce>gB{yxsVc-o+GAc^U?2WY&k;(cW* z?>^Gq_p#=Te1RSG#|&B0F#8}`wKlWGPy1cqbd@Cxk>3uhvA5FS{E?Dk!0qIu`x1HD6@@2~9&}+_(kLAj?j589ve>D!j&cDbDGJ1hKTvf%QRRWkxgL z?X8~1h*cRcb@=BmoNM%1E>!bqiitmz?>fR6_096BTcDGrgZT*G10RMI z)MGDg7Mi&{eTw~GdXiZkn0v%346z^VM!Jq(2j28 z$ua_+nt<+EMq~ggkx;Wn+;q#n7P&evdYvh-|KMIR{RiaD^bRQJ@(bOk9d-TDL~x~? z1L!(X2b|O&mxp{G$Z9Ff_T1OQ(WJ8zbif4WO1b9mmMBv;dmsO3OTx3ooq@e|;z8{o zTeHiK-5%`RV+EJV;joWQm&0x|rFyf|iNi9Ww!+UG=Aao8yr8rVoz)73&yWEWkf^VW zyR_S!)rHL;DQ;2Kerl!3o#6&}L(Q@-R_3c!xAn8|M^H<%q zi(>NwdVaoiW-z@RSn$9o>{Na|^tT;-S9^>Yubpt6i2Kq9v+b^76=`nl18n2|8Q2ze zRnFZ?N#lf`_&w%v#R>Y^k&|ca&RX&j5MoqeG{VIg8}O~d2EBxUn#BBMc+5NRp=on! zuD%LPC}l&#?shs-UW6mp@6Id{BLZ0ym=`OYZQIOtIj7y-vn%?b;0jXNv3WeB@tq-` z*~Lzemx!MMZw03C!@L!}g6KPjEmPMxL)NB}f=S%3xz&(9uGT)JhSxG%Ti1uf!OImX zT|oLx7*A4?%4$M?0aAjepsPOc9NLl&UJX_;gjhnOjIk)M*AslNt`G6tpyZyl~R|1xyqb4uA=b>$I0A_9iA4 za!mIvX>O*S&0FPi4q`({v#_t8_?~HU=8ot6B4rSpNFe)<`j1brd4c3a$+L=h+q2xp zr!05Or4%$q%3RHGBRg&U*q1(=vg}RQ*+y}%{Kh#AaH9#Q}*B|*GqI-U|0U_Q8=?C z)!QPAK`8WCVDIqj&Qnez`-<^_A&qFO)jv?075 zMRJJHVdW#jDOY;$*#yep!TNfA^je!95o0&+vGNZFXgn`*%uqUw_;SVv6Unc>_V(8Q zBJh$bM;&Y5E{(`;+H6l_)lF(-1l}Nh&Jt`Dz-0@%>j`MF0e&Or&uCtVc_{omSx`fOsu zp$YC z%1F76$2HRSuWp~n4!QCq^ff^2{NsyzEF?wjtID-608CtsNx5Dn0y1;c{G00*APEzi zWk39T62r)J5`}>GLYp2HFKmtXgUty`f!^)lPv zbz{23*4j(1D=`rI^>$z?3oOUxH8jNk_ATZgt!g-Pe!rz?NwVQ6OcB*JsXH1VF!L~N zNVn@K(9A9F*JlrznoQxR-tB9}ROaj6xmMOy)BRe13VSoWzMxmjMFMEdS{U&K!;5|u z5Y3Uao3|;S9Ld|MU>)9-eFa$VdclT*F$F>SqW=*Tq#o$3`~%sZqg7 zU#{}i$SIx9rHl{!_5AK7d3SXbV0q(@*UYx82^)c*h;kg`+u5blv#-yIb|zq98@($< z|E2L#WWKx+1*|o*Gi|8*0EKx zLvJo7r10yWU$_$}49caeJ#*Wg-N#4TO-PP{x7tU(=%GNgFyOwGI9&U~@jCBtjI71@ zKted6bb1tEQs}8g`FtC3;-oZ$*YC3b@?+TY>4~OKfuDqF5P2%972UhVbic(yPa{hD z5NQGZ^Of>9io`VE8L3Ixd`{Khc}NMKPT$R~X>zw#`%dV7mUs3}W=+$%)Zs7S@luL-GMR_a8)2Qe%InquK)O zO0+@|%>D(oV|x8V*uJfCb$ClG^r^h9Mqu&)yp~;xoHO=y%Pu{wzry%3PloPuQXz5Z z#`xF5Sk_sj{j9Cf-v?y+9%%z;DCgT~#K9L%XM>@S6*1wD0wp>Wrc$Y*yt_67@hqam zx6ioF@B}w%*~mflb~;_RsvjU;o?`<%^E2-0nCE1vTTp;mMG$wOWfE)RXn#@VHBp># z>4bBO&*OI`d=Z&Bk9_ey96rNq4TJaWd#CJiAs(e-Y4=-;g8N??#k;Jdyvb;|0s!A3 zob5adO^A#pw7armQ{PFZZ+`$hg=-a++Znn4fWwBLvfydH77@pehJ5~ql*Wk=NW7p{ zvY($OTT))Mt4ZuM@q*zib)s55G4au14Zjyt`JVS@f1f8USg?~;yK_M|r9rh%gZpJC zC$2at0oJiW_mcJ%AK|jU3aby-2X#0(qD;^VU*%bgdERzVo&6Dh)It=mVS=~5K_k?F zdH^b{b>E4P@|K+k9QnYhxcA6q8jz39|L9B9D@I#`L*ewYAO8uQ{OmP<2dsLF-Myw;`Dbl35G$d7f0u7RtX0R@P3prq=6sodU9oBaOQ` z;V(B5@^UwT-wRHLa$Jj+90Gl6I96YL7g)@P?)>Yx0R_do^Fddh zgdKmP>a%&U6wChl4M6h!0M~Xc>d(di~R+-r($CC_o(S1$(lW)5c z0gAU*9_fD-JUCxLL9th6qPWTZCg2DY&3;=H6#E48|Mlakt&fKO^B{x1@}I5S0CIca zUH5|1L*d|mA4k|r>_#$ptdlB6PSD*yNa_SN{B)4-@!GD`JrhHigeK2xo_NRK}Kb>%2_9=rHa z6#B%lOF!t-dXrYe`X39QjA2^(9x{x{vj{0r`F#~`Lo&4^opSmxp0)OZ}pr(bf`fXsIaAg zj{C+g^4K@)K5hXfpb8Ed?Mdz(9*#?h6-#H%9`gGsH&}z0pIdSINvzpf9S6uN=iWVW zng7OHqx$N9NTOATt22^>;YoTgUbm`Y1HS$)JpgZdr@#Boo7~qp0A($(dOXi8`+QwB z^mX&P5|{a?jRj>aPN#*YHq(+8oVpbZFRnB1hg`bxcz}GVxeRzeP`+U8QoXyKc-*7A=NbBs+@b7;+$%I=VNhOG7Zmy za(5#x$M_B7>T1C3bvRWQJU(v?=4vB{}v@0{pQD z&jB|&1!%{}UP2fM-NNTX&?hF$1^?6k0CTJHHEw2N{CS)$vaH5R&DV`w;LLi2^qY0Q zfcuyg8+QCx`*p>4$+Q8m8@?43y#B94aB|HDr_F$^!?KH%-krbO@a&I9ZvF}rBD_Fs z`Stk~0{;ED;TJ*7c$Ejego|17Md0yR`ELd9EDoecfFCYO4~L_Pt33#pnI^Rat*Cx> z=h^j(S}4Mn#pkYF4{3^UaIib9{CfU(?`|4I2x7**wDq-jE)3KbTB!tG!ubG_rJ4i8 zyx%d?;|aG^0iFPE($muuk-(?%aUd7wpycg+HU1?r)Y@?rQS+l`X&{m|ss}i0!}DP- zCfhc3J6!CpSgr%I(4x63NyI26Bx&mPSf^ zo5OHTdL_cYW^nf}rmeAr@xH-4quOoEU?jhuq6y5RePr6LrvsB;B9gftz2E#XnT(XzQDWs;ZIsun)ZPly_+9C($G8l;v0hM1Je8iL{h>}hCBulC7d zRIJpiDAgGs#$=%p0j;oIWLVFolWE+t>4>?S;-d|3%bI=Yg7pTYir$wZ9`A5y@r?s^ zx1XWmSYw(GW0JZEc2ExU*8FF0C{!9wEvL)Y=NVTy_blHLIZI$r`w~ezS_%m#j#s$M z0IOnYNoumSFN_J~UneCg=?q63SNUyuv}LrEjriMi&}#wjj(C+I);}QrR;H~|DNx)j zyHzEmIsuB1&&@HQT`KmS>2eq?t+{WMk@P!Z+3ki()1t+^7E(>72rv=Yc(i&OBhIW= zdsYoHvIBM0{M7YU{6fmg`EA{V)$zV;m}FK~yk4pOxM9hB)al&Z$g=qAPXMSv$TJn& zx*2*?kPr}JFPhK*T@d_k5vi~`0O85uEw=ccT2ujPv2La#HrFOLl)5iV-IBR+T6^ajP0 zcof114aGv4bgqtc1IA5F(%5$9;Jq%Co{YX{x4>BDfMV_(t8}yL741P=chU3UMYV^>l4$1@ZZ!h2EYkAM4rZ)$~f Y_IaACkqsv;Y7A literal 0 HcmV?d00001 diff --git a/doc/diagram_eer_fablab.pdf b/doc/diagram_eer_fablab.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1eaba9e82357c43521273f783d9eeefccb349d63 GIT binary patch literal 54153 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 + + + + + +models_diagram + + +_diagram_info +Models diagram +Date: Oct 26 2015 - 13:23 +Migration version: 20151008152219 +Generated by RailRoady 1.4.0 +http://railroady.prestonlee.com + + +Group + +Group + + +Price + +Price + + +Group->Price + + + +machines_prices + + +TrainingsPricing + +TrainingsPricing + + +Group->TrainingsPricing + + + + + +Plan + +Plan + + +Group->Plan + + + + + +User + +User + + +Group->User + + + + + +InvoiceItem + +InvoiceItem + + +InvoiceItem->InvoiceItem + + + + + +AvailabilityTag + +AvailabilityTag + + +UserAvatar + +UserAvatar + + +ProjectImage + +ProjectImage + + +Tag + +Tag + + +Tag->AvailabilityTag + + + + + +Tag->User + + + + + +Availability + +Availability + + +Tag->Availability + + + + + +UserTag + +UserTag + + +Tag->UserTag + + + + + +Event + +Event + + +EventImage + +EventImage + + +Event->EventImage + + + + + +EventFile + +EventFile + + +Event->EventFile + + + + + +Category + +Category + + +Event->Category + + + + + +StatisticSubType + +StatisticSubType + + +StatisticType + +StatisticType + + +StatisticSubType->StatisticType + + + + + +StatisticTypeSubType + +StatisticTypeSubType + + +StatisticSubType->StatisticTypeSubType + + + + + +Project + +Project + + +Project->ProjectImage + + + + + +Project->User + + + + + +Theme + +Theme + + +Project->Theme + + + + + +ProjectCao + +ProjectCao + + +Project->ProjectCao + + + + + +ProjectStep + +ProjectStep + + +Project->ProjectStep + + + + + +ProjectUser + +ProjectUser + + +Project->ProjectUser + + + + + +Machine + +Machine + + +Project->Machine + + + + + +Component + +Component + + +Project->Component + + + + + +CustomAsset + +CustomAsset + + +CustomAssetFile + +CustomAssetFile + + +CustomAsset->CustomAssetFile + + + + + +Stats::Event + +Stats::Event + + +Stats::Project + +Stats::Project + + +Stats::User + +Stats::User + + +Stats::Subscription + +Stats::Subscription + + +Stats::Training + +Stats::Training + + +Stats::Account + +Stats::Account + + +Stats::Machine + +Stats::Machine + + +Invoice + +Invoice + + +Invoice->InvoiceItem + + + + + +Invoice->Invoice + + + +avoir + + +PlanFile + +PlanFile + + +OfferDay + +OfferDay + + +OfferDay->Invoice + + + + + +StatisticIndex + +StatisticIndex + + +StatisticType->StatisticIndex + + + + + +StatisticType->StatisticTypeSubType + + + + + +Address + +Address + + +Plan->Price + + + + + +Plan->PlanFile + + + + + +Credit + +Credit + + +Plan->Credit + + + + + +Plan->Credit + + + +training_credits + + +Plan->Credit + + + +machine_credits + + +Subscription + +Subscription + + +Plan->Subscription + + + + + +PlanImage + +PlanImage + + +Plan->PlanImage + + + + + +MachineFile + +MachineFile + + +MachinesPricing + +MachinesPricing + + +StatisticGraph + +StatisticGraph + + +OAuth2Mapping + +OAuth2Mapping + + +Licence + +Licence + + +Licence->Project + + + + + +User->Project + + + +my_projects + + +User->Invoice + + + + + +User->Credit + + + + + +Role + +Role + + +User->Role + + + + + +Notification + +Notification + + +User->Notification + + + + + +User->Subscription + + + + + +Training + +Training + + +User->Training + + + + + +Reservation + +Reservation + + +User->Reservation + + + + + +UsersCredit + +UsersCredit + + +User->UsersCredit + + + + + +User->UserTag + + + + + +UserTraining + +UserTraining + + +User->UserTraining + + + + + +User->ProjectUser + + + + + +Profile + +Profile + + +User->Profile + + + + + +ProjectStepImage + +ProjectStepImage + + +Credit->UsersCredit + + + + + +PartnerPlan + +PartnerPlan + + +PartnerPlan->Price + + + + + +PartnerPlan->PlanFile + + + + + +PartnerPlan->Credit + + + + + +PartnerPlan->Credit + + + +training_credits + + +PartnerPlan->Credit + + + +machine_credits + + +PartnerPlan->Role + + + + + +PartnerPlan->Subscription + + + + + +PartnerPlan->PlanImage + + + + + +OAuth2Provider + +OAuth2Provider + + +OAuth2Provider->OAuth2Mapping + + + + + +AuthProvider + +AuthProvider + + +OAuth2Provider->AuthProvider + + + + + +Asset + +Asset + + +DatabaseProvider + +DatabaseProvider + + +DatabaseProvider->AuthProvider + + + + + +Availability->AvailabilityTag + + + + + +Availability->Event + + + + + +Availability->Training + + + + + +Availability->Reservation + + + + + +Slot + +Slot + + +Availability->Slot + + + + + +MachinesAvailability + +MachinesAvailability + + +Availability->MachinesAvailability + + + + + +TrainingsAvailability + +TrainingsAvailability + + +Availability->TrainingsAvailability + + + + + +Availability->Machine + + + + + +Subscription->Invoice + + + + + +Subscription->OfferDay + + + + + +Training->TrainingsPricing + + + + + +Training->Plan + + + + + +Training->Credit + + + + + +Training->Reservation + + + + + +Training->UserTraining + + + + + +Training->TrainingsAvailability + + + + + +Training->Machine + + + + + +ProjectStep->ProjectStepImage + + + + + +Reservation->Invoice + + + + + +Reservation->Slot + + + + + +StatisticIndex->StatisticType + + + + + +StatisticIndex->StatisticGraph + + + + + +StatisticField + +StatisticField + + +StatisticIndex->StatisticField + + + + + +StatisticField->StatisticIndex + + + + + +Avoir + +Avoir + + +Avoir->InvoiceItem + + + + + +Avoir->Invoice + + + +avoir + + +NotificationType + +NotificationType + + +Stylesheet + +Stylesheet + + +MachineImage + +MachineImage + + +Feed + +Feed + + +Machine->Price + + + + + +Machine->Plan + + + + + +Machine->MachineFile + + + + + +Machine->Credit + + + + + +Machine->Reservation + + + + + +Machine->MachinesAvailability + + + + + +Machine->MachineImage + + + + + +Profile->UserAvatar + + + + + +Profile->Address + + + + + +Setting + +Setting + + + diff --git a/doc/models_complete.svg b/doc/models_complete.svg new file mode 100644 index 000000000..43bd49de8 --- /dev/null +++ b/doc/models_complete.svg @@ -0,0 +1,1496 @@ + + + + + + +models_diagram + + +_diagram_info +Models diagram +Date: Oct 26 2015 - 13:23 +Migration version: 20151008152219 +Generated by RailRoady 1.4.0 +http://railroady.prestonlee.com + + +Group + +Group + +id :integer +name :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone +slug :character varying(255) + + +Price + +Price + +id :integer +group_id :integer +plan_id :integer +priceable_id :integer +priceable_type :character varying +amount :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Group->Price + + + +machines_prices + + +TrainingsPricing + +TrainingsPricing + +id :integer +group_id :integer +amount :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone +training_id :integer + + +Group->TrainingsPricing + + + + + +Plan + +Plan + +id :integer +name :character varying(255) +amount :integer +interval :character varying(255) +group_id :integer +stp_plan_id :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone +training_credit_nb :integer +is_rolling :boolean +description :text +type :character varying +base_name :character varying +ui_weight :integer +interval_count :integer + + +Group->Plan + + + + + +User + +User + +id :integer +username :character varying(255) +email :character varying(255) +encrypted_password :character varying(255) +reset_password_token :character varying(255) +reset_password_sent_at :timestamp without time zone +remember_created_at :timestamp without time zone +sign_in_count :integer +current_sign_in_at :timestamp without time zone +last_sign_in_at :timestamp without time zone +current_sign_in_ip :character varying(255) +last_sign_in_ip :character varying(255) +confirmation_token :character varying(255) +confirmed_at :timestamp without time zone +confirmation_sent_at :timestamp without time zone +unconfirmed_email :character varying(255) +failed_attempts :integer +unlock_token :character varying(255) +locked_at :timestamp without time zone +created_at :timestamp without time zone +updated_at :timestamp without time zone +is_allow_contact :boolean +group_id :integer +stp_customer_id :character varying(255) +slug :character varying(255) +is_active :boolean +invoicing_disabled :boolean +provider :character varying +uid :character varying +auth_token :character varying + + +Group->User + + + + + +InvoiceItem + +InvoiceItem + +id :integer +invoice_id :integer +stp_invoice_item_id :character varying(255) +amount :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone +description :text +subscription_id :integer +invoice_item_id :integer + + +InvoiceItem->InvoiceItem + + + + + +AvailabilityTag + +AvailabilityTag + +id :integer +availability_id :integer +tag_id :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +UserAvatar + +UserAvatar + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +ProjectImage + +ProjectImage + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Tag + +Tag + +id :integer +name :character varying +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Tag->AvailabilityTag + + + + + +Tag->User + + + + + +Availability + +Availability + +id :integer +start_at :timestamp without time zone +end_at :timestamp without time zone +available_type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone +nb_total_places :integer + + +Tag->Availability + + + + + +UserTag + +UserTag + +id :integer +user_id :integer +tag_id :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Tag->UserTag + + + + + +Event + +Event + +id :integer +title :character varying(255) +description :text +created_at :timestamp without time zone +updated_at :timestamp without time zone +availability_id :integer +amount :integer +reduced_amount :integer +nb_total_places :integer +nb_free_places :integer +recurrence_id :integer + + +EventImage + +EventImage + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Event->EventImage + + + + + +EventFile + +EventFile + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Event->EventFile + + + + + +Category + +Category + +id :integer +name :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Event->Category + + + + + +StatisticSubType + +StatisticSubType + +id :integer +key :character varying(255) +label :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +StatisticType + +StatisticType + +id :integer +statistic_index_id :integer +key :character varying(255) +label :character varying(255) +graph :boolean +created_at :timestamp without time zone +updated_at :timestamp without time zone +simple :boolean + + +StatisticSubType->StatisticType + + + + + +StatisticTypeSubType + +StatisticTypeSubType + +id :integer +statistic_type_id :integer +statistic_sub_type_id :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +StatisticSubType->StatisticTypeSubType + + + + + +Project + +Project + +id :integer +name :character varying(255) +description :text +created_at :timestamp without time zone +updated_at :timestamp without time zone +author_id :integer +tags :text +licence_id :integer +state :character varying(255) +slug :character varying(255) +published_at :timestamp without time zone + + +Project->ProjectImage + + + + + +Project->User + + + + + +Theme + +Theme + +id :integer +name :character varying(255) + + +Project->Theme + + + + + +ProjectCao + +ProjectCao + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Project->ProjectCao + + + + + +ProjectStep + +ProjectStep + +id :integer +description :text +project_id :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone +title :character varying(255) + + +Project->ProjectStep + + + + + +ProjectUser + +ProjectUser + +id :integer +project_id :integer +user_id :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone +is_valid :boolean +valid_token :character varying(255) + + +Project->ProjectUser + + + + + +Machine + +Machine + +id :integer +name :character varying(255) +description :text +spec :text +created_at :timestamp without time zone +updated_at :timestamp without time zone +slug :character varying(255) + + +Project->Machine + + + + + +Component + +Component + +id :integer +name :character varying(255) + + +Project->Component + + + + + +CustomAsset + +CustomAsset + +id :integer +name :character varying +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +CustomAssetFile + +CustomAssetFile + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +CustomAsset->CustomAssetFile + + + + + +Stats::Event + +Stats::Event + + + + +Stats::Project + +Stats::Project + + + + +Stats::User + +Stats::User + + + + +Stats::Subscription + +Stats::Subscription + + + + +Stats::Training + +Stats::Training + + + + +Stats::Account + +Stats::Account + + + + +Stats::Machine + +Stats::Machine + + + + +Invoice + +Invoice + +id :integer +invoiced_id :integer +invoiced_type :character varying(255) +stp_invoice_id :character varying(255) +total :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone +user_id :integer +reference :character varying(255) +avoir_mode :character varying(255) +avoir_date :timestamp without time zone +invoice_id :integer +type :character varying(255) +subscription_to_expire :boolean +description :text + + +Invoice->InvoiceItem + + + + + +Invoice->Invoice + + + +avoir + + +PlanFile + +PlanFile + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +OfferDay + +OfferDay + +id :integer +subscription_id :integer +start_at :timestamp without time zone +end_at :timestamp without time zone +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +OfferDay->Invoice + + + + + +StatisticIndex + +StatisticIndex + +id :integer +es_type_key :character varying(255) +label :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone +table :boolean +ca :boolean + + +StatisticType->StatisticIndex + + + + + +StatisticType->StatisticTypeSubType + + + + + +Address + +Address + +id :integer +address :character varying(255) +street_number :character varying(255) +route :character varying(255) +locality :character varying(255) +country :character varying(255) +postal_code :character varying(255) +placeable_id :integer +placeable_type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Plan->Price + + + + + +Plan->PlanFile + + + + + +Credit + +Credit + +id :integer +creditable_id :integer +creditable_type :character varying(255) +plan_id :integer +hours :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Plan->Credit + + + + + +Plan->Credit + + + +training_credits + + +Plan->Credit + + + +machine_credits + + +Subscription + +Subscription + +id :integer +plan_id :integer +user_id :integer +stp_subscription_id :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone +expired_at :timestamp without time zone +canceled_at :timestamp without time zone + + +Plan->Subscription + + + + + +PlanImage + +PlanImage + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Plan->PlanImage + + + + + +MachineFile + +MachineFile + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +StatisticGraph + +StatisticGraph + +id :integer +statistic_index_id :integer +chart_type :character varying(255) +limit :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +OAuth2Mapping + +OAuth2Mapping + +id :integer +o_auth2_provider_id :integer +local_field :character varying +api_field :character varying +created_at :timestamp without time zone +updated_at :timestamp without time zone +local_model :character varying +api_endpoint :character varying +api_data_type :character varying + + +Licence + +Licence + +id :integer +name :character varying(255) +description :text + + +Licence->Project + + + + + +User->Project + + + +my_projects + + +User->Invoice + + + + + +User->Credit + + + + + +Role + +Role + +id :integer +name :character varying(255) +resource_id :integer +resource_type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +User->Role + + + + + +Notification + +Notification + +id :integer +receiver_id :integer +attached_object_id :integer +attached_object_type :character varying(255) +notification_type_id :integer +is_read :boolean +created_at :timestamp without time zone +updated_at :timestamp without time zone +receiver_type :character varying(255) +is_send :boolean +meta_data :jsonb + + +User->Notification + + + + + +User->Subscription + + + + + +Training + +Training + +id :integer +name :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone +nb_total_places :integer +slug :character varying(255) +description :text + + +User->Training + + + + + +Reservation + +Reservation + +id :integer +user_id :integer +message :text +created_at :timestamp without time zone +updated_at :timestamp without time zone +reservable_id :integer +reservable_type :character varying(255) +stp_invoice_id :character varying(255) +nb_reserve_places :integer +nb_reserve_reduced_places :integer + + +User->Reservation + + + + + +UsersCredit + +UsersCredit + +id :integer +user_id :integer +credit_id :integer +hours_used :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +User->UsersCredit + + + + + +User->UserTag + + + + + +UserTraining + +UserTraining + +id :integer +user_id :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone +training_id :integer + + +User->UserTraining + + + + + +User->ProjectUser + + + + + +Profile + +Profile + +id :integer +user_id :integer +first_name :character varying(255) +last_name :character varying(255) +gender :boolean +birthday :date +phone :character varying(255) +interest :text +software_mastered :text +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +User->Profile + + + + + +ProjectStepImage + +ProjectStepImage + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Credit->UsersCredit + + + + + +PartnerPlan + +PartnerPlan + +id :integer +name :character varying(255) +amount :integer +interval :character varying(255) +group_id :integer +stp_plan_id :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone +training_credit_nb :integer +is_rolling :boolean +description :text +type :character varying +base_name :character varying +ui_weight :integer +interval_count :integer + + +PartnerPlan->Price + + + + + +PartnerPlan->PlanFile + + + + + +PartnerPlan->Credit + + + + + +PartnerPlan->Credit + + + +training_credits + + +PartnerPlan->Credit + + + +machine_credits + + +PartnerPlan->Role + + + + + +PartnerPlan->Subscription + + + + + +PartnerPlan->PlanImage + + + + + +OAuth2Provider + +OAuth2Provider + +id :integer +base_url :character varying +token_endpoint :character varying +authorization_endpoint :character varying +client_id :character varying +client_secret :character varying +created_at :timestamp without time zone +updated_at :timestamp without time zone +profile_url :character varying + + +OAuth2Provider->OAuth2Mapping + + + + + +AuthProvider + +AuthProvider + +id :integer +name :character varying +status :character varying +created_at :timestamp without time zone +updated_at :timestamp without time zone +providable_type :character varying +providable_id :integer + + +OAuth2Provider->AuthProvider + + + + + +Asset + +Asset + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +DatabaseProvider + +DatabaseProvider + +id :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +DatabaseProvider->AuthProvider + + + + + +Availability->AvailabilityTag + + + + + +Availability->Event + + + + + +Availability->Training + + + + + +Availability->Reservation + + + + + +Slot + +Slot + +id :integer +start_at :timestamp without time zone +end_at :timestamp without time zone +reservation_id :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone +availability_id :integer +ex_start_at :timestamp without time zone +canceled_at :timestamp without time zone +ex_end_at :timestamp without time zone +offered :boolean + + +Availability->Slot + + + + + +MachinesAvailability + +MachinesAvailability + +id :integer +machine_id :integer +availability_id :integer + + +Availability->MachinesAvailability + + + + + +TrainingsAvailability + +TrainingsAvailability + +id :integer +training_id :integer +availability_id :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Availability->TrainingsAvailability + + + + + +Availability->Machine + + + + + +Subscription->Invoice + + + + + +Subscription->OfferDay + + + + + +Training->TrainingsPricing + + + + + +Training->Plan + + + + + +Training->Credit + + + + + +Training->Reservation + + + + + +Training->UserTraining + + + + + +Training->TrainingsAvailability + + + + + +Training->Machine + + + + + +ProjectStep->ProjectStepImage + + + + + +Reservation->Invoice + + + + + +Reservation->Slot + + + + + +StatisticIndex->StatisticType + + + + + +StatisticIndex->StatisticGraph + + + + + +StatisticField + +StatisticField + +id :integer +statistic_index_id :integer +key :character varying(255) +label :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone +data_type :character varying(255) + + +StatisticIndex->StatisticField + + + + + +StatisticField->StatisticIndex + + + + + +Avoir + +Avoir + +id :integer +invoiced_id :integer +invoiced_type :character varying(255) +stp_invoice_id :character varying(255) +total :integer +created_at :timestamp without time zone +updated_at :timestamp without time zone +user_id :integer +reference :character varying(255) +avoir_mode :character varying(255) +avoir_date :timestamp without time zone +invoice_id :integer +type :character varying(255) +subscription_to_expire :boolean +description :text + + +Avoir->InvoiceItem + + + + + +Avoir->Invoice + + + +avoir + + +NotificationType + +NotificationType + + + + +Stylesheet + +Stylesheet + +id :integer +contents :text +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +MachineImage + +MachineImage + +id :integer +viewable_id :integer +viewable_type :character varying(255) +attachment :character varying(255) +type :character varying(255) +created_at :timestamp without time zone +updated_at :timestamp without time zone + + +Feed + +Feed + + + + +Machine->Price + + + + + +Machine->Plan + + + + + +Machine->MachineFile + + + + + +Machine->Credit + + + + + +Machine->Reservation + + + + + +Machine->MachinesAvailability + + + + + +Machine->MachineImage + + + + + +Profile->UserAvatar + + + + + +Profile->Address + + + + + +Setting + +Setting + +id :integer +name :character varying +value :text +created_at :timestamp without time zone +updated_at :timestamp without time zone + + + diff --git a/doc/sso_authentication.md b/doc/sso_authentication.md new file mode 100644 index 000000000..af0c9d800 --- /dev/null +++ b/doc/sso_authentication.md @@ -0,0 +1,130 @@ +# How to add an authentication method to the FabLab ? + +First, take a look at the [OmniAuth list of strategies](https://github.com/intridea/omniauth/wiki/List-of-Strategies) + for the Strategy or Developer Strategy you want to add to the FabLab. + +For this guide, we will consider you want to add a generic *developer strategy*, like LDAP. + +Create the OmniAuth implementation ( **lib/omni_auth/strategies/ldap_provider.rb** ) + + # first require the OmniAuth gem you added to your Gemfile (see the link above for a list of gems) + require 'omniauth-ldap' + module OmniAuth + module Strategies + # in the class name, replace Ldap with the kind of authentication you are implementing + class SsoLdapProvider < OmniAuth::Strategies::LDAP + # implement the logic here, see the gem specific documentation for more details + end + end + end + +Create the ActiveRecord models ( **from the terminal** ) + + # in the models names, replace Ldap with the kind of authentication you are implementing + # replace ldap_fields with the fields you need for implementing LDAP or whatever you are implementing + $ rails g model LdapProvider ...ldap_fields + $ rails g model LdapMapping ldap_provider:belongs_to local_field:string local_model:string ...ldap_fields + +Complete the Provider Model ( **app/model/ldap_provider.rb** ) + + class LdapProvider < ActiveRecord::Base + has_one :auth_provider, as: :providable + has_many :ldap_mappings, dependent: :destroy + accepts_nested_attributes_for :ldap_mappings, allow_destroy: true + + # return here the fields you want to protect from being directly on the FabLab, typically mapped fields + def protected_fields + fields = [] + ldap_mappings.each do |mapping| + fields.push(mapping.local_model+'.'+mapping.local_field) + end + fields + end + + # return here the link the current users will have to follow to edit his profile on the SSO + def profile_url + # you can also create a profile_url field in the Database model + end + end + +Whitelist your implementation fields in the controller ( **app/controllers/api/auth_providers_controller.rb** ) + + class API::AuthProvidersController < API::ApiController + ... + private + def provider_params + if params['auth_provider']['providable_type'] == DatabaseProvider.name + ... + elsif if params['auth_provider']['providable_type'] == LdapProvider.name + params.require(:auth_provider).permit(:name, :providable_type, providable_attributes: [ + # list here your LdapProvider model's fields, followed by the mappings : + ldap_mappings_attributes: [ + :id, :local_model, :local_field, ... + # add your other customs LdapMapping fields, don't forget the :_destroy symbol if + # you want your admin to be able to remove mappings + ] + ]) + end + end + end + +List the fields to display in the JSON API view ( **app/views/api/auth_providers/show.json.jbuilder** ) + + json.partial! 'api/auth_providers/auth_provider', auth_provider: @provider + + ... + + if @provider.providable_type == LdapProvider.name + json.providable_attributes do + json.extract! @provider.providable, :id, ... # list LdapProvider fields here + json.ldap_mappings_attributes @provider.providable.ldap_mappings do |m| + json.extract! m, :id, :local_model, :local_field, ... # list LdapMapping fields here + end + end + end + +Configure the initializer ( **config/initializers/devise.rb** ) + + require_relative '../../lib/omni_auth/omni_auth' + ... + elsif active_provider.providable_type == LdapProvider.name + config.omniauth OmniAuth::Strategies::SsoLdapProvider.name.to_sym, # pass here the required parameters, see the gem documentation for details + end + +Finally you have to create an admin interface with AngularJS: + +- **app/assets/templates/admin/authentifications/_ldap.html.erb** must contains html input fields (partial html form) for the LdapProvider fields configuration +- **app/assets/templates/admin/authentifications/_ldap_mapping.html.erb** must contains html partial to configure the LdapMappings, see _oauth2_mapping.html.erb for a working example +- **app/assets/javascript/controllers/admin/authentifications.coffee** + + + ## list of supported authentication methods + METHODS = { + ... + 'LdapProvider' : 'LDAP' # add the name of your ActiveRecord model class here as a hash key, associated with a human readable name as a hash value (string) + } + + Application.Controllers.controller "newAuthentificationController", ... + + $scope.updateProvidable = -> + ... + if $scope.provider.providable_type == 'LdapProvider' + # you may want to do some stuff to initialize your provider here + + $scope.registerProvider = -> + ... + # === LdapProvider === + else if $scope.provider.providable_type == 'LdapProvider' + # here you may want to do some data validation + # then: save the settings + AuthProvider.save auth_provider: $scope.provider, (provider) -> + # register was a success, display a message, redirect, etc. + +And to include this interface into the existing one ( **app/assets/templates/admin/authentifications/edit.html.erb**) + +

    + ... + + + + \ No newline at end of file diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..7d9ae31e8 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,195 @@ + +## Docker + +Docker is an application deployment software. + +## PREPARE HOST COREOS +Install VPS WITH Version coreOS STABLE (Ex : on DigitalOcean) + +### Creating Swap File in CoreOS + +Firstly, switch to sudo and create swap file +```bash +sudo -i +touch /2GiB.swap +chattr +C /2GiB.swap +fallocate -l 2048m /2GiB.swap +chmod 600 /2GiB.swap +mkswap /2GiB.swap +``` + +Create file /etc/systemd/system/swap.service with +```bash +[Unit] +Description=Turn on swap +[Service] +Type=oneshot +Environment="SWAPFILE=/2GiB.swap" +RemainAfterExit=true +ExecStartPre=/usr/sbin/losetup -f ${SWAPFILE} +ExecStart=/usr/bin/sh -c "/sbin/swapon $(/usr/sbin/losetup -j ${SWAPFILE} | /usr/bin/cut -d : -f 1)" +ExecStop=/usr/bin/sh -c "/sbin/swapoff $(/usr/sbin/losetup -j ${SWAPFILE} | /usr/bin/cut -d : -f 1)" +ExecStopPost=/usr/bin/sh -c "/usr/sbin/losetup -d $(/usr/sbin/losetup -j ${SWAPFILE} | /usr/bin/cut -d : -f 1)" +[Install] +WantedBy=multi-user.target +``` + +Then add service and start: +```bash +systemctl enable /etc/systemd/system/swap.service +systemctl start swap +exit +``` + +## PREPARE FOLDERS AND ENV CONFIG ON HOST + +mkdir -p /home/core/fabmanager/config +MOVE docker/env.example to /home/core/fabmanager/config/env +CUSTOM ENV +mkdir -p /home/core/fabmanager/config/nginx +MOVE docker/nginx.conf.example to /home/core/fabmanager/config/nginx/fabmanager.conf +CUSTOM fabmanager.conf + +IF SSL +mkdir -p /home/core/fabmanager/config/nginx/ssl +Move your crt and deprotected key +MOVE docker/nginx_with_ssl.conf.example to /home/core/fabmanager/config/nginx/fabmanager.conf +CUSTOM fabmanager.conf + + +## DEPLOY DOCKERS CONTAINERS ON HOST + + +```bash +docker pull redis:3.0 +docker pull postgres:9.4 +docker pull elasticsearch:1.7 +docker pull sleede/fabmanager + +docker run --restart=always -d --name=fabmanager-postgres -v /home/core/fabmanager/postgresql:/var/lib/postgresql/data postgres:9.4 +docker run --restart=always -d --name=fabmanager-redis -v /home/core/fabmanager/redis:/data redis:3.0 +docker run --restart=always -d --name=fabmanager-elastic -v /home/core/fabmanager/elasticsearch:/usr/share/elasticsearch/data elasticsearch:1.7 +``` + +### DB CREATE + +```bash +docker run --rm \ + --link=fabmanager-postgres:postgres \ + --link=fabmanager-redis:redis \ + --link=fabmanager-elastic:elasticsearch \ + -e RAILS_ENV=production \ + --env-file /home/core/fabmanager/config/env \ + sleede/fabmanager \ + bundle exec rake db:create +``` + +### DB MIGRATE + +```bash +docker run --rm \ + --link=fabmanager-postgres:postgres \ + --link=fabmanager-redis:redis \ + --link=fabmanager-elastic:elasticsearch \ + -e RAILS_ENV=production \ + --env-file /home/core/fabmanager/config/env \ + sleede/fabmanager \ + bundle exec rake db:migrate +``` + +### DB SEED + +```bash +docker run --rm \ + --link=fabmanager-postgres:postgres \ + --link=fabmanager-redis:redis \ + --link=fabmanager-elastic:elasticsearch \ + -e RAILS_ENV=production \ + --env-file /home/core/fabmanager/config/env \ + sleede/fabmanager \ + bundle exec rake db:seed +``` + + +### PREPARE ELASTIC + +```bash +docker run --rm \ + --link=fabmanager-postgres:postgres \ + --link=fabmanager-postgres:postgres \ + --link=fabmanager-redis:redis \ + --link=fabmanager-elastic:elasticsearch \ + -e RAILS_ENV=production \ + --env-file /home/core/fabmanager/config/env \ + sleede/fabmanager \ + bundle exec rake fablab:es_build_stats +``` + + +### recreate every versions of images + +```bash +docker run --rm \ + --link=fabmanager-postgres:postgres \ + --link=fabmanager-redis:redis \ + --link=fabmanager-elastic:elasticsearch \ + -e RAILS_ENV=production \ + --env-file /home/core/fabmanager/config/env \ + -v /home/core/fabmanager/public/uploads:/usr/src/app/public/uploads \ + sleede/fabmanager \ + bundle exec rake fablab:build_images_versions +``` + + +### BUILD ASSETS + +```bash +docker run --rm \ + --link=fabmanager-postgres:postgres \ + --link=fabmanager-redis:redis \ + --link=fabmanager-elastic:elasticsearch \ + -e RAILS_ENV=production \ + --env-file /home/core/fabmanager/config/env \ + -v /home/core/fabmanager/public/assets:/usr/src/app/public/assets \ + sleede/fabmanager \ + bundle exec rake assets:precompile + +docker run --rm -v /home/core/fabmanager/public/assets:/usr/src/app/public/assets sleede/fabmanager cp vendor/assets/components/select2/select2.png public/assets/select2.png +docker run --rm -v /home/core/fabmanager/public/assets:/usr/src/app/public/assets sleede/fabmanager cp vendor/assets/components/select2/select2x2.png public/assets/select2x2.png +docker run --rm -v /home/core/fabmanager/public/assets:/usr/src/app/public/assets sleede/fabmanager cp vendor/assets/components/select2/select2-spinner.gif public/assets/select2-spinner.gif +``` + + +### RUN APP + +```bash +docker run --restart=always -d --name=fabmanager \ + -p 80:80 \ + -p 443:443 \ + --link=fabmanager-postgres:postgres \ + --link=fabmanager-redis:redis \ + --link=fabmanager-elastic:elasticsearch \ + -e RAILS_ENV=production \ + -e RACK_ENV=production \ + --env-file /home/core/fabmanager/config/env \ + -v /home/core/fabmanager/config/nginx:/etc/nginx/conf.d \ + -v /home/core/fabmanager/public/assets:/usr/src/app/public/assets \ + -v /home/core/fabmanager/public/uploads:/usr/src/app/public/uploads \ + -v /home/core/fabmanager/invoices:/usr/src/app/invoices \ + -v /home/core/fabmanager/log:/var/log/supervisor \ + sleede/fabmanager +``` + + +### for debug + +```bash +docker run --rm -it \ + --link=fabmanager-postgres:postgres \ + --link=fabmanager-redis:redis \ + --link=fabmanager-elastic:elasticsearch \ + -e RAILS_ENV=production \ + --env-file /home/core/fabmanager/config/env \ + sleede/fabmanager \ + bash +``` diff --git a/docker/database.yml b/docker/database.yml new file mode 100644 index 000000000..4feff4ea1 --- /dev/null +++ b/docker/database.yml @@ -0,0 +1,27 @@ +default: &default + adapter: postgresql + encoding: unicode + pool: 25 + username: postgres + password: <%= ENV["POSTGRES_PASSWORD"] %> + host: <%= ENV["POSTGRES_HOST"] %> + +development: + <<: *default + database: fablab_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: fablab_test + +staging: + <<: *default + database: fablab_staging + + +production: + <<: *default + database: fablab_production diff --git a/docker/env.example b/docker/env.example new file mode 100644 index 000000000..7889c44ed --- /dev/null +++ b/docker/env.example @@ -0,0 +1,45 @@ +POSTGRES_HOST=postgres +REDIS_HOST=redis +ELASTICSEARCH_HOST=elasticsearch + +SECRET_KEY_BASE= + +STRIPE_API_KEY= +STRIPE_PUBLISHABLE_KEY= +STRIPE_CURRENCY= + +INVOICE_PREFIX=Demo-FabLab-facture +FABLAB_WITHOUT_PLANS=false + +DEFAULT_MAIL_FROM=Fab Manager Demo +DEFAULT_HOST=demo.fab-manager.com +DEFAULT_PROTOCOL=http + +DELIVERY_METHOD=smtp +SMTP_ADDRESS=smtp.sparkpostmail.com +SMTP_PORT=587 +SMTP_USER_NAME=SMTP_Injection +SMTP_PASSWORD= + +GA_ID= + +DISQUS_SHORTNAME= + +TWITTER_NAME=FablabGrenoble +TWITTER_CONSUMER_KEY= +TWITTER_CONSUMER_SECRET= +TWITTER_ACCESS_TOKEN= +TWITTER_ACCESS_TOKEN_SECRET= + +RAILS_LOCALE=fr +MOMENT_LOCALE=fr +SUMMERNOTE_LOCALE=fr-FR +ANGULAR_LOCALE=fr-fr +MESSAGEFORMAT_LOCALE=fr +FULLCALENDAR_LOCALE=fr + +ELASTICSEARCH_LANGUAGE_ANALYZER=french + +TIME_ZONE=Paris +WEEK_STARTING_DAY=monday +D3_DATE_FORMAT=%d/%m/%y \ No newline at end of file diff --git a/docker/nginx.conf.example b/docker/nginx.conf.example new file mode 100644 index 000000000..bca995d9e --- /dev/null +++ b/docker/nginx.conf.example @@ -0,0 +1,49 @@ +upstream puma { + server unix:/usr/src/app/tmp/sockets/fabmanager.sock fail_timeout=0; +} + +server { + listen 80; + server_name demo.fab-manager.com; + root /usr/src/app/public; + + location ^~ /assets/ { + gzip_static on; + expires max; + add_header Cache-Control public; + } + + try_files $uri/index.html $uri @puma; + location @puma { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://puma; + } + + client_max_body_size 4G; + keepalive_timeout 10; + + error_page 500 502 504 /500.html; + error_page 503 @503; + + # Return a 503 error if the maintenance page exists. + if (-f /usr/src/app/public/maintenance.html) { + return 503; + } + + location @503 { + # Serve static assets if found. + if (-f $request_filename) { + break; + } + + # Set root to the shared directory. + root /usr/src/app/public/; + rewrite ^(.*)$ /maintenance.html break; + } + + # no spam bot + if ($http_referer ~* (guardlink.org|free-share-buttons|social-buttons|buy-cheap-online.info|social-buttons.com|free-share-buttons.com|darodar.com|blackhatworth.com|hulfingtonpost.com|priceg.com|semalt.com|imaspammer.com|iedit.ilovevitaly.com|7makemoneyonline.com|iedit.ilovevitaly.com|7makemoneyonline.com|gamersyde.com|iloveitaly.com|econom.co|semalt.com|forum.topic44637676.darodar.com|darodar.com|iskalko.ru|ilovevitaly.ru|ilovevitaly.com|ilovevitaly.co|o-o-8-o-o.ru|o-o-6-o-o.ru|buttons-for-website.com|semalt.semalt.com|cenoval.ru|priceg.com|darodar.com|cenokos.ru|seoexperimenty.ru|gobongo.info|vodkoved.ru|adcash.com|websocial.me|cityadspix.com|luxup.ru|ykecwqlixx.ru|superiends.org|slftsdybbg.ru|edakgfvwql.ru|socialseet.ru|screentoolkit.com|econom.co|semalt.com|savetubevideo.com|shopping.ilovevitaly.com|iedit.ilovevitaly.com|forum.topic52548358.darodar.com|forum.topic53813291.darodar.com|share-buttons.com|event-tracking.com|success-seo.com|free-floating-buttons.com|get-free-social-traffic.com|chinese-amezon.com|get-free-traffic-now.com|free-social-buttons.com|videos-for-your-business.com)) { return 403; } + +} diff --git a/docker/nginx_with_ssl.conf.example b/docker/nginx_with_ssl.conf.example new file mode 100644 index 000000000..a71e9e35f --- /dev/null +++ b/docker/nginx_with_ssl.conf.example @@ -0,0 +1,63 @@ +upstream puma { + server unix:/usr/src/app/tmp/sockets/fabmanager.sock fail_timeout=0; +} + +server { + listen 443 ssl; + server_name demo.fab-manager.com; + root /usr/src/app/public; + ssl on; + ssl_certificate /etc/nginx/conf.d/ssl/fab-manager.crt; + ssl_certificate_key /etc/nginx/conf.d/ssl/fab-manager.deprotected.key; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + + location ^~ /assets/ { + gzip_static on; + expires max; + add_header Cache-Control public; + } + + try_files $uri/index.html $uri @puma; + location @puma { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://puma; + } + + client_max_body_size 4G; + keepalive_timeout 10; + + error_page 500 502 504 /500.html; + error_page 503 @503; + + # Return a 503 error if the maintenance page exists. + if (-f /usr/src/app/public/maintenance.html) { + return 503; + } + + location @503 { + # Serve static assets if found. + if (-f $request_filename) { + break; + } + + # Set root to the shared directory. + root /usr/src/app/public/; + rewrite ^(.*)$ /maintenance.html break; + } + + # no spam bot + if ($http_referer ~* (guardlink.org|free-share-buttons|social-buttons|buy-cheap-online.info|social-buttons.com|free-share-buttons.com|darodar.com|blackhatworth.com|hulfingtonpost.com|priceg.com|semalt.com|imaspammer.com|iedit.ilovevitaly.com|7makemoneyonline.com|iedit.ilovevitaly.com|7makemoneyonline.com|gamersyde.com|iloveitaly.com|econom.co|semalt.com|forum.topic44637676.darodar.com|darodar.com|iskalko.ru|ilovevitaly.ru|ilovevitaly.com|ilovevitaly.co|o-o-8-o-o.ru|o-o-6-o-o.ru|buttons-for-website.com|semalt.semalt.com|cenoval.ru|priceg.com|darodar.com|cenokos.ru|seoexperimenty.ru|gobongo.info|vodkoved.ru|adcash.com|websocial.me|cityadspix.com|luxup.ru|ykecwqlixx.ru|superiends.org|slftsdybbg.ru|edakgfvwql.ru|socialseet.ru|screentoolkit.com|econom.co|semalt.com|savetubevideo.com|shopping.ilovevitaly.com|iedit.ilovevitaly.com|forum.topic52548358.darodar.com|forum.topic53813291.darodar.com|share-buttons.com|event-tracking.com|success-seo.com|free-floating-buttons.com|get-free-social-traffic.com|chinese-amezon.com|get-free-traffic-now.com|free-social-buttons.com|videos-for-your-business.com)) { return 403; } + +} + + +server { + listen 80; + server_name demo.fab-manager.com; + rewrite ^ https://demo.fab-manager.com$request_uri? permanent; +} diff --git a/docker/supervisor.conf b/docker/supervisor.conf new file mode 100644 index 000000000..c722ea67e --- /dev/null +++ b/docker/supervisor.conf @@ -0,0 +1,30 @@ +[unix_http_server] +file=/var/run/supervisor.sock ; path to your socket file + +[supervisord] +nodaemon=true ; dont run supervisord as a daemon +logfile=/var/log/supervisor/supervisord.log ; supervisord log file +logfile_maxbytes=10MB ; maximum size of logfile before rotation +logfile_backups=10 ; number of backed up logfiles +loglevel=info ; info, debug, warn, trace +pidfile=/var/run/supervisord.pid ; pidfile location +user=root ; default user +childlogdir=/var/log/supervisor/ ; where child log files will live + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket + +[program:nginx] +command=nginx -g "daemon off;" +stderr_logfile=/var/log/supervisor/nginx-stderr.log +stdout_logfile=/var/log/supervisor/nginx-stdout.log + +[program:app] +command=bundle exec puma -b unix:///usr/src/app/tmp/sockets/fabmanager.sock --pidfile /usr/src/app/tmp/pids/fabmanager.pid +stderr_logfile=/var/log/supervisor/app-stderr.log +stdout_logfile=/var/log/supervisor/app-stdout.log + +[program:worker] +command=bundle exec sidekiq -C /usr/src/app/config/sidekiq.yml +stderr_logfile=/var/log/supervisor/worker-stderr.log +stdout_logfile=/var/log/supervisor/worker-stdout.log diff --git a/lib/assets/javascripts/fullcalendar/fullcalendar.js b/lib/assets/javascripts/fullcalendar/fullcalendar.js new file mode 100644 index 000000000..a4c60d652 --- /dev/null +++ b/lib/assets/javascripts/fullcalendar/fullcalendar.js @@ -0,0 +1,6112 @@ +/*! + * FullCalendar v1.6.4 + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ + +/* + * Use fullcalendar.css for basic styling. + * For event drag & drop, requires jQuery UI draggable. + * For event resizing, requires jQuery UI resizable. + */ + +(function($, undefined) { + + +;; + +var defaults = { + + // display + defaultView: 'month', + aspectRatio: 1.35, + header: { + left: 'title', + center: '', + right: 'today prev,next' + }, + weekends: true, + weekNumbers: false, + weekNumberCalculation: 'iso', + weekNumberTitle: 'W', + + // editing + //editable: false, + //disableDragging: false, + //disableResizing: false, + + allDayDefault: true, + ignoreTimezone: true, + + // event ajax + lazyFetching: true, + startParam: 'start', + endParam: 'end', + + // time formats + titleFormat: { + month: 'MMMM yyyy', + week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}", + day: 'dddd, MMM d, yyyy' + }, + columnFormat: { + month: 'ddd', + week: 'ddd M/d', + day: 'dddd M/d' + }, + timeFormat: { // for event elements + '': 'h(:mm)t' // default + }, + + // locale + isRTL: false, + firstDay: 0, + monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'], + monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'], + dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], + dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'], + buttonText: { + prev: "", + next: "", + prevYear: "«", + nextYear: "»", + today: 'today', + month: 'month', + week: 'week', + day: 'day' + }, + + // jquery-ui theming + theme: false, + buttonIcons: { + prev: 'circle-triangle-w', + next: 'circle-triangle-e' + }, + + //selectable: false, + unselectAuto: true, + + dropAccept: '*', + + handleWindowResize: true + +}; + +// right-to-left defaults +var rtlDefaults = { + header: { + left: 'next,prev today', + center: '', + right: 'title' + }, + buttonText: { + prev: "", + next: "", + prevYear: "»", + nextYear: "«" + }, + buttonIcons: { + prev: 'circle-triangle-e', + next: 'circle-triangle-w' + } +}; + + + +;; + +var fc = $.fullCalendar = { version: "1.6.4" }; +var fcViews = fc.views = {}; + + +$.fn.fullCalendar = function(options) { + + + // method calling + if (typeof options == 'string') { + var args = Array.prototype.slice.call(arguments, 1); + var res; + this.each(function() { + var calendar = $.data(this, 'fullCalendar'); + if (calendar && $.isFunction(calendar[options])) { + var r = calendar[options].apply(calendar, args); + if (res === undefined) { + res = r; + } + if (options == 'destroy') { + $.removeData(this, 'fullCalendar'); + } + } + }); + if (res !== undefined) { + return res; + } + return this; + } + + options = options || {}; + + // would like to have this logic in EventManager, but needs to happen before options are recursively extended + var eventSources = options.eventSources || []; + delete options.eventSources; + if (options.events) { + eventSources.push(options.events); + delete options.events; + } + + + options = $.extend(true, {}, + defaults, + (options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {}, + options + ); + + + this.each(function(i, _element) { + var element = $(_element); + var calendar = new Calendar(element, options, eventSources); + element.data('fullCalendar', calendar); // TODO: look into memory leak implications + calendar.render(); + }); + + + return this; + +}; + + +// function for adding/overriding defaults +function setDefaults(d) { + $.extend(true, defaults, d); +} + + + +;; + + +function Calendar(element, options, eventSources) { + var t = this; + + + // exports + t.options = options; + t.render = render; + t.destroy = destroy; + t.refetchEvents = refetchEvents; + t.reportEvents = reportEvents; + t.reportEventChange = reportEventChange; + t.rerenderEvents = rerenderEvents; + t.changeView = changeView; + t.select = select; + t.unselect = unselect; + t.prev = prev; + t.next = next; + t.prevYear = prevYear; + t.nextYear = nextYear; + t.today = today; + t.gotoDate = gotoDate; + t.incrementDate = incrementDate; + t.formatDate = function(format, date) { return formatDate(format, date, options) }; + t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) }; + t.getDate = getDate; + t.getView = getView; + t.option = option; + t.trigger = trigger; + + + // imports + EventManager.call(t, options, eventSources); + var isFetchNeeded = t.isFetchNeeded; + var fetchEvents = t.fetchEvents; + + + // locals + var _element = element[0]; + var header; + var headerElement; + var content; + var tm; // for making theme classes + var currentView; + var elementOuterWidth; + var suggestedViewHeight; + var resizeUID = 0; + var ignoreWindowResize = 0; + var date = new Date(); + var events = []; + var _dragElement; + + + + /* Main Rendering + -----------------------------------------------------------------------------*/ + + + setYMD(date, options.year, options.month, options.date); + + + function render(inc) { + if (!content) { + initialRender(); + } + else if (elementVisible()) { + // mainly for the public API + calcSize(); + _renderView(inc); + } + } + + + function initialRender() { + tm = options.theme ? 'ui' : 'fc'; + element.addClass('fc'); + if (options.isRTL) { + element.addClass('fc-rtl'); + } + else { + element.addClass('fc-ltr'); + } + if (options.theme) { + element.addClass('ui-widget'); + } + + content = $("
    ") + .prependTo(element); + + header = new Header(t, options); + headerElement = header.render(); + if (headerElement) { + element.prepend(headerElement); + } + + changeView(options.defaultView); + + if (options.handleWindowResize) { + $(window).resize(windowResize); + } + + // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize + if (!bodyVisible()) { + lateRender(); + } + } + + + // called when we know the calendar couldn't be rendered when it was initialized, + // but we think it's ready now + function lateRender() { + setTimeout(function() { // IE7 needs this so dimensions are calculated correctly + if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once + renderView(); + } + },0); + } + + + function destroy() { + + if (currentView) { + trigger('viewDestroy', currentView, currentView, currentView.element); + currentView.triggerEventDestroy(); + } + + $(window).unbind('resize', windowResize); + + header.destroy(); + content.remove(); + element.removeClass('fc fc-rtl ui-widget'); + } + + + function elementVisible() { + return element.is(':visible'); + } + + + function bodyVisible() { + return $('body').is(':visible'); + } + + + + /* View Rendering + -----------------------------------------------------------------------------*/ + + + function changeView(newViewName) { + if (!currentView || newViewName != currentView.name) { + _changeView(newViewName); + } + } + + + function _changeView(newViewName) { + ignoreWindowResize++; + + if (currentView) { + trigger('viewDestroy', currentView, currentView, currentView.element); + unselect(); + currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event + freezeContentHeight(); + currentView.element.remove(); + header.deactivateButton(currentView.name); + } + + header.activateButton(newViewName); + + currentView = new fcViews[newViewName]( + $("
    ") + .appendTo(content), + t // the calendar object + ); + + renderView(); + unfreezeContentHeight(); + + ignoreWindowResize--; + } + + + function renderView(inc) { + if ( + !currentView.start || // never rendered before + inc || date < currentView.start || date >= currentView.end // or new date range + ) { + if (elementVisible()) { + _renderView(inc); + } + } + } + + + function _renderView(inc) { // assumes elementVisible + ignoreWindowResize++; + + if (currentView.start) { // already been rendered? + trigger('viewDestroy', currentView, currentView, currentView.element); + unselect(); + clearEvents(); + } + + freezeContentHeight(); + currentView.render(date, inc || 0); // the view's render method ONLY renders the skeleton, nothing else + setSize(); + unfreezeContentHeight(); + (currentView.afterRender || noop)(); + + updateTitle(); + updateTodayButton(); + + trigger('viewRender', currentView, currentView, currentView.element); + currentView.trigger('viewDisplay', _element); // deprecated + + ignoreWindowResize--; + + getAndRenderEvents(); + } + + + + /* Resizing + -----------------------------------------------------------------------------*/ + + + function updateSize() { + if (elementVisible()) { + unselect(); + clearEvents(); + calcSize(); + setSize(); + renderEvents(); + } + } + + + function calcSize() { // assumes elementVisible + if (options.contentHeight) { + suggestedViewHeight = options.contentHeight; + } + else if (options.height) { + suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content); + } + else { + suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5)); + } + } + + + function setSize() { // assumes elementVisible + + if (suggestedViewHeight === undefined) { + calcSize(); // for first time + // NOTE: we don't want to recalculate on every renderView because + // it could result in oscillating heights due to scrollbars. + } + + ignoreWindowResize++; + currentView.setHeight(suggestedViewHeight); + currentView.setWidth(content.width()); + ignoreWindowResize--; + + elementOuterWidth = element.outerWidth(); + } + + + function windowResize() { + if (!ignoreWindowResize) { + if (currentView.start) { // view has already been rendered + var uid = ++resizeUID; + setTimeout(function() { // add a delay + if (uid == resizeUID && !ignoreWindowResize && elementVisible()) { + if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) { + ignoreWindowResize++; // in case the windowResize callback changes the height + updateSize(); + currentView.trigger('windowResize', _element); + ignoreWindowResize--; + } + } + }, 200); + }else{ + // calendar must have been initialized in a 0x0 iframe that has just been resized + lateRender(); + } + } + } + + + + /* Event Fetching/Rendering + -----------------------------------------------------------------------------*/ + // TODO: going forward, most of this stuff should be directly handled by the view + + + function refetchEvents() { // can be called as an API method + clearEvents(); + fetchAndRenderEvents(); + } + + + function rerenderEvents(modifiedEventID) { // can be called as an API method + clearEvents(); + renderEvents(modifiedEventID); + } + + + function renderEvents(modifiedEventID) { // TODO: remove modifiedEventID hack + if (elementVisible()) { + currentView.setEventData(events); // for View.js, TODO: unify with renderEvents + currentView.renderEvents(events, modifiedEventID); // actually render the DOM elements + currentView.trigger('eventAfterAllRender'); + } + } + + + function clearEvents() { + currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event + currentView.clearEvents(); // actually remove the DOM elements + currentView.clearEventData(); // for View.js, TODO: unify with clearEvents + } + + + function getAndRenderEvents() { + if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) { + fetchAndRenderEvents(); + } + else { + renderEvents(); + } + } + + + function fetchAndRenderEvents() { + fetchEvents(currentView.visStart, currentView.visEnd); + // ... will call reportEvents + // ... which will call renderEvents + } + + + // called when event data arrives + function reportEvents(_events) { + events = _events; + renderEvents(); + } + + + // called when a single event's data has been changed + function reportEventChange(eventID) { + rerenderEvents(eventID); + } + + + + /* Header Updating + -----------------------------------------------------------------------------*/ + + + function updateTitle() { + header.updateTitle(currentView.title); + } + + + function updateTodayButton() { + var today = new Date(); + if (today >= currentView.start && today < currentView.end) { + header.disableButton('today'); + } + else { + header.enableButton('today'); + } + } + + + + /* Selection + -----------------------------------------------------------------------------*/ + + + function select(start, end, allDay) { + currentView.select(start, end, allDay===undefined ? true : allDay); + } + + + function unselect() { // safe to be called before renderView + if (currentView) { + currentView.unselect(); + } + } + + + + /* Date + -----------------------------------------------------------------------------*/ + + + function prev() { + renderView(-1); + } + + + function next() { + renderView(1); + } + + + function prevYear() { + addYears(date, -1); + renderView(); + } + + + function nextYear() { + addYears(date, 1); + renderView(); + } + + + function today() { + date = new Date(); + renderView(); + } + + + function gotoDate(year, month, dateOfMonth) { + if (year instanceof Date) { + date = cloneDate(year); // provided 1 argument, a Date + }else{ + setYMD(date, year, month, dateOfMonth); + } + renderView(); + } + + + function incrementDate(years, months, days) { + if (years !== undefined) { + addYears(date, years); + } + if (months !== undefined) { + addMonths(date, months); + } + if (days !== undefined) { + addDays(date, days); + } + renderView(); + } + + + function getDate() { + return cloneDate(date); + } + + + + /* Height "Freezing" + -----------------------------------------------------------------------------*/ + + + function freezeContentHeight() { + content.css({ + width: '100%', + height: content.height(), + overflow: 'hidden' + }); + } + + + function unfreezeContentHeight() { + content.css({ + width: '', + height: '', + overflow: '' + }); + } + + + + /* Misc + -----------------------------------------------------------------------------*/ + + + function getView() { + return currentView; + } + + + function option(name, value) { + if (value === undefined) { + return options[name]; + } + if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') { + options[name] = value; + updateSize(); + } + } + + + function trigger(name, thisObj) { + if (options[name]) { + return options[name].apply( + thisObj || _element, + Array.prototype.slice.call(arguments, 2) + ); + } + } + + + + /* External Dragging + ------------------------------------------------------------------------*/ + + if (options.droppable) { + $(document) + .bind('dragstart', function(ev, ui) { + var _e = ev.target; + var e = $(_e); + if (!e.parents('.fc').length) { // not already inside a calendar + var accept = options.dropAccept; + if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) { + _dragElement = _e; + currentView.dragStart(_dragElement, ev, ui); + } + } + }) + .bind('dragstop', function(ev, ui) { + if (_dragElement) { + currentView.dragStop(_dragElement, ev, ui); + _dragElement = null; + } + }); + } + + +} + +;; + +function Header(calendar, options) { + var t = this; + + + // exports + t.render = render; + t.destroy = destroy; + t.updateTitle = updateTitle; + t.activateButton = activateButton; + t.deactivateButton = deactivateButton; + t.disableButton = disableButton; + t.enableButton = enableButton; + + + // locals + var element = $([]); + var tm; + + + + function render() { + tm = options.theme ? 'ui' : 'fc'; + var sections = options.header; + if (sections) { + element = $("") + .append( + $("") + .append(renderSection('left')) + .append(renderSection('center')) + .append(renderSection('right')) + ); + return element; + } + } + + + function destroy() { + element.remove(); + } + + + function renderSection(position) { + var e = $(""; + + if (showWeekNumbers) { + html += + ""; + } + + for (col=0; col" + + htmlEscape(formatDate(date, colFormat)) + + ""; + } + + html += ""; + + return html; + } + + + function buildBodyHTML() { + var contentClass = tm + "-widget-content"; + var html = ''; + var row; + var col; + var date; + + html += ""; + + for (row=0; row" + + "
    " + + htmlEscape(formatDate(date, weekNumberFormat)) + + "
    " + + ""; + } + + for (col=0; col" + + "
    "; + + if (showNumbers) { + html += "
    " + date.getDate() + "
    "; + } + + html += + "
    " + + "
     
    " + + "
    " + + "
    " + + ""; + + return html; + } + + + + /* Dimensions + -----------------------------------------------------------*/ + + + function setHeight(height) { + viewHeight = height; + + var bodyHeight = viewHeight - head.height(); + var rowHeight; + var rowHeightLast; + var cell; + + if (opt('weekMode') == 'variable') { + rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6)); + }else{ + rowHeight = Math.floor(bodyHeight / rowCnt); + rowHeightLast = bodyHeight - rowHeight * (rowCnt-1); + } + + bodyFirstCells.each(function(i, _cell) { + if (i < rowCnt) { + cell = $(_cell); + cell.find('> div').css( + 'min-height', + (i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell) + ); + } + }); + + } + + + function setWidth(width) { + viewWidth = width; + colPositions.clear(); + colContentPositions.clear(); + + weekNumberWidth = 0; + if (showWeekNumbers) { + weekNumberWidth = head.find('th.fc-week-number').outerWidth(); + } + + colWidth = Math.floor((viewWidth - weekNumberWidth) / colCnt); + setOuterWidth(headCells.slice(0, -1), colWidth); + } + + + + /* Day clicking and binding + -----------------------------------------------------------*/ + + + function dayBind(days) { + days.click(dayClick) + .mousedown(daySelectionMousedown); + } + + + function dayClick(ev) { + if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick + var date = parseISO8601($(this).data('date')); + trigger('dayClick', this, date, true, ev); + } + } + + + + /* Semi-transparent Overlay Helpers + ------------------------------------------------------*/ + // TODO: should be consolidated with AgendaView's methods + + + function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive + + if (refreshCoordinateGrid) { + coordinateGrid.build(); + } + + var segments = rangeToSegments(overlayStart, overlayEnd); + + for (var i=0; i") + .appendTo(element); + + if (opt('allDaySlot')) { + + daySegmentContainer = + $("
    ") + .appendTo(slotLayer); + + s = + "
    "); + var buttonStr = options.header[position]; + if (buttonStr) { + $.each(buttonStr.split(' '), function(i) { + if (i > 0) { + e.append(""); + } + var prevButton; + $.each(this.split(','), function(j, buttonName) { + if (buttonName == 'title') { + e.append("

     

    "); + if (prevButton) { + prevButton.addClass(tm + '-corner-right'); + } + prevButton = null; + }else{ + var buttonClick; + if (calendar[buttonName]) { + buttonClick = calendar[buttonName]; // calendar method + } + else if (fcViews[buttonName]) { + buttonClick = function() { + button.removeClass(tm + '-state-hover'); // forget why + calendar.changeView(buttonName); + }; + } + if (buttonClick) { + var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here? + var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here? + var button = $( + "" + + (icon ? + "" + + "" + + "" : + text + ) + + "" + ) + .click(function() { + if (!button.hasClass(tm + '-state-disabled')) { + buttonClick(); + } + }) + .mousedown(function() { + button + .not('.' + tm + '-state-active') + .not('.' + tm + '-state-disabled') + .addClass(tm + '-state-down'); + }) + .mouseup(function() { + button.removeClass(tm + '-state-down'); + }) + .hover( + function() { + button + .not('.' + tm + '-state-active') + .not('.' + tm + '-state-disabled') + .addClass(tm + '-state-hover'); + }, + function() { + button + .removeClass(tm + '-state-hover') + .removeClass(tm + '-state-down'); + } + ) + .appendTo(e); + disableTextSelection(button); + if (!prevButton) { + button.addClass(tm + '-corner-left'); + } + prevButton = button; + } + } + }); + if (prevButton) { + prevButton.addClass(tm + '-corner-right'); + } + }); + } + return e; + } + + + function updateTitle(html) { + element.find('h2') + .html(html); + } + + + function activateButton(buttonName) { + element.find('span.fc-button-' + buttonName) + .addClass(tm + '-state-active'); + } + + + function deactivateButton(buttonName) { + element.find('span.fc-button-' + buttonName) + .removeClass(tm + '-state-active'); + } + + + function disableButton(buttonName) { + element.find('span.fc-button-' + buttonName) + .addClass(tm + '-state-disabled'); + } + + + function enableButton(buttonName) { + element.find('span.fc-button-' + buttonName) + .removeClass(tm + '-state-disabled'); + } + + +} + +;; + +fc.sourceNormalizers = []; +fc.sourceFetchers = []; + +var ajaxDefaults = { + dataType: 'json', + cache: false +}; + +var eventGUID = 1; + + +function EventManager(options, _sources) { + var t = this; + + + // exports + t.isFetchNeeded = isFetchNeeded; + t.fetchEvents = fetchEvents; + t.addEventSource = addEventSource; + t.removeEventSource = removeEventSource; + t.updateEvent = updateEvent; + t.renderEvent = renderEvent; + t.removeEvents = removeEvents; + t.clientEvents = clientEvents; + t.normalizeEvent = normalizeEvent; + + + // imports + var trigger = t.trigger; + var getView = t.getView; + var reportEvents = t.reportEvents; + + + // locals + var stickySource = { events: [] }; + var sources = [ stickySource ]; + var rangeStart, rangeEnd; + var currentFetchID = 0; + var pendingSourceCnt = 0; + var loadingLevel = 0; + var cache = []; + + + for (var i=0; i<_sources.length; i++) { + _addEventSource(_sources[i]); + } + + + + /* Fetching + -----------------------------------------------------------------------------*/ + + + function isFetchNeeded(start, end) { + return !rangeStart || start < rangeStart || end > rangeEnd; + } + + + function fetchEvents(start, end) { + rangeStart = start; + rangeEnd = end; + cache = []; + var fetchID = ++currentFetchID; + var len = sources.length; + pendingSourceCnt = len; + for (var i=0; i)), return null instead + return null; +} + + +function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false + // derived from http://delete.me.uk/2005/03/iso8601.html + // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html + var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/); + if (!m) { + return null; + } + var date = new Date(m[1], 0, 1); + if (ignoreTimezone || !m[13]) { + var check = new Date(m[1], 0, 1, 9, 0); + if (m[3]) { + date.setMonth(m[3] - 1); + check.setMonth(m[3] - 1); + } + if (m[5]) { + date.setDate(m[5]); + check.setDate(m[5]); + } + fixDate(date, check); + if (m[7]) { + date.setHours(m[7]); + } + if (m[8]) { + date.setMinutes(m[8]); + } + if (m[10]) { + date.setSeconds(m[10]); + } + if (m[12]) { + date.setMilliseconds(Number("0." + m[12]) * 1000); + } + fixDate(date, check); + }else{ + date.setUTCFullYear( + m[1], + m[3] ? m[3] - 1 : 0, + m[5] || 1 + ); + date.setUTCHours( + m[7] || 0, + m[8] || 0, + m[10] || 0, + m[12] ? Number("0." + m[12]) * 1000 : 0 + ); + if (m[14]) { + var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0); + offset *= m[15] == '-' ? 1 : -1; + date = new Date(+date + (offset * 60 * 1000)); + } + } + return date; +} + + +function parseTime(s) { // returns minutes since start of day + if (typeof s == 'number') { // an hour + return s * 60; + } + if (typeof s == 'object') { // a Date object + return s.getHours() * 60 + s.getMinutes(); + } + var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/); + if (m) { + var h = parseInt(m[1], 10); + if (m[3]) { + h %= 12; + if (m[3].toLowerCase().charAt(0) == 'p') { + h += 12; + } + } + return h * 60 + (m[2] ? parseInt(m[2], 10) : 0); + } +} + + + +/* Date Formatting +-----------------------------------------------------------------------------*/ +// TODO: use same function formatDate(date, [date2], format, [options]) + + +function formatDate(date, format, options) { + return formatDates(date, null, format, options); +} + + +function formatDates(date1, date2, format, options) { + options = options || defaults; + var date = date1, + otherDate = date2, + i, len = format.length, c, + i2, formatter, + res = ''; + for (i=0; ii; i2--) { + if (formatter = dateFormatters[format.substring(i, i2)]) { + if (date) { + res += formatter(date, options); + } + i = i2 - 1; + break; + } + } + if (i2 == i) { + if (date) { + res += c; + } + } + } + } + return res; +}; + + +var dateFormatters = { + s : function(d) { return d.getSeconds() }, + ss : function(d) { return zeroPad(d.getSeconds()) }, + m : function(d) { return d.getMinutes() }, + mm : function(d) { return zeroPad(d.getMinutes()) }, + h : function(d) { return d.getHours() % 12 || 12 }, + hh : function(d) { return zeroPad(d.getHours() % 12 || 12) }, + H : function(d) { return d.getHours() }, + HH : function(d) { return zeroPad(d.getHours()) }, + d : function(d) { return d.getDate() }, + dd : function(d) { return zeroPad(d.getDate()) }, + ddd : function(d,o) { return o.dayNamesShort[d.getDay()] }, + dddd: function(d,o) { return o.dayNames[d.getDay()] }, + M : function(d) { return d.getMonth() + 1 }, + MM : function(d) { return zeroPad(d.getMonth() + 1) }, + MMM : function(d,o) { return o.monthNamesShort[d.getMonth()] }, + MMMM: function(d,o) { return o.monthNames[d.getMonth()] }, + yy : function(d) { return (d.getFullYear()+'').substring(2) }, + yyyy: function(d) { return d.getFullYear() }, + t : function(d) { return d.getHours() < 12 ? 'a' : 'p' }, + tt : function(d) { return d.getHours() < 12 ? 'am' : 'pm' }, + T : function(d) { return d.getHours() < 12 ? 'A' : 'P' }, + TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' }, + u : function(d) { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") }, + S : function(d) { + var date = d.getDate(); + if (date > 10 && date < 20) { + return 'th'; + } + return ['st', 'nd', 'rd'][date%10-1] || 'th'; + }, + w : function(d, o) { // local + return o.weekNumberCalculation(d); + }, + W : function(d) { // ISO + return iso8601Week(d); + } +}; +fc.dateFormatters = dateFormatters; + + +/* thanks jQuery UI (https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js) + * + * Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. + * `date` - the date to get the week for + * `number` - the number of the week within the year that contains this date + */ +function iso8601Week(date) { + var time; + var checkDate = new Date(date.getTime()); + + // Find Thursday of this week starting on Monday + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); + + time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; +} + + +;; + +fc.applyAll = applyAll; + + +/* Event Date Math +-----------------------------------------------------------------------------*/ + + +function exclEndDay(event) { + if (event.end) { + return _exclEndDay(event.end, event.allDay); + }else{ + return addDays(cloneDate(event.start), 1); + } +} + + +function _exclEndDay(end, allDay) { + end = cloneDate(end); + return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end); + // why don't we check for seconds/ms too? +} + + + +/* Event Element Binding +-----------------------------------------------------------------------------*/ + + +function lazySegBind(container, segs, bindHandlers) { + container.unbind('mouseover').mouseover(function(ev) { + var parent=ev.target, e, + i, seg; + while (parent != this) { + e = parent; + parent = parent.parentNode; + } + if ((i = e._fci) !== undefined) { + e._fci = undefined; + seg = segs[i]; + bindHandlers(seg.event, seg.element, seg); + $(ev.target).trigger(ev); + } + ev.stopPropagation(); + }); +} + + + +/* Element Dimensions +-----------------------------------------------------------------------------*/ + + +function setOuterWidth(element, width, includeMargins) { + for (var i=0, e; i=0; i--) { + res = obj[parts[i].toLowerCase()]; + if (res !== undefined) { + return res; + } + } + return obj['']; +} + + +function htmlEscape(s) { + return s.replace(/&/g, '&') + .replace(//g, '>') + .replace(/'/g, ''') + .replace(/"/g, '"') + .replace(/\n/g, '
    '); +} + + +function disableTextSelection(element) { + element + .attr('unselectable', 'on') + .css('MozUserSelect', 'none') + .bind('selectstart.ui', function() { return false; }); +} + + +/* +function enableTextSelection(element) { + element + .attr('unselectable', 'off') + .css('MozUserSelect', '') + .unbind('selectstart.ui'); +} +*/ + + +function markFirstLast(e) { + e.children() + .removeClass('fc-first fc-last') + .filter(':first-child') + .addClass('fc-first') + .end() + .filter(':last-child') + .addClass('fc-last'); +} + + +function setDayID(cell, date) { + cell.each(function(i, _cell) { + _cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]); + // TODO: make a way that doesn't rely on order of classes + }); +} + + +function getSkinCss(event, opt) { + var source = event.source || {}; + var eventColor = event.color; + var sourceColor = source.color; + var optionColor = opt('eventColor'); + var backgroundColor = + event.backgroundColor || + eventColor || + source.backgroundColor || + sourceColor || + opt('eventBackgroundColor') || + optionColor; + var borderColor = + event.borderColor || + eventColor || + source.borderColor || + sourceColor || + opt('eventBorderColor') || + optionColor; + var textColor = + event.textColor || + source.textColor || + opt('eventTextColor'); + var statements = []; + if (backgroundColor) { + statements.push('background-color:' + backgroundColor); + } + if (borderColor) { + statements.push('border-color:' + borderColor); + } + if (textColor) { + statements.push('color:' + textColor); + } + return statements.join(';'); +} + + +function applyAll(functions, thisObj, args) { + if ($.isFunction(functions)) { + functions = [ functions ]; + } + if (functions) { + var i; + var ret; + for (i=0; i") + .appendTo(element); + } + + + function buildTable() { + var html = buildTableHTML(); + + if (table) { + table.remove(); + } + table = $(html).appendTo(element); + + head = table.find('thead'); + headCells = head.find('.fc-day-header'); + body = table.find('tbody'); + bodyRows = body.find('tr'); + bodyCells = body.find('.fc-day'); + bodyFirstCells = bodyRows.find('td:first-child'); + + firstRowCellInners = bodyRows.eq(0).find('.fc-day > div'); + firstRowCellContentInners = bodyRows.eq(0).find('.fc-day-content > div'); + + markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's + markFirstLast(bodyRows); // marks first+last td's + bodyRows.eq(0).addClass('fc-first'); + bodyRows.filter(':last').addClass('fc-last'); + + bodyCells.each(function(i, _cell) { + var date = cellToDate( + Math.floor(i / colCnt), + i % colCnt + ); + trigger('dayRender', t, date, $(_cell)); + }); + + dayBind(bodyCells); + } + + + + /* HTML Building + -----------------------------------------------------------*/ + + + function buildTableHTML() { + var html = + "" + + buildHeadHTML() + + buildBodyHTML() + + "
    "; + + return html; + } + + + function buildHeadHTML() { + var headerClass = tm + "-widget-header"; + var html = ''; + var col; + var date; + + html += "
    " + + htmlEscape(weekNumberTitle) + + "
    " + + "" + + "" + + "" + + "" + + "" + + "
    " + opt('allDayText') + "" + + "
    " + + "
     
    "; + allDayTable = $(s).appendTo(slotLayer); + allDayRow = allDayTable.find('tr'); + + dayBind(allDayRow.find('td')); + + slotLayer.append( + "
    " + + "
    " + + "
    " + ); + + }else{ + + daySegmentContainer = $([]); // in jQuery 1.4, we can just do $() + + } + + slotScroller = + $("
    ") + .appendTo(slotLayer); + + slotContainer = + $("
    ") + .appendTo(slotScroller); + + slotSegmentContainer = + $("
    ") + .appendTo(slotContainer); + + s = + "" + + ""; + d = zeroDate(); + maxd = addMinutes(cloneDate(d), maxMinute); + addMinutes(d, minMinute); + slotCnt = 0; + for (i=0; d < maxd; i++) { + minutes = d.getMinutes(); + s += + "" + + "" + + "" + + ""; + addMinutes(d, opt('slotMinutes')); + slotCnt++; + } + s += + "" + + "
    " + + ((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : ' ') + + "" + + "
     
    " + + "
    "; + slotTable = $(s).appendTo(slotContainer); + + slotBind(slotTable.find('td')); + } + + + + /* Build Day Table + -----------------------------------------------------------------------*/ + + + function buildDayTable() { + var html = buildDayTableHTML(); + + if (dayTable) { + dayTable.remove(); + } + dayTable = $(html).appendTo(element); + + dayHead = dayTable.find('thead'); + dayHeadCells = dayHead.find('th').slice(1, -1); // exclude gutter + dayBody = dayTable.find('tbody'); + dayBodyCells = dayBody.find('td').slice(0, -1); // exclude gutter + dayBodyCellInners = dayBodyCells.find('> div'); + dayBodyCellContentInners = dayBodyCells.find('.fc-day-content > div'); + + dayBodyFirstCell = dayBodyCells.eq(0); + dayBodyFirstCellStretcher = dayBodyCellInners.eq(0); + + markFirstLast(dayHead.add(dayHead.find('tr'))); + markFirstLast(dayBody.add(dayBody.find('tr'))); + + // TODO: now that we rebuild the cells every time, we should call dayRender + } + + + function buildDayTableHTML() { + var html = + "" + + buildDayTableHeadHTML() + + buildDayTableBodyHTML() + + "
    "; + + return html; + } + + + function buildDayTableHeadHTML() { + var headerClass = tm + "-widget-header"; + var date; + var html = ''; + var weekText; + var col; + + html += + "" + + ""; + + if (showWeekNumbers) { + date = cellToDate(0, 0); + weekText = formatDate(date, weekNumberFormat); + if (rtl) { + weekText += weekNumberTitle; + } + else { + weekText = weekNumberTitle + weekText; + } + html += + "" + + htmlEscape(weekText) + + ""; + } + else { + html += " "; + } + + for (col=0; col" + + htmlEscape(formatDate(date, colFormat)) + + ""; + } + + html += + " " + + "" + + ""; + + return html; + } + + + function buildDayTableBodyHTML() { + var headerClass = tm + "-widget-header"; // TODO: make these when updateOptions() called + var contentClass = tm + "-widget-content"; + var date; + var today = clearTime(new Date()); + var col; + var cellsHTML; + var cellHTML; + var classNames; + var html = ''; + + html += + "" + + "" + + " "; + + cellsHTML = ''; + + for (col=0; col" + + "
    " + + "
    " + + "
     
    " + + "
    " + + "
    " + + ""; + + cellsHTML += cellHTML; + } + + html += cellsHTML; + html += + " " + + "" + + ""; + + return html; + } + + + // TODO: data-date on the cells + + + + /* Dimensions + -----------------------------------------------------------------------*/ + + + function setHeight(height) { + if (height === undefined) { + height = viewHeight; + } + viewHeight = height; + slotTopCache = {}; + + var headHeight = dayBody.position().top; + var allDayHeight = slotScroller.position().top; // including divider + var bodyHeight = Math.min( // total body height, including borders + height - headHeight, // when scrollbars + slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border + ); + + dayBodyFirstCellStretcher + .height(bodyHeight - vsides(dayBodyFirstCell)); + + slotLayer.css('top', headHeight); + + slotScroller.height(bodyHeight - allDayHeight - 1); + + // the stylesheet guarantees that the first row has no border. + // this allows .height() to work well cross-browser. + slotHeight = slotTable.find('tr:first').height() + 1; // +1 for bottom border + + snapRatio = opt('slotMinutes') / snapMinutes; + snapHeight = slotHeight / snapRatio; + } + + + function setWidth(width) { + viewWidth = width; + colPositions.clear(); + colContentPositions.clear(); + + var axisFirstCells = dayHead.find('th:first'); + if (allDayTable) { + axisFirstCells = axisFirstCells.add(allDayTable.find('th:first')); + } + axisFirstCells = axisFirstCells.add(slotTable.find('th:first')); + + axisWidth = 0; + setOuterWidth( + axisFirstCells + .width('') + .each(function(i, _cell) { + axisWidth = Math.max(axisWidth, $(_cell).outerWidth()); + }), + axisWidth + ); + + var gutterCells = dayTable.find('.fc-agenda-gutter'); + if (allDayTable) { + gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter')); + } + + var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7) + + gutterWidth = slotScroller.width() - slotTableWidth; + if (gutterWidth) { + setOuterWidth(gutterCells, gutterWidth); + gutterCells + .show() + .prev() + .removeClass('fc-last'); + }else{ + gutterCells + .hide() + .prev() + .addClass('fc-last'); + } + + colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt); + setOuterWidth(dayHeadCells.slice(0, -1), colWidth); + } + + + + /* Scrolling + -----------------------------------------------------------------------*/ + + + function resetScroll() { + var d0 = zeroDate(); + var scrollDate = cloneDate(d0); + scrollDate.setHours(opt('firstHour')); + var top = timePosition(d0, scrollDate) + 1; // +1 for the border + function scroll() { + slotScroller.scrollTop(top); + } + scroll(); + setTimeout(scroll, 0); // overrides any previous scroll state made by the browser + } + + + function afterRender() { // after the view has been freshly rendered and sized + resetScroll(); + } + + + + /* Slot/Day clicking and binding + -----------------------------------------------------------------------*/ + + + function dayBind(cells) { + cells.click(slotClick) + .mousedown(daySelectionMousedown); + } + + + function slotBind(cells) { + cells.click(slotClick) + .mousedown(slotSelectionMousedown); + } + + + function slotClick(ev) { + if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick + var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth)); + var date = cellToDate(0, col); + var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data + if (rowMatch) { + var mins = parseInt(rowMatch[1]) * opt('slotMinutes'); + var hours = Math.floor(mins/60); + date.setHours(hours); + date.setMinutes(mins%60 + minMinute); + trigger('dayClick', dayBodyCells[col], date, false, ev); + }else{ + trigger('dayClick', dayBodyCells[col], date, true, ev); + } + } + } + + + + /* Semi-transparent Overlay Helpers + -----------------------------------------------------*/ + // TODO: should be consolidated with BasicView's methods + + + function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive + + if (refreshCoordinateGrid) { + coordinateGrid.build(); + } + + var segments = rangeToSegments(overlayStart, overlayEnd); + + for (var i=0; i= 0) { + addMinutes(d, minMinute + slotIndex * snapMinutes); + } + return d; + } + + + // get the Y coordinate of the given time on the given day (both Date objects) + function timePosition(day, time) { // both date objects. day holds 00:00 of current day + day = cloneDate(day, true); + if (time < addMinutes(cloneDate(day), minMinute)) { + return 0; + } + if (time >= addMinutes(cloneDate(day), maxMinute)) { + return slotTable.height(); + } + var slotMinutes = opt('slotMinutes'), + minutes = time.getHours()*60 + time.getMinutes() - minMinute, + slotI = Math.floor(minutes / slotMinutes), + slotTop = slotTopCache[slotI]; + if (slotTop === undefined) { + slotTop = slotTopCache[slotI] = + slotTable.find('tr').eq(slotI).find('td div')[0].offsetTop; + // .eq() is faster than ":eq()" selector + // [0].offsetTop is faster than .position().top (do we really need this optimization?) + // a better optimization would be to cache all these divs + } + return Math.max(0, Math.round( + slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes) + )); + } + + + function getAllDayRow(index) { + return allDayRow; + } + + + function defaultEventEnd(event) { + var start = cloneDate(event.start); + if (event.allDay) { + return start; + } + return addMinutes(start, opt('defaultEventMinutes')); + } + + + + /* Selection + ---------------------------------------------------------------------------------*/ + + + function defaultSelectionEnd(startDate, allDay) { + if (allDay) { + return cloneDate(startDate); + } + return addMinutes(cloneDate(startDate), opt('slotMinutes')); + } + + + function renderSelection(startDate, endDate, allDay) { // only for all-day + if (allDay) { + if (opt('allDaySlot')) { + renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); + } + }else{ + renderSlotSelection(startDate, endDate); + } + } + + + function renderSlotSelection(startDate, endDate) { + var helperOption = opt('selectHelper'); + coordinateGrid.build(); + if (helperOption) { + var col = dateToCell(startDate).col; + if (col >= 0 && col < colCnt) { // only works when times are on same day + var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // only for horizontal coords + var top = timePosition(startDate, startDate); + var bottom = timePosition(startDate, endDate); + if (bottom > top) { // protect against selections that are entirely before or after visible range + rect.top = top; + rect.height = bottom - top; + rect.left += 2; + rect.width -= 5; + if ($.isFunction(helperOption)) { + var helperRes = helperOption(startDate, endDate); + if (helperRes) { + rect.position = 'absolute'; + selectionHelper = $(helperRes) + .css(rect) + .appendTo(slotContainer); + } + }else{ + rect.isStart = true; // conside rect a "seg" now + rect.isEnd = true; // + selectionHelper = $(slotSegHtml( + { + title: '', + start: startDate, + end: endDate, + className: ['fc-select-helper'], + editable: false + }, + rect + )); + selectionHelper.css('opacity', opt('dragOpacity')); + } + if (selectionHelper) { + slotBind(selectionHelper); + slotContainer.append(selectionHelper); + setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended + setOuterHeight(selectionHelper, rect.height, true); + } + } + } + }else{ + renderSlotOverlay(startDate, endDate); + } + } + + + function clearSelection() { + clearOverlays(); + if (selectionHelper) { + selectionHelper.remove(); + selectionHelper = null; + } + } + + + function slotSelectionMousedown(ev) { + if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button + unselect(ev); + var dates; + hoverListener.start(function(cell, origCell) { + clearSelection(); + if (cell && cell.col == origCell.col && !getIsCellAllDay(cell)) { + var d1 = realCellToDate(origCell); + var d2 = realCellToDate(cell); + dates = [ + d1, + addMinutes(cloneDate(d1), snapMinutes), // calculate minutes depending on selection slot minutes + d2, + addMinutes(cloneDate(d2), snapMinutes) + ].sort(dateCompare); + renderSlotSelection(dates[0], dates[3]); + }else{ + dates = null; + } + }, ev); + $(document).one('mouseup', function(ev) { + hoverListener.stop(); + if (dates) { + if (+dates[0] == +dates[1]) { + reportDayClick(dates[0], false, ev); + } + reportSelection(dates[0], dates[3], false, ev); + } + }); + } + } + + + function reportDayClick(date, allDay, ev) { + trigger('dayClick', dayBodyCells[dateToCell(date).col], date, allDay, ev); + } + + + + /* External Dragging + --------------------------------------------------------------------------------*/ + + + function dragStart(_dragElement, ev, ui) { + hoverListener.start(function(cell) { + clearOverlays(); + if (cell) { + if (getIsCellAllDay(cell)) { + renderCellOverlay(cell.row, cell.col, cell.row, cell.col); + }else{ + var d1 = realCellToDate(cell); + var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes')); + renderSlotOverlay(d1, d2); + } + } + }, ev); + } + + + function dragStop(_dragElement, ev, ui) { + var cell = hoverListener.stop(); + clearOverlays(); + if (cell) { + trigger('drop', _dragElement, realCellToDate(cell), getIsCellAllDay(cell), ev, ui); + } + } + + +} + +;; + +function AgendaEventRenderer() { + var t = this; + + + // exports + t.renderEvents = renderEvents; + t.clearEvents = clearEvents; + t.slotSegHtml = slotSegHtml; + + + // imports + DayEventRenderer.call(t); + var opt = t.opt; + var trigger = t.trigger; + var isEventDraggable = t.isEventDraggable; + var isEventResizable = t.isEventResizable; + var eventEnd = t.eventEnd; + var eventElementHandlers = t.eventElementHandlers; + var setHeight = t.setHeight; + var getDaySegmentContainer = t.getDaySegmentContainer; + var getSlotSegmentContainer = t.getSlotSegmentContainer; + var getHoverListener = t.getHoverListener; + var getMaxMinute = t.getMaxMinute; + var getMinMinute = t.getMinMinute; + var timePosition = t.timePosition; + var getIsCellAllDay = t.getIsCellAllDay; + var colContentLeft = t.colContentLeft; + var colContentRight = t.colContentRight; + var cellToDate = t.cellToDate; + var getColCnt = t.getColCnt; + var getColWidth = t.getColWidth; + var getSnapHeight = t.getSnapHeight; + var getSnapMinutes = t.getSnapMinutes; + var getSlotContainer = t.getSlotContainer; + var reportEventElement = t.reportEventElement; + var showEvents = t.showEvents; + var hideEvents = t.hideEvents; + var eventDrop = t.eventDrop; + var eventResize = t.eventResize; + var renderDayOverlay = t.renderDayOverlay; + var clearOverlays = t.clearOverlays; + var renderDayEvents = t.renderDayEvents; + var calendar = t.calendar; + var formatDate = calendar.formatDate; + var formatDates = calendar.formatDates; + + + // overrides + t.draggableDayEvent = draggableDayEvent; + + + + /* Rendering + ----------------------------------------------------------------------------*/ + + + function renderEvents(events, modifiedEventId) { + var i, len=events.length, + dayEvents=[], + slotEvents=[]; + for (i=0; i start && eventStart < end) { + if (eventStart < start) { + segStart = cloneDate(start); + isStart = false; + }else{ + segStart = eventStart; + isStart = true; + } + if (eventEnd > end) { + segEnd = cloneDate(end); + isEnd = false; + }else{ + segEnd = eventEnd; + isEnd = true; + } + segs.push({ + event: event, + start: segStart, + end: segEnd, + isStart: isStart, + isEnd: isEnd + }); + } + } + return segs.sort(compareSlotSegs); + } + + + function slotEventEnd(event) { + if (event.end) { + return cloneDate(event.end); + }else{ + return addMinutes(cloneDate(event.start), opt('defaultEventMinutes')); + } + } + + + // renders events in the 'time slots' at the bottom + // TODO: when we refactor this, when user returns `false` eventRender, don't have empty space + // TODO: refactor will include using pixels to detect collisions instead of dates (handy for seg cmp) + + function renderSlotSegs(segs, modifiedEventId) { + + var i, segCnt=segs.length, seg, + event, + top, + bottom, + columnLeft, + columnRight, + columnWidth, + width, + left, + right, + html = '', + eventElements, + eventElement, + triggerRes, + titleElement, + height, + slotSegmentContainer = getSlotSegmentContainer(), + isRTL = opt('isRTL'); + + // calculate position/dimensions, create html + for (i=0; i" + + // modified sleede peng + '' + + "
    " + + "
    " + + htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) + + "
    " + + "
    " + + htmlEscape(event.title || '') + + "
    " + + "
    " + + "
    "; + if (seg.isEnd && isEventResizable(event)) { + html += + "
    =
    "; + } + html += + ""; + return html; + } + + + function bindSlotSeg(event, eventElement, seg) { + var timeElement = eventElement.find('div.fc-event-time'); + if (isEventDraggable(event)) { + draggableSlotEvent(event, eventElement, timeElement); + } + if (seg.isEnd && isEventResizable(event)) { + resizableSlotEvent(event, eventElement, timeElement); + } + eventElementHandlers(event, eventElement); + } + + + + /* Dragging + -----------------------------------------------------------------------------------*/ + + + // when event starts out FULL-DAY + // overrides DayEventRenderer's version because it needs to account for dragging elements + // to and from the slot area. + + function draggableDayEvent(event, eventElement, seg) { + var isStart = seg.isStart; + var origWidth; + var revert; + var allDay = true; + var dayDelta; + var hoverListener = getHoverListener(); + var colWidth = getColWidth(); + var snapHeight = getSnapHeight(); + var snapMinutes = getSnapMinutes(); + var minMinute = getMinMinute(); + eventElement.draggable({ + opacity: opt('dragOpacity', 'month'), // use whatever the month view was using + revertDuration: opt('dragRevertDuration'), + start: function(ev, ui) { + trigger('eventDragStart', eventElement, event, ev, ui); + hideEvents(event, eventElement); + origWidth = eventElement.width(); + hoverListener.start(function(cell, origCell) { + clearOverlays(); + if (cell) { + revert = false; + var origDate = cellToDate(0, origCell.col); + var date = cellToDate(0, cell.col); + dayDelta = dayDiff(date, origDate); + if (!cell.row) { + // on full-days + renderDayOverlay( + addDays(cloneDate(event.start), dayDelta), + addDays(exclEndDay(event), dayDelta) + ); + resetElement(); + }else{ + // mouse is over bottom slots + if (isStart) { + if (allDay) { + // convert event to temporary slot-event + eventElement.width(colWidth - 10); // don't use entire width + setOuterHeight( + eventElement, + snapHeight * Math.round( + (event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes')) / + snapMinutes + ) + ); + eventElement.draggable('option', 'grid', [colWidth, 1]); + allDay = false; + } + }else{ + revert = true; + } + } + revert = revert || (allDay && !dayDelta); + }else{ + resetElement(); + revert = true; + } + eventElement.draggable('option', 'revert', revert); + }, ev, 'drag'); + }, + stop: function(ev, ui) { + hoverListener.stop(); + clearOverlays(); + trigger('eventDragStop', eventElement, event, ev, ui); + if (revert) { + // hasn't moved or is out of bounds (draggable has already reverted) + resetElement(); + eventElement.css('filter', ''); // clear IE opacity side-effects + showEvents(event, eventElement); + }else{ + // changed! + var minuteDelta = 0; + if (!allDay) { + minuteDelta = Math.round((eventElement.offset().top - getSlotContainer().offset().top) / snapHeight) + * snapMinutes + + minMinute + - (event.start.getHours() * 60 + event.start.getMinutes()); + } + eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui); + } + } + }); + function resetElement() { + if (!allDay) { + eventElement + .width(origWidth) + .height('') + .draggable('option', 'grid', null); + allDay = true; + } + } + } + + + // when event starts out IN TIMESLOTS + + function draggableSlotEvent(event, eventElement, timeElement) { + var coordinateGrid = t.getCoordinateGrid(); + var colCnt = getColCnt(); + var colWidth = getColWidth(); + var snapHeight = getSnapHeight(); + var snapMinutes = getSnapMinutes(); + + // states + var origPosition; // original position of the element, not the mouse + var origCell; + var isInBounds, prevIsInBounds; + var isAllDay, prevIsAllDay; + var colDelta, prevColDelta; + var dayDelta; // derived from colDelta + var minuteDelta, prevMinuteDelta; + + eventElement.draggable({ + scroll: false, + grid: [ colWidth, snapHeight ], + axis: colCnt==1 ? 'y' : false, + opacity: opt('dragOpacity'), + revertDuration: opt('dragRevertDuration'), + start: function(ev, ui) { + + trigger('eventDragStart', eventElement, event, ev, ui); + hideEvents(event, eventElement); + + coordinateGrid.build(); + + // initialize states + origPosition = eventElement.position(); + origCell = coordinateGrid.cell(ev.pageX, ev.pageY); + isInBounds = prevIsInBounds = true; + isAllDay = prevIsAllDay = getIsCellAllDay(origCell); + colDelta = prevColDelta = 0; + dayDelta = 0; + minuteDelta = prevMinuteDelta = 0; + + }, + drag: function(ev, ui) { + + // NOTE: this `cell` value is only useful for determining in-bounds and all-day. + // Bad for anything else due to the discrepancy between the mouse position and the + // element position while snapping. (problem revealed in PR #55) + // + // PS- the problem exists for draggableDayEvent() when dragging an all-day event to a slot event. + // We should overhaul the dragging system and stop relying on jQuery UI. + var cell = coordinateGrid.cell(ev.pageX, ev.pageY); + + // update states + isInBounds = !!cell; + if (isInBounds) { + isAllDay = getIsCellAllDay(cell); + + // calculate column delta + colDelta = Math.round((ui.position.left - origPosition.left) / colWidth); + if (colDelta != prevColDelta) { + // calculate the day delta based off of the original clicked column and the column delta + var origDate = cellToDate(0, origCell.col); + var col = origCell.col + colDelta; + col = Math.max(0, col); + col = Math.min(colCnt-1, col); + var date = cellToDate(0, col); + dayDelta = dayDiff(date, origDate); + } + + // calculate minute delta (only if over slots) + if (!isAllDay) { + minuteDelta = Math.round((ui.position.top - origPosition.top) / snapHeight) * snapMinutes; + } + } + + // any state changes? + if ( + isInBounds != prevIsInBounds || + isAllDay != prevIsAllDay || + colDelta != prevColDelta || + minuteDelta != prevMinuteDelta + ) { + + updateUI(); + + // update previous states for next time + prevIsInBounds = isInBounds; + prevIsAllDay = isAllDay; + prevColDelta = colDelta; + prevMinuteDelta = minuteDelta; + } + + // if out-of-bounds, revert when done, and vice versa. + eventElement.draggable('option', 'revert', !isInBounds); + + }, + stop: function(ev, ui) { + + clearOverlays(); + trigger('eventDragStop', eventElement, event, ev, ui); + + if (isInBounds && (isAllDay || dayDelta || minuteDelta)) { // changed! + eventDrop(this, event, dayDelta, isAllDay ? 0 : minuteDelta, isAllDay, ev, ui); + } + else { // either no change or out-of-bounds (draggable has already reverted) + + // reset states for next time, and for updateUI() + isInBounds = true; + isAllDay = false; + colDelta = 0; + dayDelta = 0; + minuteDelta = 0; + + updateUI(); + eventElement.css('filter', ''); // clear IE opacity side-effects + + // sometimes fast drags make event revert to wrong position, so reset. + // also, if we dragged the element out of the area because of snapping, + // but the *mouse* is still in bounds, we need to reset the position. + eventElement.css(origPosition); + + showEvents(event, eventElement); + } + } + }); + + function updateUI() { + clearOverlays(); + if (isInBounds) { + if (isAllDay) { + timeElement.hide(); + eventElement.draggable('option', 'grid', null); // disable grid snapping + renderDayOverlay( + addDays(cloneDate(event.start), dayDelta), + addDays(exclEndDay(event), dayDelta) + ); + } + else { + updateTimeText(minuteDelta); + timeElement.css('display', ''); // show() was causing display=inline + eventElement.draggable('option', 'grid', [colWidth, snapHeight]); // re-enable grid snapping + } + } + } + + function updateTimeText(minuteDelta) { + var newStart = addMinutes(cloneDate(event.start), minuteDelta); + var newEnd; + if (event.end) { + newEnd = addMinutes(cloneDate(event.end), minuteDelta); + } + timeElement.text(formatDates(newStart, newEnd, opt('timeFormat'))); + } + + } + + + + /* Resizing + --------------------------------------------------------------------------------------*/ + + + function resizableSlotEvent(event, eventElement, timeElement) { + var snapDelta, prevSnapDelta; + var snapHeight = getSnapHeight(); + var snapMinutes = getSnapMinutes(); + eventElement.resizable({ + handles: { + s: '.ui-resizable-handle' + }, + grid: snapHeight, + start: function(ev, ui) { + snapDelta = prevSnapDelta = 0; + hideEvents(event, eventElement); + trigger('eventResizeStart', this, event, ev, ui); + }, + resize: function(ev, ui) { + // don't rely on ui.size.height, doesn't take grid into account + snapDelta = Math.round((Math.max(snapHeight, eventElement.height()) - ui.originalSize.height) / snapHeight); + if (snapDelta != prevSnapDelta) { + timeElement.text( + formatDates( + event.start, + (!snapDelta && !event.end) ? null : // no change, so don't display time range + addMinutes(eventEnd(event), snapMinutes*snapDelta), + opt('timeFormat') + ) + ); + prevSnapDelta = snapDelta; + } + }, + stop: function(ev, ui) { + trigger('eventResizeStop', this, event, ev, ui); + if (snapDelta) { + eventResize(this, event, 0, snapMinutes*snapDelta, ev, ui); + }else{ + showEvents(event, eventElement); + // BUG: if event was really short, need to put title back in span + } + } + }); + } + + +} + + + +/* Agenda Event Segment Utilities +-----------------------------------------------------------------------------*/ + + +// Sets the seg.backwardCoord and seg.forwardCoord on each segment and returns a new +// list in the order they should be placed into the DOM (an implicit z-index). +function placeSlotSegs(segs) { + var levels = buildSlotSegLevels(segs); + var level0 = levels[0]; + var i; + + computeForwardSlotSegs(levels); + + if (level0) { + + for (i=0; i seg2.start && seg1.start < seg2.end; +} + + +// A cmp function for determining which forward segment to rely on more when computing coordinates. +function compareForwardSlotSegs(seg1, seg2) { + // put higher-pressure first + return seg2.forwardPressure - seg1.forwardPressure || + // put segments that are closer to initial edge first (and favor ones with no coords yet) + (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) || + // do normal sorting... + compareSlotSegs(seg1, seg2); +} + + +// A cmp function for determining which segment should be closer to the initial edge +// (the left edge on a left-to-right calendar). +function compareSlotSegs(seg1, seg2) { + return seg1.start - seg2.start || // earlier start time goes first + (seg2.end - seg2.start) - (seg1.end - seg1.start) || // tie? longer-duration goes first + (seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title +} + + +;; + + +function View(element, calendar, viewName) { + var t = this; + + + // exports + t.element = element; + t.calendar = calendar; + t.name = viewName; + t.opt = opt; + t.trigger = trigger; + t.isEventDraggable = isEventDraggable; + t.isEventResizable = isEventResizable; + t.setEventData = setEventData; + t.clearEventData = clearEventData; + t.eventEnd = eventEnd; + t.reportEventElement = reportEventElement; + t.triggerEventDestroy = triggerEventDestroy; + t.eventElementHandlers = eventElementHandlers; + t.showEvents = showEvents; + t.hideEvents = hideEvents; + t.eventDrop = eventDrop; + t.eventResize = eventResize; + // t.title + // t.start, t.end + // t.visStart, t.visEnd + + + // imports + var defaultEventEnd = t.defaultEventEnd; + var normalizeEvent = calendar.normalizeEvent; // in EventManager + var reportEventChange = calendar.reportEventChange; + + + // locals + var eventsByID = {}; // eventID mapped to array of events (there can be multiple b/c of repeating events) + var eventElementsByID = {}; // eventID mapped to array of jQuery elements + var eventElementCouples = []; // array of objects, { event, element } // TODO: unify with segment system + var options = calendar.options; + + + + function opt(name, viewNameOverride) { + var v = options[name]; + if ($.isPlainObject(v)) { + return smartProperty(v, viewNameOverride || viewName); + } + return v; + } + + + function trigger(name, thisObj) { + return calendar.trigger.apply( + calendar, + [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t]) + ); + } + + + + /* Event Editable Boolean Calculations + ------------------------------------------------------------------------------*/ + + + function isEventDraggable(event) { + var source = event.source || {}; + return firstDefined( + event.startEditable, + source.startEditable, + opt('eventStartEditable'), + event.editable, + source.editable, + opt('editable') + ) + && !opt('disableDragging'); // deprecated + } + + + function isEventResizable(event) { // but also need to make sure the seg.isEnd == true + var source = event.source || {}; + return firstDefined( + event.durationEditable, + source.durationEditable, + opt('eventDurationEditable'), + event.editable, + source.editable, + opt('editable') + ) + && !opt('disableResizing'); // deprecated + } + + + + /* Event Data + ------------------------------------------------------------------------------*/ + + + function setEventData(events) { // events are already normalized at this point + eventsByID = {}; + var i, len=events.length, event; + for (i=0; i
    selection for IE + element + .mousedown(function(ev) { // prevent native selection for others + ev.preventDefault(); + }) + .click(function(ev) { + if (isResizing) { + ev.preventDefault(); // prevent link from being visited (only method that worked in IE6) + ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called + // (eventElementHandlers needs to be bound after resizableDayEvent) + } + }); + + handle.mousedown(function(ev) { + if (ev.which != 1) { + return; // needs to be left mouse button + } + isResizing = true; + var hoverListener = getHoverListener(); + var rowCnt = getRowCnt(); + var colCnt = getColCnt(); + var elementTop = element.css('top'); + var dayDelta; + var helpers; + var eventCopy = $.extend({}, event); + var minCellOffset = dayOffsetToCellOffset( dateToDayOffset(event.start) ); + clearSelection(); + $('body') + .css('cursor', direction + '-resize') + .one('mouseup', mouseup); + trigger('eventResizeStart', this, event, ev); + hoverListener.start(function(cell, origCell) { + if (cell) { + + var origCellOffset = cellToCellOffset(origCell); + var cellOffset = cellToCellOffset(cell); + + // don't let resizing move earlier than start date cell + cellOffset = Math.max(cellOffset, minCellOffset); + + dayDelta = + cellOffsetToDayOffset(cellOffset) - + cellOffsetToDayOffset(origCellOffset); + + if (dayDelta) { + eventCopy.end = addDays(eventEnd(event), dayDelta, true); + var oldHelpers = helpers; + + helpers = renderTempDayEvent(eventCopy, segment.row, elementTop); + helpers = $(helpers); // turn array into a jQuery object + + helpers.find('*').css('cursor', direction + '-resize'); + if (oldHelpers) { + oldHelpers.remove(); + } + + hideEvents(event); + } + else { + if (helpers) { + showEvents(event); + helpers.remove(); + helpers = null; + } + } + clearOverlays(); + renderDayOverlay( // coordinate grid already rebuilt with hoverListener.start() + event.start, + addDays( exclEndDay(event), dayDelta ) + // TODO: instead of calling renderDayOverlay() with dates, + // call _renderDayOverlay (or whatever) with cell offsets. + ); + } + }, ev); + + function mouseup(ev) { + trigger('eventResizeStop', this, event, ev); + $('body').css('cursor', ''); + hoverListener.stop(); + clearOverlays(); + if (dayDelta) { + eventResize(this, event, dayDelta, 0, ev); + // event redraw will clear helpers + } + // otherwise, the drag handler already restored the old events + + setTimeout(function() { // make this happen after the element's click event + isResizing = false; + },0); + } + }); + } + + +} + + + +/* Generalized Segment Utilities +-------------------------------------------------------------------------------------------------*/ + + +function isDaySegmentCollision(segment, otherSegments) { + for (var i=0; i= segment.leftCol + ) { + return true; + } + } + return false; +} + + +function segmentElementEach(segments, callback) { // TODO: use in AgendaView? + for (var i=0; i"); + } + if (e[0].parentNode != parent[0]) { + e.appendTo(parent); + } + usedOverlays.push(e.css(rect).show()); + return e; + } + + + function clearOverlays() { + var e; + while (e = usedOverlays.shift()) { + unusedOverlays.push(e.hide().unbind()); + } + } + + +} + +;; + +function CoordinateGrid(buildFunc) { + + var t = this; + var rows; + var cols; + + + t.build = function() { + rows = []; + cols = []; + buildFunc(rows, cols); + }; + + + t.cell = function(x, y) { + var rowCnt = rows.length; + var colCnt = cols.length; + var i, r=-1, c=-1; + for (i=0; i= rows[i][0] && y < rows[i][1]) { + r = i; + break; + } + } + for (i=0; i= cols[i][0] && x < cols[i][1]) { + c = i; + break; + } + } + return (r>=0 && c>=0) ? { row:r, col:c } : null; + }; + + + t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive + var origin = originElement.offset(); + return { + top: rows[row0][0] - origin.top, + left: cols[col0][0] - origin.left, + width: cols[col1][1] - cols[col0][0], + height: rows[row1][1] - rows[row0][0] + }; + }; + +} + +;; + +function HoverListener(coordinateGrid) { + + + var t = this; + var bindType; + var change; + var firstCell; + var cell; + + + t.start = function(_change, ev, _bindType) { + change = _change; + firstCell = cell = null; + coordinateGrid.build(); + mouse(ev); + bindType = _bindType || 'mousemove'; + $(document).bind(bindType, mouse); + }; + + + function mouse(ev) { + _fixUIEvent(ev); // see below + var newCell = coordinateGrid.cell(ev.pageX, ev.pageY); + if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) { + if (newCell) { + if (!firstCell) { + firstCell = newCell; + } + change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col); + }else{ + change(newCell, firstCell); + } + cell = newCell; + } + } + + + t.stop = function() { + $(document).unbind(bindType, mouse); + return cell; + }; + + +} + + + +// this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1) +// upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem +// but keep this in here for 1.8.16 users +// and maybe remove it down the line + +function _fixUIEvent(event) { // for issue 1168 + if (event.pageX === undefined) { + event.pageX = event.originalEvent.pageX; + event.pageY = event.originalEvent.pageY; + } +} +;; + +function HorizontalPositionCache(getElement) { + + var t = this, + elements = {}, + lefts = {}, + rights = {}; + + function e(i) { + return elements[i] = elements[i] || getElement(i); + } + + t.left = function(i) { + return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i]; + }; + + t.right = function(i) { + return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i]; + }; + + t.clear = function() { + elements = {}; + lefts = {}; + rights = {}; + }; + +} + +;; + +})(jQuery); diff --git a/lib/omni_auth/omni_auth.rb b/lib/omni_auth/omni_auth.rb new file mode 100644 index 000000000..e02cddabc --- /dev/null +++ b/lib/omni_auth/omni_auth.rb @@ -0,0 +1,5 @@ +active_provider = AuthProvider.active + +if active_provider.providable_type != DatabaseProvider.name + require_relative "strategies/sso_#{active_provider.provider_type}_provider" +end \ No newline at end of file diff --git a/lib/omni_auth/strategies/sso_oauth2_provider.rb b/lib/omni_auth/strategies/sso_oauth2_provider.rb new file mode 100644 index 000000000..6289da712 --- /dev/null +++ b/lib/omni_auth/strategies/sso_oauth2_provider.rb @@ -0,0 +1,65 @@ +require 'omniauth-oauth2' + +module OmniAuth + module Strategies + class SsoOauth2Provider < OmniAuth::Strategies::OAuth2 + + def self.active_provider + active_provider = AuthProvider.active + if active_provider.providable_type != OAuth2Provider.name + raise "Trying to instantiate the wrong provider: Expected OAuth2Provider, received #{active_provider.providable_type}" + end + active_provider + end + + # Strategy name. + option :name, active_provider.strategy_name + + + option :client_options, { + :site => active_provider.providable.base_url, + :authorize_url => active_provider.providable.authorization_endpoint, + :token_url => active_provider.providable.token_endpoint + } + + uid { parsed_info['user.uid'.to_sym] } + + info do + { + :mapping => parsed_info + } + end + + extra do + { + :raw_info => raw_info + } + end + + # retrieve data from various url, querying each only once + def raw_info + @raw_info ||= Hash.new + unless @raw_info.size > 0 + OmniAuth::Strategies::SsoOauth2Provider.active_provider.providable.o_auth2_mappings.each do |mapping| + unless @raw_info.has_key?(mapping.api_endpoint.to_sym) + @raw_info[mapping.api_endpoint.to_sym] = access_token.get(mapping.api_endpoint).parsed + end + end + end + @raw_info + end + + + def parsed_info + @parsed_info ||= Hash.new + unless @parsed_info.size > 0 + OmniAuth::Strategies::SsoOauth2Provider.active_provider.providable.o_auth2_mappings.each do |mapping| + @parsed_info[(mapping.local_model+'.'+mapping.local_field).to_sym] = raw_info[mapping.api_endpoint.to_sym][mapping.api_field] + end + end + @parsed_info + end + + end + end +end \ No newline at end of file diff --git a/lib/tasks/fablab.rake b/lib/tasks/fablab.rake new file mode 100644 index 000000000..7aa432945 --- /dev/null +++ b/lib/tasks/fablab.rake @@ -0,0 +1,180 @@ +namespace :fablab do + #desc "Get all stripe plans and create in fablab app" + #task stripe_plan: :environment do + #Stripe::Plan.all.data.each do |plan| + #unless Plan.find_by_stp_plan_id(plan.id) + #group = Group.friendly.find(plan.id.split('-').first) + #if group + #Plan.create(stp_plan_id: plan.id, name: plan.name, amount: plan.amount, interval: plan.interval, group_id: group.id, skip_create_stripe_plan: true) + #else + #puts plan.name + " n'a pas été créé. [error]" + #end + #end + #end + + #if Plan.column_names.include? "training_credit_nb" + #Plan.all.each do |p| + #p.update_columns(training_credit_nb: (p.interval == 'month' ? 1 : 5)) + #end + #end + #end + + desc "Regenerate the invoices" + task :regenerate_invoices, [:year, :month] => :environment do |task, args| + year = args.year || Time.now.year + month = args.month || Time.now.month + start_date = Time.new(year.to_i, month.to_i, 1) + end_date = start_date.next_month + puts "-> Start regenerate the invoices between #{I18n.l start_date, format: :long} in #{I18n.l end_date-1.minute, format: :long}" + index = '000' + invoices = Invoice.only_invoice.where("created_at >= :start_date AND created_at < :end_date", {start_date: start_date, end_date: end_date}).order(created_at: :asc) + invoices.each do |i| + i.update_columns(reference: "#{year.to_s[2..3]}#{'%02d' % month}#{index.next!}#{i.stp_invoice_id ? '/VL' : ''}") + if i.avoir + i.avoir.update_columns(reference: "#{year.to_s[2..3]}#{'%02d' % month}#{index.next!}/A") + end + end + invoices.each(&:regenerate_invoice_pdf) + puts "-> Done" + end + + desc "Cancel strip subscriptions" + task cancel_subscriptions: :environment do + Subscription.where("expired_at >= ?", Time.now.at_beginning_of_day).each do |s| + puts "-> Start cancel subscription of #{s.user.email}" + s.cancel + puts "-> Done" + end + end + + desc '(re)Build ElasticSearch fablab base for stats' + task es_build_stats: :environment do + + puts "DELETE stats" + `curl -XDELETE http://#{ENV["ELASTICSEARCH_HOST"]}:9200/stats` + + puts "PUT index stats" + `curl -XPUT http://#{ENV["ELASTICSEARCH_HOST"]}:9200/stats` + + %w[account event machine project subscription training user].each do |stat| + puts "PUT Mapping stats/#{stat}" + `curl -XPUT http://#{ENV["ELASTICSEARCH_HOST"]}:9200/stats/#{stat}/_mapping -d ' + { + "properties": { + "type": { + "type": "string", + "index" : "not_analyzed" + }, + "subType": { + "type": "string", + "index" : "not_analyzed" + }, + "date": { + "type": "date" + }, + "name": { + "type": "string", + "index" : "not_analyzed" + } + } + }';` + end + end + + desc "sync all/one project in elastic search index" + task :es_build_projects_index, [:id] => :environment do |task, args| + unless Project.__elasticsearch__.client.indices.exists? index: 'fablab' + Project.__elasticsearch__.client.indices.create index: Project.index_name, body: { settings: Project.settings.to_hash, mappings: Project.mappings.to_hash } + end + if args.id + IndexerWorker.perform_async(:index, id) + else + Project.pluck(:id).each do |project_id| + IndexerWorker.perform_async(:index, project_id) + end + end + end + + desc 'recreate every versions of images' + task build_images_versions: :environment do + Project.find_each do |project| + project.project_image.attachment.recreate_versions! if project.project_image.present? and project.project_image.attachment.present? + end + ProjectStepImage.find_each do |project_step_image| + project_step_image.attachment.recreate_versions! if project_step_image.present? and project_step_image.attachment.present? + end + Machine.find_each do |machine| + machine.machine_image.attachment.recreate_versions! if machine.machine_image.present? + end + Event.find_each do |event| + event.event_image.attachment.recreate_versions! if event.event_image.present? + end + + end + + + desc 'switch the active authentication provider' + task :switch_auth_provider, [:provider] => :environment do |task, args| + unless args.provider + fail 'FATAL ERROR: You must pass a provider name to activate' + end + + unless AuthProvider.find_by(name: args.provider) != nil + providers = AuthProvider.all.inject('') do |str, item| + str += item[:name]+', ' + end + fail "FATAL ERROR: the provider '#{args.provider}' does not exists. Available providers are: #{providers[0..-3]}" + end + + if AuthProvider.active.name == args.provider + fail "FATAL ERROR: the provider '#{args.provider}' is already enabled" + end + + # disable previous provider + prev_prev = AuthProvider.find_by(status: 'previous') + unless prev_prev.nil? + prev_prev.update_attribute(:status, 'pending') + end + AuthProvider.active.update_attribute(:status, 'previous') + + # enable given provider + AuthProvider.find_by(name: args.provider).update_attribute(:status, 'active') + + # migrate the current users. + if AuthProvider.active.providable_type != DatabaseProvider.name + User.all.each do |user| + # Concerns any providers except local database + user.generate_auth_migration_token + end + else + User.all.each do |user| + # Concerns local database provider + user.update_attribute(:auth_token, nil) + end + end + + # ask the user to restart the application + puts "\nActivation successful" + + puts "\n/!\\ WARNING: Please consider the following, otherwise the authentication will be bogus:" + puts "\t1) CLEAN the cache with `rake tmp:clear`" + puts "\t2) RESTART the application" + puts "\t3) NOTIFY the current users with `rake fablab:notify_auth_changed`\n\n" + + end + + desc 'notify users that the auth provider has changed' + task notify_auth_changed: :environment do + + # notify every users if the provider is not local database provider + if AuthProvider.active.providable_type != DatabaseProvider.name + User.all.each do |user| + NotificationCenter.call type: 'notify_user_auth_migration', + receiver: user, + attached_object: user + end + end + + puts "\nUsers successfully notified\n\n" + end +end diff --git a/public/Charte_FABLAB.pdf b/public/Charte_FABLAB.pdf deleted file mode 100644 index 41e7823f7c10166fbf00f009ed4dc0788f03dc51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91022 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 N!cqbfBjk9geMQ#lCg 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

    AR{ZYs@AHy;=X0)@^l+b zs)hT|%7qp^m6RsbGNdyPhJ;A8Pt!sY@G*@9Nc)3PA%+5%r#sT2bfw6Su%Uv_;*n?2 zIg1G`ya>L^W2#rN3qt`y`7oK2zgBjkpp-0u7w`b4-=k>w0+0Mw3@`g*jYs_g)ow05Pt|&*h@{lWIq051HpiBe{M)z=d{Ft-&0u7F25uXFNg)wNn}_>nH*Ka zcU%EoJZr0f-hq4mVSFyl%fZ`5HE#X9tY?wyB$+{5$D+mj^P1OOcV&ohOnAole23ng&cy3ryRwH2Md zf6H)tfBWG0ib|d8L}`zEM3@=&fi6_qP%Z2W7K*AmHhsk55lY)co*ln1{Po9!i-kJD`F-%mqNOg6?T)QWx3^5+ zKJyLkI$-Tqz$=dG>%^BSy9}A1B?QN9}&L>hb-gz|$&RgDB%2&R1 ziehT};J$LWxYuRiGKorHGabuSNVXA$* zxdS-^p>tZ*vF$1OolIC&uAxUXMdwG+o%e?137XB(zz}?!} zAtcJVhGIO`uV$OAwxY=}z9-nWjK-xDnpQ-DNZ78~+?ixJOmqwyOYE5qz` zz$(-=6=uXUo7~{*S-UQQm*aNjr4-9jom$ycic=6!MrCp9d{cd9g1O`XuxVP^a`}-; z!+-tYHptBw*?-uz59QL(2-%X@%t0>h$?YQ%bgm?&MulShxd+#wEXVj}w|bzU zIF>XbG9vAu+-LWbucn;UGWi4G&>B~nL8oxi z)T~*DekUZ*Bf2j-{Bp?JM^VMHk??}1)Bxy6)XXTRobf^7lJ*0{)EAOA7}&!fB9Yc` z-njj(+d^>Ct+nqNeUO^(wLw zQTNhz_LLRvKq>bX*0%D#B_`*G-rMKn(K1HKHoY>HZuihJ^P|Equ0=FTLgTC3O2S29 z5cJWzhXl`@`^<%npHhtC0~bBO*Oz=yC_wRH>$cl^1nijydfk(ZwJ)wO2z2Sj4_Vi4 z62y910wuEAImQn|1K`}8aq%9#}K;yk{!S~pGot-(hA(ZFKj>4snC zhcJvUcl9|1Irp7R5ZrtPZ7Z)z)+PhK2PgVyZNe5vg8Uk&$3{NK&#jaPm?1%f!-W8S z+|;Em7Z=dMX_;i`hKrBa%1u*j()b(M`L(-f&LSFkjxIWy^t#v#PnlO+?K<~s zEbljc>RyRnW>U3QUfuOZise&7Rc|i{&gq+5oF6+Z-fa*hC=i<{xC^}7zreF;CJ~|uu>o1Ve_2fpl~(zu8ocL zDXD|?_n_C3aDZ(ZU6Jn<=sYAPB%GR@Y;p8ifNQva`ym>)7Kh5R6bze~6Kh>#7z6*Z zqwor(DU&)A(8^p;dFQs+u$-pm>D(V7S?Xf%v@%iEJJ@!lph%o}QF4~}!;HRw!a`$U zos2`~tQJpqkhD^uiNf>R^yGB7m$<>9{k$kqM4q%@Xo(|HKNu`+Xu7P^Z__YIfreDJ zFDR@(i)gMwFw|qs@CWQZgOm)8SdZwJdM1H2{^9RXaG??y{GHzP34fgva2_FpAHG$7 zd()~e2L5%UQ!En+lc#C|MpRhq6VrD{5K0$u$miZ?t=K9v>yPy{owG{Hx`}6F=Cg-m z;scMhV<@MyUFK%CO2Ovlb2R0us6KCd6jD&jsamXDGir4rQ}R(* zdk*jXZN|5MGV7K+NfxWpl*6R`#4T@i*ri$CgmseikHU=(&Yr?-87`fV)6 z%ZjJH2o*ugU@?q3ml8}2y`6Z4amw=s*U;6<9ORJb%9X9|>(L^*HDC3rtG)9b!B=m< z>Oz4fhC9z{oYiJ4UHPOl>;=LI8@3mkns20m^BId$GHhvb2p(V=BvShD5@M@LAV~PC ztzIKIWgfaz2-?FTB;C3boZ)bKgZIx*3i~@DOT``I$OL=E{cdlep#U`IgpQRljn}#M z4R(92rLG9UcI6POmK>=1~GF}eQ02jRx)=qb~3dXx@7 zT2f5tHh$S+x>hzl$I8y6mPw{Al9QVH6f^HzzHZ}0ef0TRzx0lps1SmTg3o44$tnV8F<-q`-+GJm%UqIL*)w6`56FVvD z<8#?JE4@da3%OXAd?2^y>FwllIv{Tfr6M&$Q*54!@sPXIeP|$x>=f0bRvf9JNi;IC zlrG{xbtJX zkw8{8Zd!XXTyu}7d3Kl%MiqVqIG*rqjB%%WK`CpoOVYM~3|xOc5zh5s`pUi(?I;(E z1_^9CGn75fyCz50!*m78bP6x7je7xaff@h8s!bQPEB8eIT=7j&2;OA#R~x}lb}LqT zX3*$Uzp!U6KZa5)R;axp%Ft}HvQX8kYon>J2RIfHX80K$g}F(QSMaAZQ|J!B(x0Vr zqtUAxztvSAy!4aDQku!+zu{5-C56k%$j0<9Amv}N|Eq=9in1!d)x;_Iz7Z~qe+v5k zg8}*991A$(KU8P`9|V1I>FK`du73kv^en94e~Hun*T;9wcl00Wf35vSyS^#0zsFhV z>Ay$%PQRnf%-D|2gvC&gnnbf0w=yz5lt_ z|9QN>)%^SX{ucY&k^lcjzt8+XFPZhdk*?+fMp7~p?n2O;W*ZZdmGac*y z8Gxc?`a1wc_n!bL7P{{R@c&d{{-*f;ygo$`zIO2^iQRNf00r2bhO_W^nWCyD%!z36oeL8G!&iB z*4&-lre-%z^6MD!@J?U>V%B*>cm;dS(8B{1y8{CTEQNW6_-y!wJkfT4g|p8(z#=5l zofhwnKMx-t7uO{l{~9vfk1@vng+{co)nx?|h}QM==6Ut@?JjYuv`}&|U-B)*TspS8 zaFz(qup$bSOVz2Ti!$TA!^wYa9#*A>G1_?)jw_hbRDL!~QG4Y}6(HKxc7JQ0WVl5z z5fGl<;&IYTwBl@sZgX!(Xs~69S#9!yWL`DW8O;6@MzhoG#>Q($w%u8)<~nufaRlcY zyVY(7N#f^C+DTttz4=0F!z)F~&5!ND$_0twu{kGxOzSGurc?0Ha3+aL70EwH0^Jfs zFC3=0&hRO;dP8fN;rYHDl7#Jd9wSaS_H)c#t=)^)F#cOUK>XNW5(3=8Rov|+lS|rJ z*tG}(aHFMeu9-hM4Lx4@>1U)nKVQ$z&Q}s$CR4NzHdipxD0}M+U;xku83LyLfAB+0 zJ(4GyRlrQ2%<7IJlwD3if=(3uN!IPtZ050=mET{-em!KCrpb3_+7JLfp zdytzJ9v6|@j$X~fE`X5%rK9ucBKu(Z8kKf0y>0b zk_3RDkPDl@^=+U6H;W1bq!|Gf9m3`fSO-lxb8FKvmL|0zGNoPSAvd>cZuXq*cPs@K z&{$U>C8aLRv*u5VO}^kuu;i3Tm4tBch>L*8Kd66=gJ=*^;vsirsZ~vAb--OP<+|i; z6B`a&!E@%LSF3_XYjiBP#xt!=wknvqG6{3aUY`hA_yzXM&f}7noa!8BXP@+M&6cIQ zd^P5uU!L~$PWbU_kT$>-wDYa$_Jpu8pAw{Ol&O_Av7`j9pdMO86NPZg{Z8Rw+8IS8 zH?sqc3*g-4+cf0owgqei1!i9-;uKc9{xq>>q+zxB{TvhN$Wnzk5j9o2_^cJy;Fc|h zG)TZgw(bkoEMV>7ZtWn+WEu)ykPq(bJBR_h=98?s9J;JDzN$1n|JUYVM$sGv+Jt|k z6kmc^Nub%gV8GD}-Or}pB9BX|4SA^`YwFmwjpBhD{(&nh7K47QSkZ3W8P6a>b9lz2 zUWNe!VLt2t6oglG@$g!zjDU&{4dU!l?G zBj%hpyvF|=NiGna+rZP~aRsD2ixHqR>WRqb!-vTSL~chL=uJhN=Ev)&&FJoqzhPCr zUP51t&W%lf8V9DWTWMP8%GfzkY%sYOhzig%JO*x{4-LTU50GWGTZrx>RVp-)W^2^X zbgW&A({*RIP6@P1wLO~W?UsmtqW)|!Iq@9yat5M*+>l_N}1 za|vJ9pV4KyYeX^Crj2~RWxvg|>>zO$*3vcTcwItoAY#D|Ijgpp`V zePj#tzl-3gitNImsCYU7K5(;i{N12G34p?*BjEu43S#v^LvqlbcUPaGI_WI?>VgDm z4pjqw2iqd)K*RZTAOZA$51|4L;?n-|r7PC|F2bVvpPzR0#uO_1+5wS@_}B@GumQvq z@cxyskHTeR{v3_j$+73R2}1jE`NIapha-Cr`iJw6X25Q&XWvKcMpN>S!d_(qi-p=VAx7$wWDiU|cXMtMzU>;nS z(`$CQqP{XQh2Uf}tV$w2Kq=re3n}anpA%3BSS7r3-ev!5e81eGP5yI&0LZchaG^AM z&6S*USuKTpuF>pXi(3cR;C2x&*~G_Te|iX}1Nty(5j?fwjP5)Gz)Wp?9fY1b+3$OT zbu~g@a&ebtayNBP<#^2QRf)~BCjFAE$T@M2I3YtuNOLw&6btaDNt;n*Bur=2Zxkvt zm=TAePnz}~1qX-fDC|)`da9r$8rVntbHQe~9OxYM9Dp2DypZpfAZl$&tQrS+-S7dl z{Gd6JE}Xv+GNAnzrzjnaugZ{fZ=J-&eVu6)<$$M?$Wx%N`AZFCtHlc5#K4w9kMC3z zrhZ(C0Y~A#6{%iZkh{QIx5@Xg@`{ui5O%*SjHLAtLu3f};frK&K^p7!zPycpi$aA? zhysuO&?|{0rO}KG3XM_!74b@nyUXq*+gKn<&I(-sA>ZSswaP30+%KP^aJ&%*dvwK5 z!+vkbV~J8?1da{yN%{=@`o;o_e15}MECb|H)*Lmt5Z)tz#3Q^@s8c#JJf`ox%mEpP z$&ItF>S2DZJ1-RW1cCz@3ArgdW6XPdar{)_hETE{k;DBMo`|rHS`J=W!HTYdkdCf_ znvQl(D(pt?VtWmHMJzV$^f>;hgF&9|CyQ9*cZ8BhbF3$Ey_$_o3APvv{VkV3*KTTK zH-IPoQ!c~O6X;l7D|}JDht1J*J|}@vkR7@2~HI%?u z5@Vs)V%x#?e8d#qxJjj1c6WX|$?^u7k#Whz0kt^swfD!4_{|tzvslZMJOd|poQdoF zH2b*8Kf{D9TbXMzqz_vj?)ZEjnPupn5tBFHdJTtHt`4VvGA3%SygKXhUPj*3j2lR@ zCS?fATnXc{Or$nxDI9FwdFtrK@(;a?4sd5SX`JZR@>4i9&&{N>mt{Lpn^!{h@&f=7 z86v>JW_fATU_&N3Kk6p-Fw;e9>nNs#oLTopT>X{XdP(w8vMo09Px+pQ^%d5%Pc z@#l1$E!~9wmJ4yBR%=i13zUdE!Bw_v_a5;IG>!^oydtWSOx@^wLb5cq zH;ZRBlQYn@&uyy7={QMmwEAeQcnrESE%Q}goLn=TzxgbvAkN$vUNhUZWV&fYZxHUN z5Tjt-YHGM*iU#}hCs`TVJ!J$G`*IwLgxKX=bB|5nDGK6k@dz6hY(q-2MsOB0CAuE> zx;(TnlsGfcaj>pjv=PeqWiblk2s^tixb9rkNsu_20fCRz%e>Y}*qolzT#LgvA^#V= z$5wj94iDFw2yunUAWwVMLfw zRcBT%?@ctYxN~xvmWYG;ZOvWL;$E!dg5mq;k*b)Z^XHZYqqLhgt6*=r3 zlks2Brzb8Sk=3YqpXzzaQzsSjs502VGp^K!1Z%JCpM%~jA>W=XXQHA)CFg0G{Wx16 z*T0l!e#RCBYZA(Y_YUHx2DK z(%cuGU*b&_l|L*PHDw~RH8|EW7MmARB6(A>6SEanuIwXK60=orrMfMDx z`DsfJ7B@CLvH$ZFMN8mZ>y}VVp5Y9ZSez!efXslpC00YKF}7%grX;RW>`G#YfoIqQ zfO;IS;CK%F7OkRSZdpE;je+|PFGc2cgoak@lLdDY#aZlbE+R#Qfu`@(%NP9w?=@C| zYJ<)};IL-g{#sdhg!K15e|=rZx}X-mb@Ugpw=o7i#;^fB1XY*#ooR?L-_28UR)z6d zp!2Y3cq3qkrifECp;!#yJ}w*N>U$<+Y*b0u;cSTmTu0bvM#>8OW8!snwajyc=s=dw zCY&53dIgb8%*X!HzSJ#p!f$UR%RGt`ayK5&c<5TfH$P7z^!~Jd>wY}4RBTD_xE9(w znC11Kc0XGpEq+*L;dU6{yujR2x5s`7NXv0x2_(%8nZ!cR(j3w}%kS{- zQ11}cD5c|S5qFY#k#68y$|IAX%yG=|h<`DDq>V$QZ235s7xRFFOoH11Ht&+%;=6)0 zc=LXWevZF@JVfQ)9TS-tLT&J0T zuh0D?8NgZDTIK-~BSjR3*yz>gmpev?1ocL`&{M!9i-a!RAgRA`Z1F?fFX{W6_3OW47gA^GIz47Htq#`^~cf%PX}nE!rK_OW;NkpDiA z5gu<}J*7o?l}FnScqnS=)lB8B6urvc zqjJIyXYB4dfi}M$hu&5EfzM43;aR5jMJIz7bo++&lY>=NkiG=a0~rqoM(<`=Snk5u zFf|wZ2@!i5?r}>Bs$!w&5wcN3{RxG>x_=EQFT3t*u8Mu^QtybLBvDFc`ZBIgLf7~^ z0IUq5FKax8@E#8>Bk}y|(iXVPNt$8WM{^60ELfUjZ}ojobPZ5-KrkKVoD?jbCA(+k zgBga}g|{1N5_sMOtq6AhthKYK5x>niQE8KJ0OI;>zFDOffi&8J9{iXDA!4@_A#z z;_E8p`nD?SHu2dyLs22ZnYL63KJvc0{^UWJlNkQ-lKyH!S25_uN~}p_nUtCMD8>`F z8870ynl{|Geaz#Z7J=HMcZ+e$_7aLt%i=}v*FUpGckIs5l1>!RXMhhCObrw}#1#B{I8tnicGug5qy(M6-3 zrEF)ohB&bLXl}4}>GIzc>01x8+N1;ovnWP`)kwX%=X7ZKPdZPdL`O4L_q{>3kZ>#+ z(q?lEGr`WqxWp%GZZy~i5)5g9Wphb!+A&!=(u_Cc1u3uA223lqy!ZePU&imJFM@f*_z;mWr}o+C%Q|)5Q%Seo15rj;&{4BwMjtxUGm| zPoof??jYXlAMP(042AoaAr2@8tI21XZAfw(lIQAej~l!+?APgmjq??~%g^TlP*5O1 zlu>IYTw%`hl(V;DO)o}`$wxpDK_&uQ^3r#MqnEWNCX@vUfj zSQ*KkOm-DB80igLi|gf|rz1NF$=Nu}e`?L^ywZ|oyWgKMJ~{+fCwWl{aM`I)|DGiv z!|GJ;1k@cmJlOnC4W;$jmIh#{t)LXM17gM4jdT;4*|ZtL@IXCJg_Rpas+8Tygq(cz z6j;?xM?;cA;%X)_BP|SRU;s65qi7)Fz{W|dsDQYLmYDwD!}bp9TF1kN4lk;G9+o1# zeyuhFiRBKZHJg45HjY|E!KuR&x(?Q;hROHD+W03rdf-pLb~@F2gsFH z7f9kCT|mG}c3?&ZLUI|)EeqUm3!q2UXz)z| z%WH97t>L?vvXBhuy~nv0)vyE67G&}YP>H!8?VmH~LIwvXjd$(clLgg9ztRt}B|>)! zU|ps0|=-}5BXW5rBt)cvZ)r$;ZV2edza!U|Sqj&@2N81lbI)XUMjg`(Y;uX{yAN4TlV%1m#e^mXJH8A9Mi z#142;rWq}b5yAffCw;%RSP)1nAvuHf>dz#$7Om<_bjzTOKa6!B=!$+jaH|EqmVGg` zLy-~K^z@OBVFoxD8(=a3-%a~@^kedu1%Ind)K}no#gaOV)UOAM7Xz-@EB@p^)lj$A@5eKLNeS(JyOz97Cu)&{z%FREUz$f3sr%_&}!gYEg&E~c~KXgf;cLT5v{4Do<8xQ=o;CO2^ zmi=GWt0J_5blZe<#E=QN#Kk{5V(a9@r$UqX+oP_bSoIR&3ZHqn5tU1j*+YK)J`;h zta2}WQVh9H_M}&)=T&m7FS!l6ta2*5Vn5!yMY??sssGBNN{zV=eo-C{@C+|BEQn$< zV-tj*1E3?^UOnO#U$F``s7#hyXPggf2?3`#9$kUJB098>z>+ zjYnK0(A#Y)g})UY&Dzaor`ejtE!5&T^W1JmJ0e^Vg>2GtOW+VbVxl3HCmthZ>_@Ki zHj$)t5SH^N(o`fkGG@?pNE%XBgv)k*DXMf7tWC4FJU$#-6Qw2Mb!=&(ER7+>GUt<} zvaN%np4l3XdhGWz6sRZi1z5hQeH~r@aIWzJjhFftY?Y^iBGl_Afz@ykPfu;Iq4aT< zZkEkx_`F>pokHSJh_GW+cQR%HUWvHgEOuYol#Q8#4LoByVZ;!+tUR$X|7 zjmxX|+74kTLLW*s-(IG2WMVywL6823fv0-i<)Yur*V%$ z0#ju2CGwqd9XIEw+l4w79LtPBTzS1TKPs-(=f2IsRj8Pf)J8Q7GaYByACO0~_er&^ zBbU?%M+5c5$P%#QFO|1owV1>r9N5ECQ=IrgboFR^xx%2eHG+l10axn}9~47=l^Hn> zR#ZS(IJ8ErGB(jzo#hp%<<#|(E_c>X5^2MCl=3QKPuM5jlg<|cC6Ehs^M6YKI)9W? z+O`#8wM}o@)&idWDAPR_EYEv02^YXVK~)rDHxUs5Nid*;$A{(s3vS1*LW~dwHw@m8 zm*-Nm$nOC=Vd)b6!s`gs64{%tbr*Adeex};blX+#I&L{NP2~7`HoljB)~L*Xy8Qko zt#z9_I;on=^0vh(TJNadi}E*?GH7$k}Qm z^V9kI@@~?9g`>5r%KHgoC}^;TNFeIElgrWUm|A*8XnUk@dj!^1+Z;`PT8&n@Wz;6& z#i)a*PT60NO(p(|;iBHC9!7g( zINol>d)tDEZwtS-+SRpllREF{G;4K6hq}t!-e2RqWxV4)jl|!748D*WJ(HcVSr{If z`Z5Dc#QjmJ>BklS83<;~ki}Wb!x2t-SMXA=OBkU;wA#bsTm2@hHu|gJs59jjDGN_B z?i}PPIYovcC7>A33|`O|d|OOv0t163XfQIYjFt2PD`+*&l^`ZGxEwv)TegcfG}nyr zu$ptG@>c{LGe2ZW!l1;8ooz|$tP4|{u!o%I>T|nWw~O4z>hTJmm9jIcw)__DXJiLW zXOWlAdw?goSGku}y+(zWe*2#d;?4X`qxH#nr#ZPsxpoCjtM*V$3i63^MGGbM2oyAG zxwVMUDHYR*x*9zVEMtp$asY;~+EVllN?bQZA_}JlTu?&=qPkp#F(rE@g`aKRke0tB z*86c@Nh^pY*0WV5c8NcEX9-d>L?nOx4Y!x9Hz2K=o5HyU0t_y2N4g@x;K-cJ>m% zq}SoSn@q(@Dn*i7(a8?#!=Qx-mcolmd8naFjf2n4!JYXXq5v{6KVDW7t`~R zGQN4O{TSr4#XZk8zX5x_QbUB-<5iE50V4P{vu6KPyz@b7jBF5t$I!flxSIweNaeLr z_QESxo>m7{u#E|l-W}O?%;!>y=tk;E`7v~_J?q_zypO>B(JIt_llH*L3vTD?2F_{d z|LESJL@Du6>6K;EDM*vB8J9dKYf+;nR+#~zh(YHf{zw{M_N!SPtiWfZ(ADon@g?I$ z^9AFsz@zQL;&YNiomHyNIV)WE>RFnRkM_oJrQ(jA8H}|cTYMxYBukenvNA$MA~(kU zpI}8+1P#yJlOr%d3Wq&GA1l_HBsFr4Iz^q zAFJ18eR^BQlx1lx=;(z^a}+q|?GqZL04foFpW&^r5O60nU z^F|l00X8R;3x|j1JLrMES>LiM-LS4b39(w59bnzOz)o?2~cw+C>L*n?6ej3iZi3C##h$(MFGeQE@sbnoitijwp>X#hPFh`gG~V7P<^+Pp zq*mXXwna&Oy+egZgrVgL$l(+IVSsId|lYQ<#2Z#L19$g=aazbL#WR_1`W6`#ca|4ERIg2Z_18xuP9J*rrU4YN-$;!b)n1 z!j%mg$}VMw<~!1R)MJ;umf`}}OXEU#3V`~|dn^&#^dOGPgNH;UY?qfLE79w5td zv0)G9gN^YL_7oZbDUvUKnedgh)htQOST29~S_X@PA zhA^HQ$%$sVhjqlFxJ->7m0k-7%H*FZHnEjRZL;)fW(Enfi5+=$*}+iNCy)v+m;XdA z&roV$afCW%YIe28%7VJ>A4Y+2MMCy<<=dS?@L~VR?~Jj#Z6ZjWP_+hQJBx6uzNwdC zDnm0(bjynZ(PZ0^!+Vo}giw7_*;A3Q)TKIR9iXqNCaRr#j^igd@u@AN8A#WIS(>>Y zQ1$~>!NB?0U@jT9N?IZ~O&@4zD`7-ju6dbkn{30~{L;=yWAS)Fz~pgFxPN0?()R9) zABEF5xvg+)G8_&&nVP$r;W^xBI9NryA#0h8**nyF(^#Sy`}~xUc-md+4rGoo`IDvgLejviZ@O+JqtQ|LO)E&%H*tuHh*Jj#6Dv_nBRCHWHnZV- zhf`4Wr>TerQJdTG6vNB3O=DfE3up;q-@^(L@{xVR0|b4399Zav`96KMX6C*e>ZYr}*LIS)?sgyyNRD|_#^Go)r#?92C`L3=x{ z#f2NIc0@YEakpH)B`tAxg@|99pD_Yh7uGl0h?<98jBXP5R$oy79DpA!RUlq5=81-3 zYXomxM0P10J6)+8y-uT3V^t%ED0?`|s$Q}Wq(h(*F~}HbG_&9mH`hl)%19a1dO}n$ z>0f2#jnA$x@%w9caW7xvD0g=v)9AT)32UX{<6!2lk%QHV=0klbA;GqJJFc>H>fr>> zUOt>y`0Bx=bO1z*6NcodNKEl11&M;w?S13b7)I52W=b;an90~hH`3FOk>uj0QmQV# zNnZN}a~%-vW$k`T+I6|6Dl_U44|AU7{$mR$d-XGqL!Ka1u6YPyhMP7L zq-fASTxB)g?||dX&WRt7VqiAnnjZokwk%n+qm_?X>g)3x$J;uXs#xP$^kebI;o%DH4f7$rkExl#vh1oJLUG(Y|{-j zq%AKAYZhix8OWZn5BL^L8vU@ue#Aw`BBEr4VaAOJhi_KnO)K(3oEu7p^a4@Njn{`+ z2)cj14^not>5{`Ry+81uudHBUYywp###*5|Bdjsa(YvPDrWmJK3uVs7;9}8LR`@xv z%H!jz{NVtx!>17Dt(}iapX#zoh{(IiV%nbdhth#mph2 zKP6IqQb)*E;njL^U`|=1Yp1r+(CRca!{x5-eyjLsc?_qHrqp<%^X#NAYFu0Kr7p>q zo|1o#Vsn5?xQ?$au+O7Tv&@EgKY(s$+-?j|Cqlk-Rk6CpCjx2ru;#MayL8$j^VI5* zr?@m-MlSY`eS{GcnG!}}?=e-KT5hz3Vd{i1fV15u7{>d{1(a$|5S&xixLr84SY0Q{ z^UqecDix(+ig1c_RjU{d8#`qVPTphQA{y3l6Y52?9LSDNljH#SFt2N*Vrv?}#x z!?^+pW*&MTUvsEmm}9S3x{q_3GTJr=@-m&*-=skk*dEo-s%BAa&)lr#8fi7j2bfYA z)yul8L?<+uQV=T7pZ+~UBz1QO$b0vG8;KvcpWSDJ+R0lCoz~n19)>mhE4SUMq-QQ3T07VD}mq=35I3&(7VtBf0CzJHDhCP0MiohJ;{Q&CA*{T+{ zupTPtgOfMinEt!jEgotmWiv%c%hx`jkG854&YyyN(UmH!bg=O@w?A{Dp=Bfc=2LQQ zl5G(`w9uq$K(fuR`3l-G3EdUjkVgOMS(aHj5KjV3gyUWV_fC zr#CEVWzdWBf~>1HqLFQ&M`B>(Atv_(LC?rVpq=5!YLh&rJo9+g;y%|VD2mTA^ykT7 zOnW0BNX9APDpTBT1po^eC~(})zswYhb@2dFRb zJkdR*>NOx&1R?{PXGqZcQ9Q>lY-y9`_Not;LpD+cZbkwmn3g3Se8~Wxz_UQ}B(a!b zCR~bxyO6ivL6SH~W9&7A8wzJ=v4l%>NE?*dky(@TqC!&#QV|R*f%AtLwso;55lY!E zZ``X+j48JB2Pbj-MGrDh=CFRVzl;2VX+wURz-8Q()wb-jcKNnCpJ}KxtLkOJY-$_C z@^y)L#xC);*(`vuv_RRlK)%{|H~?$@Jn&&aL{mhpdrb!X-Zm zT+J=JV>x0dTA5`!MIIZJ)eGs}ge^6JDuZKWkC8$=T!XI^yjibpsS!LdY?P0bXPdN5 zM4xJ&O@;ei&s9+{K@gc1+&;)rZJaiaEZ?#sr}yBpfrQMUz^4tT{d4M!f>-;8Qt%{U z2eCbJVxln$Z$2x8$Uq$gpX1Rf#uoq>p){~15vEE6Qu;P=)9&5afnlLZ_fTNu=@STm zb7XEFuHUN-TwNjQ`Mt6yg{L&+7|t8rTXf~<&4k%;fED~uP=c@5k502|`m7^<#i>gUGH%Ok|RJF3WdI>QZS!yt`$ zB2O?O+PS`V3)k{~7d%REojPp8LiwvfBv)Nar|6MG0mD%!1&di~Te=cNzN%m_GT|r~ z%iBj&xYy!9D_WZ;@>Yi9)Snyj+hZ8SO7U1rTNKfzF7Ghi>rSf;>$=M3qFg$uDbF0M zN#6V7<7^Zq673*#STxkO42 zqk!=82`i>EK0kY&%re?78rOH2wcg)L?8R@6O!9rVOI8KNrOfdH?miycW5cg;v2X0< z2cZfW){@Mq&xUVfwSMwO?GPr)ZcsA~wy9AZWSqwYoV{o|=hr#EPkx zme*!FC5IfdiV=}vf}!QGOU-#6eTyW-{s6{Jr|eXGe04F%eu?^;qXCLpiT48U5$a0a zC~W4vAXAM>rfp7&po=|1=(Hb`lnfh+ptc}i5liMlC2+0gED>*OmnAVe=eRlpKGXAvc zk46G@7YuELXL`dqDpa*A27YQICi4OgbOBSbvLjzeTIUxzEvg@=9tE!XPp?;9?N+3G7w?A}) z?|fSUyafz;oA5ksJAazRnRlMZ63rUUQaf*h?9%H>)TV1UN?b&%YjY<)U zoYg=wvCe)!>^VDO6JNl-wW2vy2>h&@9CEZm^R`)j4=KRE2 z>&}96)p&of`+-`8Fy>;yw1X@@6JA2uB1M1wG;t9*QJ&<7s=#va3S(F@tL3A@Ul66F;!;)KHIaB z5#_r4pkiJ?;A)Em;=yc8>;Q6i0J=-NhO#+kci*acsBZGqPHL=4m&9zGW`o zxY$kgP1&||p%u|OzMfw#ieDuan-$dM8_u$e6+1MlC#kokH;3pjfJ~fA+YgSTPs~k> zuMbr&jv@k6Xz0||#1}g?fM0URLRr{c+SCG1LQQf_5=hUOlE}IYXYZt-u7M^|7w>|#ISe=n zqVbfpu3kxAiHu^8U4O5x4dac;X2_;WnxuNDlBjmcv~jhrxNNPIimxVXe?QwxG&3;F(X7Y!)|Yx?5<@VhPXn?HeWp@r$}%y#}hr!1#!|`hC)Bo9ifwHDi7eXDgonRoI!wL%qFmT&09xi)4u~b<0v2 zGh=4HGpdG)ukC)5T%94`a6@W z?u`3-{myItnDco(&pFR|&YUxUJ?E{_Fnw3wJ0;*~GoFz1BCRW0xHy!3e!3oQ?&C}fBDmMXZO%C@kQ z8>bWRT5g9|E*pFKP`8Jw;oC2YzTH|N;wDL1>dN|5T%T&y=`rcy63tC)X_R)?t(;56 zl#DGSxW4L{@bbt@+NsHHF^|h@wFui^r1iC$ZB+V#n$_QWIni&1$aT5fwaTq1PWri2 z?yMh9HRnY_;NYo)bj>|Jh*c#c-#^f@#4K6^zt&Y-w#$@so3%AJot*2fQJF13&%~2; zr|#Z0wY^++*@^i1t(aau3d-LINfm!;JvTOZ2d;a=Tp;&+D=6psZj z<7(_A*oC>@!t2NVF5d$$QsB`-i+gZQ>cM%|U2(aji*pxJZYbdH4N1hFQ?7YB`amL< zT|+&#v(6pWvZc8*S|_*Zf%*#Z>)#@-t@U0h7q$CgU+yifv{_!mlc)FU?*!B&?@QQq zR%Phm)QW^CPT5CwrNAZUJ^NOumN|v>HJN0+yJEO3-tz*HGK=^m7G?ZuWO=(uw&^L? z=}njFa21ezqw4d71`W~nz?>NKtF};LPC>MbGOHs(AN3%Vdp^3>*b~*6pLqNDrTpdt znhvG#xc8B-UtKk%`ggX~(nQ)BlI3LtOGZ-Y;i`ny+sNhy)|vfm+r|V-1?YItWFxFK z=-HN%oK~DoIpxUBM12cdkfd+1I`31t#J2YajSf%dIBSDeO5Ns8Gd^8O`Af7EP8j)J-a2XY6*2-ln6VN zNsCIGR~XWeT<4@?;rxeLyqNK=H)dfcb{NFd$<`ZPlGSaU{P&@)PbF)rMi^CXkLYR{ ziX0Ce*AR;s2@61^U{8o1K37=rTSyh19QoYIRqANr02GC>b$ZxPWaO{wU&%UR?zx7? z%6?Yk_+`e{NjES!>965XGpTWmr{l$C-ObfECEg5c-3xUbt=|@$)M&O|F5TD*nPHgw z6rFr_quhNKfckLK^&e;I9=x)}NvbH*ZnKAs&81iubCE{MF`b^=N>r$#4USy0x%xD( zxMVMbPE=wZWh-oty~%An480quLe8O+RR)nq65skYtSxtt8X_RsMUpX`uBd4682sw2KU{m<(a;yp2C;kL{Hj1Fc-q!d#+G_&UT zdGSGFOM1S}sG`o+nWZN+ELiUy>@65h{;7H9>3IJEg7&Ub{a&n zE7rydIdU{NvsS9^A*P7d!l&`lk`odaLFVP(r^T?GNH6rtA zm0J11JG3+Fh_wYc1;#L*y(_uQz`79sDqL>08LPQ7zP5ER^Vsr%(yi8A*?Esu{KE{| z3Q0RsSgj!i8{{U{+Tq1j(Qhf#0Al0hPT6ag0~5hilOPJ}MV!T;lI#WVL${ZvoSWF} zp4`(`bU-yhWHj%@ktb74^+S&T@jX*$%NG5<{_TF})mV8QW&1x`Mq4~0nlFoIJ&C!) zq(`*26n1#HKbq_MGUD~3m$UiCUAeY9JmbL~+%f8Gaduh-rOo~U&PRQvELwKua@E(A zwS>b{yD^dNb6nyY1Klrrd&p@2jK}hGS|?YBmGtKHIzE@azfs|_ICj9t`_Tn57tSIv2nexI$qu+5^SA|d z6K67=#-KScVU{#H1*Qq(BSVC-^1vF(v%C)b({x2qYQ=2O+h!@fhtB2!tB= z{IXm?L#Y3f7B~r3Fq?(g_{G2r1;XaeOjjzPkpKyhP)zAGiWiwm2dI*HL61(gW70tX zZ+Oa&zBA2T-;N2q{^qBc&?x*qC=#D{GNPXG0SjOND9XH`t7~ek^Gg+GIk4zd2T&J| z0R{O#8w`s9s1=w4Y+l3RF<4OO%x^IFzcmC7576U+8WO7wfGUC-=<#dw`R$*+kgzWT zkHCY9av`9mu(8B8gzk@|{U?2#fPK3sT z@Blg_sA07+i+(Ry0Fn`o537yC3GW-QLoBjGW3j?^;C%3}3x4~VKL|WVA-qQr8i^Gi z4~GYbOUM^!{8v8&#>3&!i_Q$t_!o|_9e6!KSi^w#6&7jef8>fuw{v!-(k1xMmzX*G zf~yV((>g+7I0V%mi2&O}#zOY?XnQJndIJj% k6y!i9BMB1!-((*5#9)Gdh#!|C4xC!F#MZ6*4jD-N54S%B8UO$Q diff --git a/public/about-fablab.jpg b/public/about-fablab.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a54b3035d867af8ddc9e537b5204d1dd4d41dfb GIT binary patch literal 71754 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:kDsUFRgq>A|vX*&Hq< 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%0Am$ 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 literal 0 HcmV?d00001 diff --git a/spec/factories/abuses.rb b/spec/factories/abuses.rb new file mode 100644 index 000000000..053c56201 --- /dev/null +++ b/spec/factories/abuses.rb @@ -0,0 +1,10 @@ +FactoryGirl.define do + factory :abus, :class => 'Abuse' do + signaled nil +first_name "MyString" +last_name "MyString" +email "" +message "MyText" + end + +end diff --git a/spec/factories/auth_providers.rb b/spec/factories/auth_providers.rb new file mode 100644 index 000000000..5825df85a --- /dev/null +++ b/spec/factories/auth_providers.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :auth_provider do + name "MyString" +type "" +status "MyString" + end + +end diff --git a/spec/factories/availability_tags.rb b/spec/factories/availability_tags.rb new file mode 100644 index 000000000..db9c2dafe --- /dev/null +++ b/spec/factories/availability_tags.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :availability_tag do + availability nil +tag nil + end + +end diff --git a/spec/factories/avoirs.rb b/spec/factories/avoirs.rb new file mode 100644 index 000000000..fee25ded1 --- /dev/null +++ b/spec/factories/avoirs.rb @@ -0,0 +1,6 @@ +#FactoryGirl.define do + #factory :avoir do + + #end + +#end diff --git a/spec/factories/credits.rb b/spec/factories/credits.rb new file mode 100644 index 000000000..3072f8855 --- /dev/null +++ b/spec/factories/credits.rb @@ -0,0 +1,6 @@ +#FactoryGirl.define do + #factory :credit do + + #end + +#end diff --git a/spec/factories/roles.rb b/spec/factories/custom_assets.rb similarity index 58% rename from spec/factories/roles.rb rename to spec/factories/custom_assets.rb index 3ae14f956..288d4dab1 100644 --- a/spec/factories/roles.rb +++ b/spec/factories/custom_assets.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :role do + factory :custom_asset do end diff --git a/spec/factories/database_providers.rb b/spec/factories/database_providers.rb new file mode 100644 index 000000000..2c42ffeaf --- /dev/null +++ b/spec/factories/database_providers.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :database_provider do + + end + +end diff --git a/spec/factories/invoices.rb b/spec/factories/invoices.rb new file mode 100644 index 000000000..c7b688824 --- /dev/null +++ b/spec/factories/invoices.rb @@ -0,0 +1,6 @@ +#FactoryGirl.define do + #factory :invoice do + + #end + +#end diff --git a/spec/factories/o_auth2_mappings.rb b/spec/factories/o_auth2_mappings.rb new file mode 100644 index 000000000..8945ae6d3 --- /dev/null +++ b/spec/factories/o_auth2_mappings.rb @@ -0,0 +1,10 @@ +FactoryGirl.define do + factory :o_auth2_mapping do + o_auth2_provider nil +resource_url "MyString" +local_field "MyString" +api_field "MyString" +data_type "MyString" + end + +end diff --git a/spec/factories/o_auth2_providers.rb b/spec/factories/o_auth2_providers.rb new file mode 100644 index 000000000..a6c72e70b --- /dev/null +++ b/spec/factories/o_auth2_providers.rb @@ -0,0 +1,11 @@ +FactoryGirl.define do + factory :o_auth2_provider do + base_url "MyString" +token_endpoint "MyString" +authorization_endpoint "MyString" +client_id "MyString" +client_secret "MyString" +auth_provider nil + end + +end diff --git a/spec/factories/plans.rb b/spec/factories/plans.rb new file mode 100644 index 000000000..f1a7428aa --- /dev/null +++ b/spec/factories/plans.rb @@ -0,0 +1,6 @@ +#FactoryGirl.define do + #factory :plan do + + #end + +#end diff --git a/spec/factories/prices.rb b/spec/factories/prices.rb new file mode 100644 index 000000000..1c349b91b --- /dev/null +++ b/spec/factories/prices.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :price do + group nil +plan nil +priceable nil +amount 1 + end + +end diff --git a/spec/factories/reservations.rb b/spec/factories/reservations.rb new file mode 100644 index 000000000..4cafa3f7e --- /dev/null +++ b/spec/factories/reservations.rb @@ -0,0 +1,6 @@ +#FactoryGirl.define do + #factory :reservation do + + #end + +#end diff --git a/spec/factories/stylesheets.rb b/spec/factories/stylesheets.rb new file mode 100644 index 000000000..a581bf44c --- /dev/null +++ b/spec/factories/stylesheets.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :stylesheet do + contents "MyString" + end + +end diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb new file mode 100644 index 000000000..5d145f9c5 --- /dev/null +++ b/spec/factories/subscriptions.rb @@ -0,0 +1,6 @@ +#FactoryGirl.define do + #factory :subscription do + + #end + +#end diff --git a/spec/factories/tags.rb b/spec/factories/tags.rb new file mode 100644 index 000000000..1cda84a6d --- /dev/null +++ b/spec/factories/tags.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :tag do + name "MyString" + end + +end diff --git a/spec/factories/trainings.rb b/spec/factories/trainings.rb new file mode 100644 index 000000000..47e11f9c0 --- /dev/null +++ b/spec/factories/trainings.rb @@ -0,0 +1,6 @@ +#FactoryGirl.define do + #factory :training do + + #end + +#end diff --git a/spec/factories/user_tags.rb b/spec/factories/user_tags.rb new file mode 100644 index 000000000..8f8e796da --- /dev/null +++ b/spec/factories/user_tags.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :user_tag do + user nil +tag nil + end + +end diff --git a/spec/mailers/.keep b/spec/mailers/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/spec/mailers/previews/devise_mailer_preview.rb b/spec/mailers/previews/devise_mailer_preview.rb new file mode 100644 index 000000000..d00d4411c --- /dev/null +++ b/spec/mailers/previews/devise_mailer_preview.rb @@ -0,0 +1,9 @@ +class DeviseMailerPreview < ActionMailer::Preview + def confirmation_instructions + Devise::Mailer.confirmation_instructions(User.last, SecureRandom.hex) + end + + def reset_password_instructions + Devise::Mailer.reset_password_instructions(User.last, SecureRandom.hex) + end +end diff --git a/spec/mailers/previews/notifications_mailer_preview.rb b/spec/mailers/previews/notifications_mailer_preview.rb new file mode 100644 index 000000000..c25687360 --- /dev/null +++ b/spec/mailers/previews/notifications_mailer_preview.rb @@ -0,0 +1,7 @@ +class NotificationsMailerPreview < ActionMailer::Preview + NotificationType::NAMES.each do |name| + define_method name do + NotificationsMailer.send_mail_by(Notification.where(notification_type_id: NotificationType.find_by_name(name)).last) + end + end +end diff --git a/spec/mailers/previews/users_mailer_preview.rb b/spec/mailers/previews/users_mailer_preview.rb new file mode 100644 index 000000000..9a61b3ad9 --- /dev/null +++ b/spec/mailers/previews/users_mailer_preview.rb @@ -0,0 +1,5 @@ +class UsersMailerPreview < ActionMailer::Preview + def notify_user_account_created + UsersMailer.notify_user_account_created(User.first, 'wfwwefwefsdfsdf') + end +end diff --git a/spec/models/abuse_spec.rb b/spec/models/abuse_spec.rb new file mode 100644 index 000000000..63525cbfc --- /dev/null +++ b/spec/models/abuse_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Abuse, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/auth_provider_spec.rb b/spec/models/auth_provider_spec.rb new file mode 100644 index 000000000..176f501d9 --- /dev/null +++ b/spec/models/auth_provider_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe AuthProvider, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/availability_spec.rb b/spec/models/availability_spec.rb index a0aa8b9e3..412931f92 100644 --- a/spec/models/availability_spec.rb +++ b/spec/models/availability_spec.rb @@ -7,11 +7,18 @@ RSpec.describe Availability, type: :model do it 'is invalid without start at' it 'is invalid without end at' it 'is invalid without type' - it 'is invalid type isnt in [event]' + it 'is invalid type isnt in [training, machines, event]' + it 'is invalid without training_ids when type training' + it 'is invalid without machine_ids when type machines' end + it 'should can associate one or many reservations' + + it 'should can destroy if dont any reservations' + it 'should get a title' it 'should set a number of places' + it 'should be completed when number of reservations equal number of places' end diff --git a/spec/models/availability_tag_spec.rb b/spec/models/availability_tag_spec.rb new file mode 100644 index 000000000..3107b4b33 --- /dev/null +++ b/spec/models/availability_tag_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe AvailabilityTag, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/avoir_spec.rb b/spec/models/avoir_spec.rb new file mode 100644 index 000000000..85d095e97 --- /dev/null +++ b/spec/models/avoir_spec.rb @@ -0,0 +1,9 @@ +require 'rails_helper' + +RSpec.describe Avoir, type: :model do + it 'should generate a reference after create' + + it 'is invoiced if avoir mode isnt in [stripe, cheque, transfer, none, cash]' + + it 'should can expire user subscription if avoir indicate subscription to expire' +end diff --git a/spec/models/credit_spec.rb b/spec/models/credit_spec.rb new file mode 100644 index 000000000..358bc728f --- /dev/null +++ b/spec/models/credit_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +RSpec.describe Credit, type: :model do + context 'training' do + it 'should is saved with plan' + end + + context 'machine' do + it 'should is saved with plan and hours' + end +end diff --git a/spec/models/custom_asset_spec.rb b/spec/models/custom_asset_spec.rb new file mode 100644 index 000000000..0ff1263fd --- /dev/null +++ b/spec/models/custom_asset_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe CustomAsset, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/database_provider_spec.rb b/spec/models/database_provider_spec.rb new file mode 100644 index 000000000..4b43e2344 --- /dev/null +++ b/spec/models/database_provider_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe DatabaseProvider, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/invoice_spec.rb b/spec/models/invoice_spec.rb new file mode 100644 index 000000000..7d1a78204 --- /dev/null +++ b/spec/models/invoice_spec.rb @@ -0,0 +1,9 @@ +require 'rails_helper' + +RSpec.describe Invoice, type: :model do + it 'should generate a reference after create' + + it 'should generate a invoice pdf' + + it 'should can build a avoir' +end diff --git a/spec/models/o_auth2_mapping_spec.rb b/spec/models/o_auth2_mapping_spec.rb new file mode 100644 index 000000000..9022984ed --- /dev/null +++ b/spec/models/o_auth2_mapping_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe OAuth2Mapping, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/o_auth2_provider_spec.rb b/spec/models/o_auth2_provider_spec.rb new file mode 100644 index 000000000..67f2cd8cd --- /dev/null +++ b/spec/models/o_auth2_provider_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe OAuth2Provider, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/plan_spec.rb b/spec/models/plan_spec.rb new file mode 100644 index 000000000..9133ce664 --- /dev/null +++ b/spec/models/plan_spec.rb @@ -0,0 +1,60 @@ +require 'rails_helper' + +RSpec.describe Plan, type: :model do + let(:group){ Group.new(name: 'groupe test', slug: SecureRandom.hex) } + + describe 'validations' do + it 'is success with amount and group' do + plan = Plan.new(amount: 500, group: group) + expect(plan).to be_valid + end + + it 'is invalid without amount' do + plan = Plan.new(group: group) + expect(plan).to be_invalid + end + + it 'is invalid without group' do + plan = Plan.new(amount: 500) + expect(plan).to be_invalid + end + end + + context "on creation" do + before :each do + @plan_id = SecureRandom.hex + @plan_name = SecureRandom.hex + allow(Stripe::Plan).to receive(:create).and_return(double(id: @plan_id, name: @plan_name)) + end + + it 'calls Stripe::Plan create method' do + plan = Plan.create(amount: 500, interval: 'month', group: group) + expect(Stripe::Plan).to have_received :create + end + + it 'saves stripe_plan.id' do + plan = Plan.create(amount: 500, interval: 'month', group: group) + expect(plan.stp_plan_id).to eq(@plan_id) + end + + it 'saves stripe_plan.name' do + plan = Plan.create(amount: 500, interval: 'month', group: group) + expect(plan.name).to eq(@plan_name) + end + end + + context "on update" do + before :each do + allow(Stripe::Plan).to receive(:create).and_return(double(id: SecureRandom.hex, name: SecureRandom.hex)) + end + + let(:plan){ Plan.create(amount: 500, interval: 'month', group: group) } + + describe "update_stripe_plan" do + it "should return false if plan already has subscriptions" do + allow(plan).to receive(:subscriptions).and_return([1,2]) + expect(plan.send(:update_stripe_plan)).to eq(false) + end + end + end +end diff --git a/spec/models/price_spec.rb b/spec/models/price_spec.rb new file mode 100644 index 000000000..5ce528be5 --- /dev/null +++ b/spec/models/price_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Price, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/reservation_spec.rb b/spec/models/reservation_spec.rb new file mode 100644 index 000000000..51cbeebd6 --- /dev/null +++ b/spec/models/reservation_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +RSpec.describe Reservation, type: :model do + describe 'create' do + it 'is success with user, slots and reservable' + it 'is invalid if reservable isnt in [Training, Machine, Event]' + + it 'is success with a subscription' + + context 'stripe' do + it 'should payment success' + it 'is invalid if payment info invalid' + end + + context 'satori' do + it 'should success' + it 'is invalid if payment info invalid' + end + end + + it 'should update user credit after creation' + + it 'should create a invoice' + + it 'should can set a nb reserve places' + + it 'should can set a nb reserve reduced places' +end diff --git a/spec/models/stylesheet_spec.rb b/spec/models/stylesheet_spec.rb new file mode 100644 index 000000000..68291eb3b --- /dev/null +++ b/spec/models/stylesheet_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Stylesheet, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb new file mode 100644 index 000000000..ffa54d5bd --- /dev/null +++ b/spec/models/subscription_spec.rb @@ -0,0 +1,26 @@ +require 'rails_helper' + +RSpec.describe Subscription, type: :model do + describe 'create' do + it 'is success with user, plan' + + it 'is success with a subscription' + + context 'stripe' do + it 'should payment success' + it 'is invalid if payment info invalid' + end + + context 'satori' do + it 'should success' + it 'is invalid if payment info invalid' + end + end + + it 'should reset user credit after creation' + + it 'should set a expired at after creation' + + it 'should create a invoice' + +end diff --git a/spec/models/role_spec.rb b/spec/models/tag_spec.rb similarity index 69% rename from spec/models/role_spec.rb rename to spec/models/tag_spec.rb index 41d406052..0d0fcb057 100644 --- a/spec/models/role_spec.rb +++ b/spec/models/tag_spec.rb @@ -1,5 +1,5 @@ require 'rails_helper' -RSpec.describe Role, type: :model do +RSpec.describe Tag, type: :model do pending "add some examples to (or delete) #{__FILE__}" end diff --git a/spec/models/training_spec.rb b/spec/models/training_spec.rb new file mode 100644 index 000000000..b3fae28ba --- /dev/null +++ b/spec/models/training_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +RSpec.describe Training, type: :model do + + describe 'create' do + it 'is success with name' + it 'is invalid without name' + it 'should auto generate slug by name' + end + + it 'can associate many machines' + + it 'can return a user list that valided by traning' + + it 'should return an amount by user group' + + it 'should can add a number place' +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 19af17f6b..e6fef8ef6 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -62,6 +62,13 @@ RSpec.describe User, type: :model do member = create(:user) expect(member.is_member?).to be true end + + it 'create a stripe customer' do + member = create(:user) + allow(member).to receive(:create_stripe_customer) { |u| member.stp_customer_id = 'stripe customer id' } + member.run_callbacks(:commit) + expect(member.stp_customer_id).to eq 'stripe customer id' + end end end diff --git a/spec/models/user_tag_spec.rb b/spec/models/user_tag_spec.rb new file mode 100644 index 000000000..11a927391 --- /dev/null +++ b/spec/models/user_tag_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe UserTag, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/test/controllers/.keep b/test/controllers/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/.keep b/test/fixtures/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/helpers/.keep b/test/helpers/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/.keep b/test/integration/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 000000000..3acf9ae30 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,7 @@ +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) +require 'rails/test_help' + +class ActiveSupport::TestCase + # Add more helper methods to be used by all tests here... +end diff --git a/vendor/assets/components/angular-animate/.bower.json b/vendor/assets/components/angular-animate/.bower.json new file mode 100644 index 000000000..695775890 --- /dev/null +++ b/vendor/assets/components/angular-animate/.bower.json @@ -0,0 +1,20 @@ +{ + "name": "angular-animate", + "version": "1.3.20", + "main": "./angular-animate.js", + "ignore": [], + "dependencies": { + "angular": "1.3.20" + }, + "homepage": "https://github.com/angular/bower-angular-animate", + "_release": "1.3.20", + "_resolution": { + "type": "version", + "tag": "v1.3.20", + "commit": "a0d4208f770315df80866fcb449eff913efbbbdc" + }, + "_source": "git://github.com/angular/bower-angular-animate.git", + "_target": "1.3.20", + "_originalSource": "angular-animate", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-animate/README.md b/vendor/assets/components/angular-animate/README.md new file mode 100644 index 000000000..8313da67c --- /dev/null +++ b/vendor/assets/components/angular-animate/README.md @@ -0,0 +1,68 @@ +# packaged angular-animate + +This repo is for distribution on `npm` and `bower`. The source for this module is in the +[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngAnimate). +Please file issues and pull requests against that repo. + +## Install + +You can install this package either with `npm` or with `bower`. + +### npm + +```shell +npm install angular-animate +``` + +Then add `ngAnimate` as a dependency for your app: + +```javascript +angular.module('myApp', [require('angular-animate')]); +``` + +### bower + +```shell +bower install angular-animate +``` + +Then add a ` +``` + +Then add `ngAnimate` as a dependency for your app: + +```javascript +angular.module('myApp', ['ngAnimate']); +``` + +## Documentation + +Documentation is available on the +[AngularJS docs site](http://docs.angularjs.org/api/ngAnimate). + +## License + +The MIT License + +Copyright (c) 2010-2015 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/assets/components/angular-animate/angular-animate.js b/vendor/assets/components/angular-animate/angular-animate.js new file mode 100644 index 000000000..d0a3f54dc --- /dev/null +++ b/vendor/assets/components/angular-animate/angular-animate.js @@ -0,0 +1,2142 @@ +/** + * @license AngularJS v1.3.20 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/* jshint maxlen: false */ + +/** + * @ngdoc module + * @name ngAnimate + * @description + * + * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives. + * + *

    + * + * # Usage + * + * To see animations in action, all that is required is to define the appropriate CSS classes + * or to register a JavaScript animation via the `myModule.animation()` function. The directives that support animation automatically are: + * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation + * by using the `$animate` service. + * + * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives: + * + * | Directive | Supported Animations | + * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| + * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move | + * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave | + * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave | + * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave | + * | {@link ng.directive:ngIf#animations ngIf} | enter and leave | + * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) | + * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) | + * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) | + * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) | + * | {@link module:ngMessages#animations ngMessage} | enter and leave | + * + * You can find out more information about animations upon visiting each directive page. + * + * Below is an example of how to apply animations to a directive that supports animation hooks: + * + * ```html + * + * + * + * + * ``` + * + * Keep in mind that, by default, if an animation is running, any child elements cannot be animated + * until the parent element's animation has completed. This blocking feature can be overridden by + * placing the `ng-animate-children` attribute on a parent container tag. + * + * ```html + *
    + *
    + *
    + * ... + *
    + *
    + *
    + * ``` + * + * When the `on` expression value changes and an animation is triggered then each of the elements within + * will all animate without the block being applied to child elements. + * + * ## Are animations run when the application starts? + * No they are not. When an application is bootstrapped Angular will disable animations from running to avoid + * a frenzy of animations from being triggered as soon as the browser has rendered the screen. For this to work, + * Angular will wait for two digest cycles until enabling animations. From there on, any animation-triggering + * layout changes in the application will trigger animations as normal. + * + * In addition, upon bootstrap, if the routing system or any directives or load remote data (via $http) then Angular + * will automatically extend the wait time to enable animations once **all** of the outbound HTTP requests + * are complete. + * + * ## CSS-defined Animations + * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes + * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported + * and can be used to play along with this naming structure. + * + * The following code below demonstrates how to perform animations using **CSS transitions** with Angular: + * + * ```html + * + * + *
    + *
    + *
    + * ``` + * + * The following code below demonstrates how to perform animations using **CSS animations** with Angular: + * + * ```html + * + * + *
    + *
    + *
    + * ``` + * + * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing. + * + * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add + * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically + * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be + * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end + * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element + * has no CSS transition/animation classes applied to it. + * + * ### Structural transition animations + * + * Structural transitions (such as enter, leave and move) will always apply a `0s none` transition + * value to force the browser into rendering the styles defined in the setup (`.ng-enter`, `.ng-leave` + * or `.ng-move`) class. This means that any active transition animations operating on the element + * will be cut off to make way for the enter, leave or move animation. + * + * ### Class-based transition animations + * + * Class-based transitions refer to transition animations that are triggered when a CSS class is + * added to or removed from the element (via `$animate.addClass`, `$animate.removeClass`, + * `$animate.setClass`, or by directives such as `ngClass`, `ngModel` and `form`). + * They are different when compared to structural animations since they **do not cancel existing + * animations** nor do they **block successive transitions** from rendering on the same element. + * This distinction allows for **multiple class-based transitions** to be performed on the same element. + * + * In addition to ngAnimate supporting the default (natural) functionality of class-based transition + * animations, ngAnimate also decorates the element with starting and ending CSS classes to aid the + * developer in further styling the element throughout the transition animation. Earlier versions + * of ngAnimate may have caused natural CSS transitions to break and not render properly due to + * $animate temporarily blocking transitions using `0s none` in order to allow the setup CSS class + * (the `-add` or `-remove` class) to be applied without triggering an animation. However, as of + * **version 1.3**, this workaround has been removed with ngAnimate and all non-ngAnimate CSS + * class transitions are compatible with ngAnimate. + * + * There is, however, one special case when dealing with class-based transitions in ngAnimate. + * When rendering class-based transitions that make use of the setup and active CSS classes + * (e.g. `.fade-add` and `.fade-add-active` for when `.fade` is added) be sure to define + * the transition value **on the active CSS class** and not the setup class. + * + * ```css + * .fade-add { + * /* remember to place a 0s transition here + * to ensure that the styles are applied instantly + * even if the element already has a transition style */ + * transition:0s linear all; + * + * /* starting CSS styles */ + * opacity:1; + * } + * .fade-add.fade-add-active { + * /* this will be the length of the animation */ + * transition:1s linear all; + * opacity:0; + * } + * ``` + * + * The setup CSS class (in this case `.fade-add`) also has a transition style property, however, it + * has a duration of zero. This may not be required, however, incase the browser is unable to render + * the styling present in this CSS class instantly then it could be that the browser is attempting + * to perform an unnecessary transition. + * + * This workaround, however, does not apply to standard class-based transitions that are rendered + * when a CSS class containing a transition is applied to an element: + * + * ```css + * /* this works as expected */ + * .fade { + * transition:1s linear all; + * opacity:0; + * } + * ``` + * + * Please keep this in mind when coding the CSS markup that will be used within class-based transitions. + * Also, try not to mix the two class-based animation flavors together since the CSS code may become + * overly complex. + * + * + * ### Preventing Collisions With Third Party Libraries + * + * Some third-party frameworks place animation duration defaults across many element or className + * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which + * is expecting actual animations on these elements and has to wait for their completion. + * + * You can prevent this unwanted behavior by using a prefix on all your animation classes: + * + * ```css + * /* prefixed with animate- */ + * .animate-fade-add.animate-fade-add-active { + * transition:1s linear all; + * opacity:0; + * } + * ``` + * + * You then configure `$animate` to enforce this prefix: + * + * ```js + * $animateProvider.classNameFilter(/animate-/); + * ``` + *
    + * + * ### CSS Staggering Animations + * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a + * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be + * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for + * the animation. The style property expected within the stagger class can either be a **transition-delay** or an + * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). + * + * ```css + * .my-animation.ng-enter { + * /* standard transition code */ + * -webkit-transition: 1s linear all; + * transition: 1s linear all; + * opacity:0; + * } + * .my-animation.ng-enter-stagger { + * /* this will have a 100ms delay between each successive leave animation */ + * -webkit-transition-delay: 0.1s; + * transition-delay: 0.1s; + * + * /* in case the stagger doesn't work then these two values + * must be set to 0 to avoid an accidental CSS inheritance */ + * -webkit-transition-duration: 0s; + * transition-duration: 0s; + * } + * .my-animation.ng-enter.ng-enter-active { + * /* standard transition styles */ + * opacity:1; + * } + * ``` + * + * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations + * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this + * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation + * will also be reset if more than 10ms has passed after the last animation has been fired. + * + * The following code will issue the **ng-leave-stagger** event on the element provided: + * + * ```js + * var kids = parent.children(); + * + * $animate.leave(kids[0]); //stagger index=0 + * $animate.leave(kids[1]); //stagger index=1 + * $animate.leave(kids[2]); //stagger index=2 + * $animate.leave(kids[3]); //stagger index=3 + * $animate.leave(kids[4]); //stagger index=4 + * + * $timeout(function() { + * //stagger has reset itself + * $animate.leave(kids[5]); //stagger index=0 + * $animate.leave(kids[6]); //stagger index=1 + * }, 100, false); + * ``` + * + * Stagger animations are currently only supported within CSS-defined animations. + * + * ## JavaScript-defined Animations + * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not + * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module. + * + * ```js + * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application. + * var ngModule = angular.module('YourApp', ['ngAnimate']); + * ngModule.animation('.my-crazy-animation', function() { + * return { + * enter: function(element, done) { + * //run the animation here and call done when the animation is complete + * return function(cancelled) { + * //this (optional) function will be called when the animation + * //completes or when the animation is cancelled (the cancelled + * //flag will be set to true if cancelled). + * }; + * }, + * leave: function(element, done) { }, + * move: function(element, done) { }, + * + * //animation that can be triggered before the class is added + * beforeAddClass: function(element, className, done) { }, + * + * //animation that can be triggered after the class is added + * addClass: function(element, className, done) { }, + * + * //animation that can be triggered before the class is removed + * beforeRemoveClass: function(element, className, done) { }, + * + * //animation that can be triggered after the class is removed + * removeClass: function(element, className, done) { } + * }; + * }); + * ``` + * + * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run + * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits + * the element's CSS class attribute value and then run the matching animation event function (if found). + * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will + * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported). + * + * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned. + * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run, + * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation + * or transition code that is defined via a stylesheet). + * + * + * ### Applying Directive-specific Styles to an Animation + * In some cases a directive or service may want to provide `$animate` with extra details that the animation will + * include into its animation. Let's say for example we wanted to render an animation that animates an element + * towards the mouse coordinates as to where the user clicked last. By collecting the X/Y coordinates of the click + * (via the event parameter) we can set the `top` and `left` styles into an object and pass that into our function + * call to `$animate.addClass`. + * + * ```js + * canvas.on('click', function(e) { + * $animate.addClass(element, 'on', { + * to: { + * left : e.client.x + 'px', + * top : e.client.y + 'px' + * } + * }): + * }); + * ``` + * + * Now when the animation runs, and a transition or keyframe animation is picked up, then the animation itself will + * also include and transition the styling of the `left` and `top` properties into its running animation. If we want + * to provide some starting animation values then we can do so by placing the starting animations styles into an object + * called `from` in the same object as the `to` animations. + * + * ```js + * canvas.on('click', function(e) { + * $animate.addClass(element, 'on', { + * from: { + * position: 'absolute', + * left: '0px', + * top: '0px' + * }, + * to: { + * left : e.client.x + 'px', + * top : e.client.y + 'px' + * } + * }): + * }); + * ``` + * + * Once the animation is complete or cancelled then the union of both the before and after styles are applied to the + * element. If `ngAnimate` is not present then the styles will be applied immediately. + * + */ + +angular.module('ngAnimate', ['ng']) + + /** + * @ngdoc provider + * @name $animateProvider + * @description + * + * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module. + * When an animation is triggered, the $animate service will query the $animate service to find any animations that match + * the provided name value. + * + * Requires the {@link ngAnimate `ngAnimate`} module to be installed. + * + * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. + * + */ + .directive('ngAnimateChildren', function() { + var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; + return function(scope, element, attrs) { + var val = attrs.ngAnimateChildren; + if (angular.isString(val) && val.length === 0) { //empty attribute + element.data(NG_ANIMATE_CHILDREN, true); + } else { + scope.$watch(val, function(value) { + element.data(NG_ANIMATE_CHILDREN, !!value); + }); + } + }; + }) + + //this private service is only used within CSS-enabled animations + //IE8 + IE9 do not support rAF natively, but that is fine since they + //also don't support transitions and keyframes which means that the code + //below will never be used by the two browsers. + .factory('$$animateReflow', ['$$rAF', '$document', function($$rAF, $document) { + var bod = $document[0].body; + return function(fn) { + //the returned function acts as the cancellation function + return $$rAF(function() { + //the line below will force the browser to perform a repaint + //so that all the animated elements within the animation frame + //will be properly updated and drawn on screen. This is + //required to perform multi-class CSS based animations with + //Firefox. DO NOT REMOVE THIS LINE. DO NOT OPTIMIZE THIS LINE. + //THE MINIFIER WILL REMOVE IT OTHERWISE WHICH WILL RESULT IN AN + //UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND WILL + //TAKE YEARS AWAY FROM YOUR LIFE! + fn(bod.offsetWidth); + }); + }; + }]) + + .config(['$provide', '$animateProvider', function($provide, $animateProvider) { + var noop = angular.noop; + var forEach = angular.forEach; + var selectors = $animateProvider.$$selectors; + var isArray = angular.isArray; + var isString = angular.isString; + var isObject = angular.isObject; + + var ELEMENT_NODE = 1; + var NG_ANIMATE_STATE = '$$ngAnimateState'; + var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; + var NG_ANIMATE_CLASS_NAME = 'ng-animate'; + var rootAnimateState = {running: true}; + + function extractElementNode(element) { + for (var i = 0; i < element.length; i++) { + var elm = element[i]; + if (elm.nodeType == ELEMENT_NODE) { + return elm; + } + } + } + + function prepareElement(element) { + return element && angular.element(element); + } + + function stripCommentsFromElement(element) { + return angular.element(extractElementNode(element)); + } + + function isMatchingElement(elm1, elm2) { + return extractElementNode(elm1) == extractElementNode(elm2); + } + var $$jqLite; + $provide.decorator('$animate', + ['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest', '$$jqLite', + function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest, $$$jqLite) { + + $$jqLite = $$$jqLite; + $rootElement.data(NG_ANIMATE_STATE, rootAnimateState); + + // Wait until all directive and route-related templates are downloaded and + // compiled. The $templateRequest.totalPendingRequests variable keeps track of + // all of the remote templates being currently downloaded. If there are no + // templates currently downloading then the watcher will still fire anyway. + var deregisterWatch = $rootScope.$watch( + function() { return $templateRequest.totalPendingRequests; }, + function(val, oldVal) { + if (val !== 0) return; + deregisterWatch(); + + // Now that all templates have been downloaded, $animate will wait until + // the post digest queue is empty before enabling animations. By having two + // calls to $postDigest calls we can ensure that the flag is enabled at the + // very end of the post digest queue. Since all of the animations in $animate + // use $postDigest, it's important that the code below executes at the end. + // This basically means that the page is fully downloaded and compiled before + // any animations are triggered. + $rootScope.$$postDigest(function() { + $rootScope.$$postDigest(function() { + rootAnimateState.running = false; + }); + }); + } + ); + + var globalAnimationCounter = 0; + var classNameFilter = $animateProvider.classNameFilter(); + var isAnimatableClassName = !classNameFilter + ? function() { return true; } + : function(className) { + return classNameFilter.test(className); + }; + + function classBasedAnimationsBlocked(element, setter) { + var data = element.data(NG_ANIMATE_STATE) || {}; + if (setter) { + data.running = true; + data.structural = true; + element.data(NG_ANIMATE_STATE, data); + } + return data.disabled || (data.running && data.structural); + } + + function runAnimationPostDigest(fn) { + var cancelFn, defer = $$q.defer(); + defer.promise.$$cancelFn = function() { + cancelFn && cancelFn(); + }; + $rootScope.$$postDigest(function() { + cancelFn = fn(function() { + defer.resolve(); + }); + }); + return defer.promise; + } + + function parseAnimateOptions(options) { + // some plugin code may still be passing in the callback + // function as the last param for the $animate methods so + // it's best to only allow string or array values for now + if (isObject(options)) { + if (options.tempClasses && isString(options.tempClasses)) { + options.tempClasses = options.tempClasses.split(/\s+/); + } + return options; + } + } + + function resolveElementClasses(element, cache, runningAnimations) { + runningAnimations = runningAnimations || {}; + + var lookup = {}; + forEach(runningAnimations, function(data, selector) { + forEach(selector.split(' '), function(s) { + lookup[s]=data; + }); + }); + + var hasClasses = Object.create(null); + forEach((element.attr('class') || '').split(/\s+/), function(className) { + hasClasses[className] = true; + }); + + var toAdd = [], toRemove = []; + forEach((cache && cache.classes) || [], function(status, className) { + var hasClass = hasClasses[className]; + var matchingAnimation = lookup[className] || {}; + + // When addClass and removeClass is called then $animate will check to + // see if addClass and removeClass cancel each other out. When there are + // more calls to removeClass than addClass then the count falls below 0 + // and then the removeClass animation will be allowed. Otherwise if the + // count is above 0 then that means an addClass animation will commence. + // Once an animation is allowed then the code will also check to see if + // there exists any on-going animation that is already adding or remvoing + // the matching CSS class. + if (status === false) { + //does it have the class or will it have the class + if (hasClass || matchingAnimation.event == 'addClass') { + toRemove.push(className); + } + } else if (status === true) { + //is the class missing or will it be removed? + if (!hasClass || matchingAnimation.event == 'removeClass') { + toAdd.push(className); + } + } + }); + + return (toAdd.length + toRemove.length) > 0 && [toAdd.join(' '), toRemove.join(' ')]; + } + + function lookup(name) { + if (name) { + var matches = [], + flagMap = {}, + classes = name.substr(1).split('.'); + + //the empty string value is the default animation + //operation which performs CSS transition and keyframe + //animations sniffing. This is always included for each + //element animation procedure if the browser supports + //transitions and/or keyframe animations. The default + //animation is added to the top of the list to prevent + //any previous animations from affecting the element styling + //prior to the element being animated. + if ($sniffer.transitions || $sniffer.animations) { + matches.push($injector.get(selectors[''])); + } + + for (var i=0; i < classes.length; i++) { + var klass = classes[i], + selectorFactoryName = selectors[klass]; + if (selectorFactoryName && !flagMap[klass]) { + matches.push($injector.get(selectorFactoryName)); + flagMap[klass] = true; + } + } + return matches; + } + } + + function animationRunner(element, animationEvent, className, options) { + //transcluded directives may sometimes fire an animation using only comment nodes + //best to catch this early on to prevent any animation operations from occurring + var node = element[0]; + if (!node) { + return; + } + + if (options) { + options.to = options.to || {}; + options.from = options.from || {}; + } + + var classNameAdd; + var classNameRemove; + if (isArray(className)) { + classNameAdd = className[0]; + classNameRemove = className[1]; + if (!classNameAdd) { + className = classNameRemove; + animationEvent = 'removeClass'; + } else if (!classNameRemove) { + className = classNameAdd; + animationEvent = 'addClass'; + } else { + className = classNameAdd + ' ' + classNameRemove; + } + } + + var isSetClassOperation = animationEvent == 'setClass'; + var isClassBased = isSetClassOperation + || animationEvent == 'addClass' + || animationEvent == 'removeClass' + || animationEvent == 'animate'; + + var currentClassName = element.attr('class'); + var classes = currentClassName + ' ' + className; + if (!isAnimatableClassName(classes)) { + return; + } + + var beforeComplete = noop, + beforeCancel = [], + before = [], + afterComplete = noop, + afterCancel = [], + after = []; + + var animationLookup = (' ' + classes).replace(/\s+/g,'.'); + forEach(lookup(animationLookup), function(animationFactory) { + var created = registerAnimation(animationFactory, animationEvent); + if (!created && isSetClassOperation) { + registerAnimation(animationFactory, 'addClass'); + registerAnimation(animationFactory, 'removeClass'); + } + }); + + function registerAnimation(animationFactory, event) { + var afterFn = animationFactory[event]; + var beforeFn = animationFactory['before' + event.charAt(0).toUpperCase() + event.substr(1)]; + if (afterFn || beforeFn) { + if (event == 'leave') { + beforeFn = afterFn; + //when set as null then animation knows to skip this phase + afterFn = null; + } + after.push({ + event: event, fn: afterFn + }); + before.push({ + event: event, fn: beforeFn + }); + return true; + } + } + + function run(fns, cancellations, allCompleteFn) { + var animations = []; + forEach(fns, function(animation) { + animation.fn && animations.push(animation); + }); + + var count = 0; + function afterAnimationComplete(index) { + if (cancellations) { + (cancellations[index] || noop)(); + if (++count < animations.length) return; + cancellations = null; + } + allCompleteFn(); + } + + //The code below adds directly to the array in order to work with + //both sync and async animations. Sync animations are when the done() + //operation is called right away. DO NOT REFACTOR! + forEach(animations, function(animation, index) { + var progress = function() { + afterAnimationComplete(index); + }; + switch (animation.event) { + case 'setClass': + cancellations.push(animation.fn(element, classNameAdd, classNameRemove, progress, options)); + break; + case 'animate': + cancellations.push(animation.fn(element, className, options.from, options.to, progress)); + break; + case 'addClass': + cancellations.push(animation.fn(element, classNameAdd || className, progress, options)); + break; + case 'removeClass': + cancellations.push(animation.fn(element, classNameRemove || className, progress, options)); + break; + default: + cancellations.push(animation.fn(element, progress, options)); + break; + } + }); + + if (cancellations && cancellations.length === 0) { + allCompleteFn(); + } + } + + return { + node: node, + event: animationEvent, + className: className, + isClassBased: isClassBased, + isSetClassOperation: isSetClassOperation, + applyStyles: function() { + if (options) { + element.css(angular.extend(options.from || {}, options.to || {})); + } + }, + before: function(allCompleteFn) { + beforeComplete = allCompleteFn; + run(before, beforeCancel, function() { + beforeComplete = noop; + allCompleteFn(); + }); + }, + after: function(allCompleteFn) { + afterComplete = allCompleteFn; + run(after, afterCancel, function() { + afterComplete = noop; + allCompleteFn(); + }); + }, + cancel: function() { + if (beforeCancel) { + forEach(beforeCancel, function(cancelFn) { + (cancelFn || noop)(true); + }); + beforeComplete(true); + } + if (afterCancel) { + forEach(afterCancel, function(cancelFn) { + (cancelFn || noop)(true); + }); + afterComplete(true); + } + } + }; + } + + /** + * @ngdoc service + * @name $animate + * @kind object + * + * @description + * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations. + * When any of these operations are run, the $animate service + * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object) + * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run. + * + * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives + * will work out of the box without any extra configuration. + * + * Requires the {@link ngAnimate `ngAnimate`} module to be installed. + * + * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. + * ## Callback Promises + * With AngularJS 1.3, each of the animation methods, on the `$animate` service, return a promise when called. The + * promise itself is then resolved once the animation has completed itself, has been cancelled or has been + * skipped due to animations being disabled. (Note that even if the animation is cancelled it will still + * call the resolve function of the animation.) + * + * ```js + * $animate.enter(element, container).then(function() { + * //...this is called once the animation is complete... + * }); + * ``` + * + * Also note that, due to the nature of the callback promise, if any Angular-specific code (like changing the scope, + * location of the page, etc...) is executed within the callback promise then be sure to wrap the code using + * `$scope.$apply(...)`; + * + * ```js + * $animate.leave(element).then(function() { + * $scope.$apply(function() { + * $location.path('/new-page'); + * }); + * }); + * ``` + * + * An animation can also be cancelled by calling the `$animate.cancel(promise)` method with the provided + * promise that was returned when the animation was started. + * + * ```js + * var promise = $animate.addClass(element, 'super-long-animation'); + * promise.then(function() { + * //this will still be called even if cancelled + * }); + * + * element.on('click', function() { + * //tooo lazy to wait for the animation to end + * $animate.cancel(promise); + * }); + * ``` + * + * (Keep in mind that the promise cancellation is unique to `$animate` since promises in + * general cannot be cancelled.) + * + */ + return { + /** + * @ngdoc method + * @name $animate#animate + * @kind function + * + * @description + * Performs an inline animation on the element which applies the provided `to` and `from` CSS styles to the element. + * If any detected CSS transition, keyframe or JavaScript matches the provided `className` value then the animation + * will take on the provided styles. For example, if a transition animation is set for the given className then the + * provided `from` and `to` styles will be applied alongside the given transition. If a JavaScript animation is + * detected then the provided styles will be given in as function paramters. + * + * ```js + * ngModule.animation('.my-inline-animation', function() { + * return { + * animate : function(element, className, from, to, done) { + * //styles + * } + * } + * }); + * ``` + * + * Below is a breakdown of each step that occurs during the `animate` animation: + * + * | Animation Step | What the element class attribute looks like | + * |-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| + * | 1. `$animate.animate(...)` is called | `class="my-animation"` | + * | 2. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` | + * | 3. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` | + * | 4. the `className` class value is added to the element | `class="my-animation ng-animate className"` | + * | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate className"` | + * | 6. `$animate` blocks all CSS transitions on the element to ensure the `.className` class styling is applied right away| `class="my-animation ng-animate className"` | + * | 7. `$animate` applies the provided collection of `from` CSS styles to the element | `class="my-animation ng-animate className"` | + * | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate className"` | + * | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate className"` | + * | 10. the `className-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate className className-active"` | + * | 11. `$animate` applies the collection of `to` CSS styles to the element which are then handled by the transition | `class="my-animation ng-animate className className-active"` | + * | 12. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate className className-active"` | + * | 13. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` | + * | 14. The returned promise is resolved. | `class="my-animation"` | + * + * @param {DOMElement} element the element that will be the focus of the enter animation + * @param {object} from a collection of CSS styles that will be applied to the element at the start of the animation + * @param {object} to a collection of CSS styles that the element will animate towards + * @param {string=} className an optional CSS class that will be added to the element for the duration of the animation (the default class is `ng-inline-animate`) + * @param {object=} options an optional collection of options that will be picked up by the CSS transition/animation + * @return {Promise} the animation callback promise + */ + animate: function(element, from, to, className, options) { + className = className || 'ng-inline-animate'; + options = parseAnimateOptions(options) || {}; + options.from = to ? from : null; + options.to = to ? to : from; + + return runAnimationPostDigest(function(done) { + return performAnimation('animate', className, stripCommentsFromElement(element), null, null, noop, options, done); + }); + }, + + /** + * @ngdoc method + * @name $animate#enter + * @kind function + * + * @description + * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once + * the animation is started, the following CSS classes will be present on the element for the duration of the animation: + * + * Below is a breakdown of each step that occurs during enter animation: + * + * | Animation Step | What the element class attribute looks like | + * |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------| + * | 1. `$animate.enter(...)` is called | `class="my-animation"` | + * | 2. element is inserted into the `parentElement` element or beside the `afterElement` element | `class="my-animation"` | + * | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` | + * | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` | + * | 5. the `.ng-enter` class is added to the element | `class="my-animation ng-animate ng-enter"` | + * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-enter"` | + * | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-enter` class styling is applied right away | `class="my-animation ng-animate ng-enter"` | + * | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-enter"` | + * | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-enter"` | + * | 10. the `.ng-enter-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-enter ng-enter-active"` | + * | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-enter ng-enter-active"` | + * | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` | + * | 13. The returned promise is resolved. | `class="my-animation"` | + * + * @param {DOMElement} element the element that will be the focus of the enter animation + * @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation + * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation + * @param {object=} options an optional collection of options that will be picked up by the CSS transition/animation + * @return {Promise} the animation callback promise + */ + enter: function(element, parentElement, afterElement, options) { + options = parseAnimateOptions(options); + element = angular.element(element); + parentElement = prepareElement(parentElement); + afterElement = prepareElement(afterElement); + + classBasedAnimationsBlocked(element, true); + $delegate.enter(element, parentElement, afterElement); + return runAnimationPostDigest(function(done) { + return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done); + }); + }, + + /** + * @ngdoc method + * @name $animate#leave + * @kind function + * + * @description + * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once + * the animation is started, the following CSS classes will be added for the duration of the animation: + * + * Below is a breakdown of each step that occurs during leave animation: + * + * | Animation Step | What the element class attribute looks like | + * |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------| + * | 1. `$animate.leave(...)` is called | `class="my-animation"` | + * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` | + * | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` | + * | 4. the `.ng-leave` class is added to the element | `class="my-animation ng-animate ng-leave"` | + * | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-leave"` | + * | 6. `$animate` blocks all CSS transitions on the element to ensure the `.ng-leave` class styling is applied right away | `class="my-animation ng-animate ng-leave"` | + * | 7. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-leave"` | + * | 8. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-leave"` | + * | 9. the `.ng-leave-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-leave ng-leave-active"` | + * | 10. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-leave ng-leave-active"` | + * | 11. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` | + * | 12. The element is removed from the DOM | ... | + * | 13. The returned promise is resolved. | ... | + * + * @param {DOMElement} element the element that will be the focus of the leave animation + * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation + * @return {Promise} the animation callback promise + */ + leave: function(element, options) { + options = parseAnimateOptions(options); + element = angular.element(element); + + cancelChildAnimations(element); + classBasedAnimationsBlocked(element, true); + return runAnimationPostDigest(function(done) { + return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() { + $delegate.leave(element); + }, options, done); + }); + }, + + /** + * @ngdoc method + * @name $animate#move + * @kind function + * + * @description + * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or + * add the element directly after the afterElement element if present. Then the move animation will be run. Once + * the animation is started, the following CSS classes will be added for the duration of the animation: + * + * Below is a breakdown of each step that occurs during move animation: + * + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| + * | 1. `$animate.move(...)` is called | `class="my-animation"` | + * | 2. element is moved into the parentElement element or beside the afterElement element | `class="my-animation"` | + * | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` | + * | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` | + * | 5. the `.ng-move` class is added to the element | `class="my-animation ng-animate ng-move"` | + * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-move"` | + * | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-move` class styling is applied right away | `class="my-animation ng-animate ng-move"` | + * | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-move"` | + * | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-move"` | + * | 10. the `.ng-move-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-move ng-move-active"` | + * | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-move ng-move-active"` | + * | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` | + * | 13. The returned promise is resolved. | `class="my-animation"` | + * + * @param {DOMElement} element the element that will be the focus of the move animation + * @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation + * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation + * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation + * @return {Promise} the animation callback promise + */ + move: function(element, parentElement, afterElement, options) { + options = parseAnimateOptions(options); + element = angular.element(element); + parentElement = prepareElement(parentElement); + afterElement = prepareElement(afterElement); + + cancelChildAnimations(element); + classBasedAnimationsBlocked(element, true); + $delegate.move(element, parentElement, afterElement); + return runAnimationPostDigest(function(done) { + return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done); + }); + }, + + /** + * @ngdoc method + * @name $animate#addClass + * + * @description + * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class. + * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide + * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions + * or keyframes are defined on the -add-active or base CSS class). + * + * Below is a breakdown of each step that occurs during addClass animation: + * + * | Animation Step | What the element class attribute looks like | + * |--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| + * | 1. `$animate.addClass(element, 'super')` is called | `class="my-animation"` | + * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` | + * | 3. the `.super-add` class is added to the element | `class="my-animation ng-animate super-add"` | + * | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate super-add"` | + * | 5. the `.super` and `.super-add-active` classes are added (this triggers the CSS transition/animation) | `class="my-animation ng-animate super super-add super-add-active"` | + * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super super-add super-add-active"` | + * | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super super-add super-add-active"` | + * | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation super"` | + * | 9. The super class is kept on the element | `class="my-animation super"` | + * | 10. The returned promise is resolved. | `class="my-animation super"` | + * + * @param {DOMElement} element the element that will be animated + * @param {string} className the CSS class that will be added to the element and then animated + * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation + * @return {Promise} the animation callback promise + */ + addClass: function(element, className, options) { + return this.setClass(element, className, [], options); + }, + + /** + * @ngdoc method + * @name $animate#removeClass + * + * @description + * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value + * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in + * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if + * no CSS transitions or keyframes are defined on the -remove or base CSS classes). + * + * Below is a breakdown of each step that occurs during removeClass animation: + * + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| + * | 1. `$animate.removeClass(element, 'super')` is called | `class="my-animation super"` | + * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation super ng-animate"` | + * | 3. the `.super-remove` class is added to the element | `class="my-animation super ng-animate super-remove"` | + * | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation super ng-animate super-remove"` | + * | 5. the `.super-remove-active` classes are added and `.super` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate super-remove super-remove-active"` | + * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super-remove super-remove-active"` | + * | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super-remove super-remove-active"` | + * | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` | + * | 9. The returned promise is resolved. | `class="my-animation"` | + * + * + * @param {DOMElement} element the element that will be animated + * @param {string} className the CSS class that will be animated and then removed from the element + * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation + * @return {Promise} the animation callback promise + */ + removeClass: function(element, className, options) { + return this.setClass(element, [], className, options); + }, + + /** + * + * @ngdoc method + * @name $animate#setClass + * + * @description Adds and/or removes the given CSS classes to and from the element. + * Once complete, the `done()` callback will be fired (if provided). + * + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------| + * | 1. `$animate.setClass(element, 'on', 'off')` is called | `class="my-animation off"` | + * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate off"` | + * | 3. the `.on-add` and `.off-remove` classes are added to the element | `class="my-animation ng-animate on-add off-remove off"` | + * | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate on-add off-remove off"` | + * | 5. the `.on`, `.on-add-active` and `.off-remove-active` classes are added and `.off` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` | + * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` | + * | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` | + * | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation on"` | + * | 9. The returned promise is resolved. | `class="my-animation on"` | + * + * @param {DOMElement} element the element which will have its CSS classes changed + * removed from it + * @param {string} add the CSS classes which will be added to the element + * @param {string} remove the CSS class which will be removed from the element + * CSS classes have been set on the element + * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation + * @return {Promise} the animation callback promise + */ + setClass: function(element, add, remove, options) { + options = parseAnimateOptions(options); + + var STORAGE_KEY = '$$animateClasses'; + element = angular.element(element); + element = stripCommentsFromElement(element); + + if (classBasedAnimationsBlocked(element)) { + return $delegate.$$setClassImmediately(element, add, remove, options); + } + + // we're using a combined array for both the add and remove + // operations since the ORDER OF addClass and removeClass matters + var classes, cache = element.data(STORAGE_KEY); + var hasCache = !!cache; + if (!cache) { + cache = {}; + cache.classes = {}; + } + classes = cache.classes; + + add = isArray(add) ? add : add.split(' '); + forEach(add, function(c) { + if (c && c.length) { + classes[c] = true; + } + }); + + remove = isArray(remove) ? remove : remove.split(' '); + forEach(remove, function(c) { + if (c && c.length) { + classes[c] = false; + } + }); + + if (hasCache) { + if (options && cache.options) { + cache.options = angular.extend(cache.options || {}, options); + } + + //the digest cycle will combine all the animations into one function + return cache.promise; + } else { + element.data(STORAGE_KEY, cache = { + classes: classes, + options: options + }); + } + + return cache.promise = runAnimationPostDigest(function(done) { + var cache, parentNode, parentElement, elementNode = extractElementNode(element); + if (elementNode) { + cache = element.data(STORAGE_KEY); + element.removeData(STORAGE_KEY); + + parentElement = element.parent(); + parentNode = elementNode.parentNode; + } + + // TODO(matsko): move this code into the animationsDisabled() function once #8092 is fixed + if (!parentNode || parentNode['$$NG_REMOVED'] || elementNode['$$NG_REMOVED']) { + done(); + return; + } + + var state = element.data(NG_ANIMATE_STATE) || {}; + var classes = resolveElementClasses(element, cache, state.active); + return !classes + ? done() + : performAnimation('setClass', classes, element, parentElement, null, function() { + if (classes[0]) $delegate.$$addClassImmediately(element, classes[0]); + if (classes[1]) $delegate.$$removeClassImmediately(element, classes[1]); + }, cache.options, done); + }); + }, + + /** + * @ngdoc method + * @name $animate#cancel + * @kind function + * + * @param {Promise} animationPromise The animation promise that is returned when an animation is started. + * + * @description + * Cancels the provided animation. + */ + cancel: function(promise) { + promise.$$cancelFn(); + }, + + /** + * @ngdoc method + * @name $animate#enabled + * @kind function + * + * @param {boolean=} value If provided then set the animation on or off. + * @param {DOMElement=} element If provided then the element will be used to represent the enable/disable operation + * @return {boolean} Current animation state. + * + * @description + * Globally enables/disables animations. + * + */ + enabled: function(value, element) { + switch (arguments.length) { + case 2: + if (value) { + cleanup(element); + } else { + var data = element.data(NG_ANIMATE_STATE) || {}; + data.disabled = true; + element.data(NG_ANIMATE_STATE, data); + } + break; + + case 1: + rootAnimateState.disabled = !value; + break; + + default: + value = !rootAnimateState.disabled; + break; + } + return !!value; + } + }; + + /* + all animations call this shared animation triggering function internally. + The animationEvent variable refers to the JavaScript animation event that will be triggered + and the className value is the name of the animation that will be applied within the + CSS code. Element, `parentElement` and `afterElement` are provided DOM elements for the animation + and the onComplete callback will be fired once the animation is fully complete. + */ + function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) { + var noopCancel = noop; + var runner = animationRunner(element, animationEvent, className, options); + if (!runner) { + fireDOMOperation(); + fireBeforeCallbackAsync(); + fireAfterCallbackAsync(); + closeAnimation(); + return noopCancel; + } + + animationEvent = runner.event; + className = runner.className; + var elementEvents = angular.element._data(runner.node); + elementEvents = elementEvents && elementEvents.events; + + if (!parentElement) { + parentElement = afterElement ? afterElement.parent() : element.parent(); + } + + //skip the animation if animations are disabled, a parent is already being animated, + //the element is not currently attached to the document body or then completely close + //the animation if any matching animations are not found at all. + //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case an animation was found. + if (animationsDisabled(element, parentElement)) { + fireDOMOperation(); + fireBeforeCallbackAsync(); + fireAfterCallbackAsync(); + closeAnimation(); + return noopCancel; + } + + var ngAnimateState = element.data(NG_ANIMATE_STATE) || {}; + var runningAnimations = ngAnimateState.active || {}; + var totalActiveAnimations = ngAnimateState.totalActive || 0; + var lastAnimation = ngAnimateState.last; + var skipAnimation = false; + + if (totalActiveAnimations > 0) { + var animationsToCancel = []; + if (!runner.isClassBased) { + if (animationEvent == 'leave' && runningAnimations['ng-leave']) { + skipAnimation = true; + } else { + //cancel all animations when a structural animation takes place + for (var klass in runningAnimations) { + animationsToCancel.push(runningAnimations[klass]); + } + ngAnimateState = {}; + cleanup(element, true); + } + } else if (lastAnimation.event == 'setClass') { + animationsToCancel.push(lastAnimation); + cleanup(element, className); + } else if (runningAnimations[className]) { + var current = runningAnimations[className]; + if (current.event == animationEvent) { + skipAnimation = true; + } else { + animationsToCancel.push(current); + cleanup(element, className); + } + } + + if (animationsToCancel.length > 0) { + forEach(animationsToCancel, function(operation) { + operation.cancel(); + }); + } + } + + if (runner.isClassBased + && !runner.isSetClassOperation + && animationEvent != 'animate' + && !skipAnimation) { + skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR + } + + if (skipAnimation) { + fireDOMOperation(); + fireBeforeCallbackAsync(); + fireAfterCallbackAsync(); + fireDoneCallbackAsync(); + return noopCancel; + } + + runningAnimations = ngAnimateState.active || {}; + totalActiveAnimations = ngAnimateState.totalActive || 0; + + if (animationEvent == 'leave') { + //there's no need to ever remove the listener since the element + //will be removed (destroyed) after the leave animation ends or + //is cancelled midway + element.one('$destroy', function(e) { + var element = angular.element(this); + var state = element.data(NG_ANIMATE_STATE); + if (state) { + var activeLeaveAnimation = state.active['ng-leave']; + if (activeLeaveAnimation) { + activeLeaveAnimation.cancel(); + cleanup(element, 'ng-leave'); + } + } + }); + } + + //the ng-animate class does nothing, but it's here to allow for + //parent animations to find and cancel child animations when needed + $$jqLite.addClass(element, NG_ANIMATE_CLASS_NAME); + if (options && options.tempClasses) { + forEach(options.tempClasses, function(className) { + $$jqLite.addClass(element, className); + }); + } + + var localAnimationCount = globalAnimationCounter++; + totalActiveAnimations++; + runningAnimations[className] = runner; + + element.data(NG_ANIMATE_STATE, { + last: runner, + active: runningAnimations, + index: localAnimationCount, + totalActive: totalActiveAnimations + }); + + //first we run the before animations and when all of those are complete + //then we perform the DOM operation and run the next set of animations + fireBeforeCallbackAsync(); + runner.before(function(cancelled) { + var data = element.data(NG_ANIMATE_STATE); + cancelled = cancelled || + !data || !data.active[className] || + (runner.isClassBased && data.active[className].event != animationEvent); + + fireDOMOperation(); + if (cancelled === true) { + closeAnimation(); + } else { + fireAfterCallbackAsync(); + runner.after(closeAnimation); + } + }); + + return runner.cancel; + + function fireDOMCallback(animationPhase) { + var eventName = '$animate:' + animationPhase; + if (elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) { + $$asyncCallback(function() { + element.triggerHandler(eventName, { + event: animationEvent, + className: className + }); + }); + } + } + + function fireBeforeCallbackAsync() { + fireDOMCallback('before'); + } + + function fireAfterCallbackAsync() { + fireDOMCallback('after'); + } + + function fireDoneCallbackAsync() { + fireDOMCallback('close'); + doneCallback(); + } + + //it is less complicated to use a flag than managing and canceling + //timeouts containing multiple callbacks. + function fireDOMOperation() { + if (!fireDOMOperation.hasBeenRun) { + fireDOMOperation.hasBeenRun = true; + domOperation(); + } + } + + function closeAnimation() { + if (!closeAnimation.hasBeenRun) { + if (runner) { //the runner doesn't exist if it fails to instantiate + runner.applyStyles(); + } + + closeAnimation.hasBeenRun = true; + if (options && options.tempClasses) { + forEach(options.tempClasses, function(className) { + $$jqLite.removeClass(element, className); + }); + } + + var data = element.data(NG_ANIMATE_STATE); + if (data) { + + /* only structural animations wait for reflow before removing an + animation, but class-based animations don't. An example of this + failing would be when a parent HTML tag has a ng-class attribute + causing ALL directives below to skip animations during the digest */ + if (runner && runner.isClassBased) { + cleanup(element, className); + } else { + $$asyncCallback(function() { + var data = element.data(NG_ANIMATE_STATE) || {}; + if (localAnimationCount == data.index) { + cleanup(element, className, animationEvent); + } + }); + element.data(NG_ANIMATE_STATE, data); + } + } + fireDoneCallbackAsync(); + } + } + } + + function cancelChildAnimations(element) { + var node = extractElementNode(element); + if (node) { + var nodes = angular.isFunction(node.getElementsByClassName) ? + node.getElementsByClassName(NG_ANIMATE_CLASS_NAME) : + node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME); + forEach(nodes, function(element) { + element = angular.element(element); + var data = element.data(NG_ANIMATE_STATE); + if (data && data.active) { + forEach(data.active, function(runner) { + runner.cancel(); + }); + } + }); + } + } + + function cleanup(element, className) { + if (isMatchingElement(element, $rootElement)) { + if (!rootAnimateState.disabled) { + rootAnimateState.running = false; + rootAnimateState.structural = false; + } + } else if (className) { + var data = element.data(NG_ANIMATE_STATE) || {}; + + var removeAnimations = className === true; + if (!removeAnimations && data.active && data.active[className]) { + data.totalActive--; + delete data.active[className]; + } + + if (removeAnimations || !data.totalActive) { + $$jqLite.removeClass(element, NG_ANIMATE_CLASS_NAME); + element.removeData(NG_ANIMATE_STATE); + } + } + } + + function animationsDisabled(element, parentElement) { + if (rootAnimateState.disabled) { + return true; + } + + if (isMatchingElement(element, $rootElement)) { + return rootAnimateState.running; + } + + var allowChildAnimations, parentRunningAnimation, hasParent; + do { + //the element did not reach the root element which means that it + //is not apart of the DOM. Therefore there is no reason to do + //any animations on it + if (parentElement.length === 0) break; + + var isRoot = isMatchingElement(parentElement, $rootElement); + var state = isRoot ? rootAnimateState : (parentElement.data(NG_ANIMATE_STATE) || {}); + if (state.disabled) { + return true; + } + + //no matter what, for an animation to work it must reach the root element + //this implies that the element is attached to the DOM when the animation is run + if (isRoot) { + hasParent = true; + } + + //once a flag is found that is strictly false then everything before + //it will be discarded and all child animations will be restricted + if (allowChildAnimations !== false) { + var animateChildrenFlag = parentElement.data(NG_ANIMATE_CHILDREN); + if (angular.isDefined(animateChildrenFlag)) { + allowChildAnimations = animateChildrenFlag; + } + } + + parentRunningAnimation = parentRunningAnimation || + state.running || + (state.last && !state.last.isClassBased); + } + while (parentElement = parentElement.parent()); + + return !hasParent || (!allowChildAnimations && parentRunningAnimation); + } + }]); + + $animateProvider.register('', ['$window', '$sniffer', '$timeout', '$$animateReflow', + function($window, $sniffer, $timeout, $$animateReflow) { + // Detect proper transitionend/animationend event names. + var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT; + + // If unprefixed events are not supported but webkit-prefixed are, use the latter. + // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them. + // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend` + // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`. + // Register both events in case `window.onanimationend` is not supported because of that, + // do the same for `transitionend` as Safari is likely to exhibit similar behavior. + // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit + // therefore there is no reason to test anymore for other vendor prefixes: http://caniuse.com/#search=transition + if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) { + CSS_PREFIX = '-webkit-'; + TRANSITION_PROP = 'WebkitTransition'; + TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend'; + } else { + TRANSITION_PROP = 'transition'; + TRANSITIONEND_EVENT = 'transitionend'; + } + + if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) { + CSS_PREFIX = '-webkit-'; + ANIMATION_PROP = 'WebkitAnimation'; + ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend'; + } else { + ANIMATION_PROP = 'animation'; + ANIMATIONEND_EVENT = 'animationend'; + } + + var DURATION_KEY = 'Duration'; + var PROPERTY_KEY = 'Property'; + var DELAY_KEY = 'Delay'; + var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount'; + var ANIMATION_PLAYSTATE_KEY = 'PlayState'; + var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey'; + var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data'; + var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3; + var CLOSING_TIME_BUFFER = 1.5; + var ONE_SECOND = 1000; + + var lookupCache = {}; + var parentCounter = 0; + var animationReflowQueue = []; + var cancelAnimationReflow; + function clearCacheAfterReflow() { + if (!cancelAnimationReflow) { + cancelAnimationReflow = $$animateReflow(function() { + animationReflowQueue = []; + cancelAnimationReflow = null; + lookupCache = {}; + }); + } + } + + function afterReflow(element, callback) { + if (cancelAnimationReflow) { + cancelAnimationReflow(); + } + animationReflowQueue.push(callback); + cancelAnimationReflow = $$animateReflow(function() { + forEach(animationReflowQueue, function(fn) { + fn(); + }); + + animationReflowQueue = []; + cancelAnimationReflow = null; + lookupCache = {}; + }); + } + + var closingTimer = null; + var closingTimestamp = 0; + var animationElementQueue = []; + function animationCloseHandler(element, totalTime) { + var node = extractElementNode(element); + element = angular.element(node); + + //this item will be garbage collected by the closing + //animation timeout + animationElementQueue.push(element); + + //but it may not need to cancel out the existing timeout + //if the timestamp is less than the previous one + var futureTimestamp = Date.now() + totalTime; + if (futureTimestamp <= closingTimestamp) { + return; + } + + $timeout.cancel(closingTimer); + + closingTimestamp = futureTimestamp; + closingTimer = $timeout(function() { + closeAllAnimations(animationElementQueue); + animationElementQueue = []; + }, totalTime, false); + } + + function closeAllAnimations(elements) { + forEach(elements, function(element) { + var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY); + if (elementData) { + forEach(elementData.closeAnimationFns, function(fn) { + fn(); + }); + } + }); + } + + function getElementAnimationDetails(element, cacheKey) { + var data = cacheKey ? lookupCache[cacheKey] : null; + if (!data) { + var transitionDuration = 0; + var transitionDelay = 0; + var animationDuration = 0; + var animationDelay = 0; + + //we want all the styles defined before and after + forEach(element, function(element) { + if (element.nodeType == ELEMENT_NODE) { + var elementStyles = $window.getComputedStyle(element) || {}; + + var transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY]; + transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration); + + var transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY]; + transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay); + + var animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY]; + animationDelay = Math.max(parseMaxTime(elementStyles[ANIMATION_PROP + DELAY_KEY]), animationDelay); + + var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]); + + if (aDuration > 0) { + aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1; + } + animationDuration = Math.max(aDuration, animationDuration); + } + }); + data = { + total: 0, + transitionDelay: transitionDelay, + transitionDuration: transitionDuration, + animationDelay: animationDelay, + animationDuration: animationDuration + }; + if (cacheKey) { + lookupCache[cacheKey] = data; + } + } + return data; + } + + function parseMaxTime(str) { + var maxValue = 0; + var values = isString(str) ? + str.split(/\s*,\s*/) : + []; + forEach(values, function(value) { + maxValue = Math.max(parseFloat(value) || 0, maxValue); + }); + return maxValue; + } + + function getCacheKey(element) { + var parentElement = element.parent(); + var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY); + if (!parentID) { + parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter); + parentID = parentCounter; + } + return parentID + '-' + extractElementNode(element).getAttribute('class'); + } + + function animateSetup(animationEvent, element, className, styles) { + var structural = ['ng-enter','ng-leave','ng-move'].indexOf(className) >= 0; + + var cacheKey = getCacheKey(element); + var eventCacheKey = cacheKey + ' ' + className; + var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0; + + var stagger = {}; + if (itemIndex > 0) { + var staggerClassName = className + '-stagger'; + var staggerCacheKey = cacheKey + ' ' + staggerClassName; + var applyClasses = !lookupCache[staggerCacheKey]; + + applyClasses && $$jqLite.addClass(element, staggerClassName); + + stagger = getElementAnimationDetails(element, staggerCacheKey); + + applyClasses && $$jqLite.removeClass(element, staggerClassName); + } + + $$jqLite.addClass(element, className); + + var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {}; + var timings = getElementAnimationDetails(element, eventCacheKey); + var transitionDuration = timings.transitionDuration; + var animationDuration = timings.animationDuration; + + if (structural && transitionDuration === 0 && animationDuration === 0) { + $$jqLite.removeClass(element, className); + return false; + } + + var blockTransition = styles || (structural && transitionDuration > 0); + var blockAnimation = animationDuration > 0 && + stagger.animationDelay > 0 && + stagger.animationDuration === 0; + + var closeAnimationFns = formerData.closeAnimationFns || []; + element.data(NG_ANIMATE_CSS_DATA_KEY, { + stagger: stagger, + cacheKey: eventCacheKey, + running: formerData.running || 0, + itemIndex: itemIndex, + blockTransition: blockTransition, + closeAnimationFns: closeAnimationFns + }); + + var node = extractElementNode(element); + + if (blockTransition) { + blockTransitions(node, true); + if (styles) { + element.css(styles); + } + } + + if (blockAnimation) { + blockAnimations(node, true); + } + + return true; + } + + function animateRun(animationEvent, element, className, activeAnimationComplete, styles) { + var node = extractElementNode(element); + var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY); + if (node.getAttribute('class').indexOf(className) == -1 || !elementData) { + activeAnimationComplete(); + return; + } + + var activeClassName = ''; + var pendingClassName = ''; + forEach(className.split(' '), function(klass, i) { + var prefix = (i > 0 ? ' ' : '') + klass; + activeClassName += prefix + '-active'; + pendingClassName += prefix + '-pending'; + }); + + var style = ''; + var appliedStyles = []; + var itemIndex = elementData.itemIndex; + var stagger = elementData.stagger; + var staggerTime = 0; + if (itemIndex > 0) { + var transitionStaggerDelay = 0; + if (stagger.transitionDelay > 0 && stagger.transitionDuration === 0) { + transitionStaggerDelay = stagger.transitionDelay * itemIndex; + } + + var animationStaggerDelay = 0; + if (stagger.animationDelay > 0 && stagger.animationDuration === 0) { + animationStaggerDelay = stagger.animationDelay * itemIndex; + appliedStyles.push(CSS_PREFIX + 'animation-play-state'); + } + + staggerTime = Math.round(Math.max(transitionStaggerDelay, animationStaggerDelay) * 100) / 100; + } + + if (!staggerTime) { + $$jqLite.addClass(element, activeClassName); + if (elementData.blockTransition) { + blockTransitions(node, false); + } + } + + var eventCacheKey = elementData.cacheKey + ' ' + activeClassName; + var timings = getElementAnimationDetails(element, eventCacheKey); + var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration); + if (maxDuration === 0) { + $$jqLite.removeClass(element, activeClassName); + animateClose(element, className); + activeAnimationComplete(); + return; + } + + if (!staggerTime && styles && Object.keys(styles).length > 0) { + if (!timings.transitionDuration) { + element.css('transition', timings.animationDuration + 's linear all'); + appliedStyles.push('transition'); + } + element.css(styles); + } + + var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay); + var maxDelayTime = maxDelay * ONE_SECOND; + + if (appliedStyles.length > 0) { + //the element being animated may sometimes contain comment nodes in + //the jqLite object, so we're safe to use a single variable to house + //the styles since there is always only one element being animated + var oldStyle = node.getAttribute('style') || ''; + if (oldStyle.charAt(oldStyle.length - 1) !== ';') { + oldStyle += ';'; + } + node.setAttribute('style', oldStyle + ' ' + style); + } + + var startTime = Date.now(); + var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT; + var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER; + var totalTime = (staggerTime + animationTime) * ONE_SECOND; + + var staggerTimeout; + if (staggerTime > 0) { + $$jqLite.addClass(element, pendingClassName); + staggerTimeout = $timeout(function() { + staggerTimeout = null; + + if (timings.transitionDuration > 0) { + blockTransitions(node, false); + } + if (timings.animationDuration > 0) { + blockAnimations(node, false); + } + + $$jqLite.addClass(element, activeClassName); + $$jqLite.removeClass(element, pendingClassName); + + if (styles) { + if (timings.transitionDuration === 0) { + element.css('transition', timings.animationDuration + 's linear all'); + } + element.css(styles); + appliedStyles.push('transition'); + } + }, staggerTime * ONE_SECOND, false); + } + + element.on(css3AnimationEvents, onAnimationProgress); + elementData.closeAnimationFns.push(function() { + onEnd(); + activeAnimationComplete(); + }); + + elementData.running++; + animationCloseHandler(element, totalTime); + return onEnd; + + // This will automatically be called by $animate so + // there is no need to attach this internally to the + // timeout done method. + function onEnd() { + element.off(css3AnimationEvents, onAnimationProgress); + $$jqLite.removeClass(element, activeClassName); + $$jqLite.removeClass(element, pendingClassName); + if (staggerTimeout) { + $timeout.cancel(staggerTimeout); + } + animateClose(element, className); + var node = extractElementNode(element); + for (var i in appliedStyles) { + node.style.removeProperty(appliedStyles[i]); + } + } + + function onAnimationProgress(event) { + event.stopPropagation(); + var ev = event.originalEvent || event; + var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now(); + + /* Firefox (or possibly just Gecko) likes to not round values up + * when a ms measurement is used for the animation */ + var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)); + + /* $manualTimeStamp is a mocked timeStamp value which is set + * within browserTrigger(). This is only here so that tests can + * mock animations properly. Real events fallback to event.timeStamp, + * or, if they don't, then a timeStamp is automatically created for them. + * We're checking to see if the timeStamp surpasses the expected delay, + * but we're using elapsedTime instead of the timeStamp on the 2nd + * pre-condition since animations sometimes close off early */ + if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { + activeAnimationComplete(); + } + } + } + + function blockTransitions(node, bool) { + node.style[TRANSITION_PROP + PROPERTY_KEY] = bool ? 'none' : ''; + } + + function blockAnimations(node, bool) { + node.style[ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY] = bool ? 'paused' : ''; + } + + function animateBefore(animationEvent, element, className, styles) { + if (animateSetup(animationEvent, element, className, styles)) { + return function(cancelled) { + cancelled && animateClose(element, className); + }; + } + } + + function animateAfter(animationEvent, element, className, afterAnimationComplete, styles) { + if (element.data(NG_ANIMATE_CSS_DATA_KEY)) { + return animateRun(animationEvent, element, className, afterAnimationComplete, styles); + } else { + animateClose(element, className); + afterAnimationComplete(); + } + } + + function animate(animationEvent, element, className, animationComplete, options) { + //If the animateSetup function doesn't bother returning a + //cancellation function then it means that there is no animation + //to perform at all + var preReflowCancellation = animateBefore(animationEvent, element, className, options.from); + if (!preReflowCancellation) { + clearCacheAfterReflow(); + animationComplete(); + return; + } + + //There are two cancellation functions: one is before the first + //reflow animation and the second is during the active state + //animation. The first function will take care of removing the + //data from the element which will not make the 2nd animation + //happen in the first place + var cancel = preReflowCancellation; + afterReflow(element, function() { + //once the reflow is complete then we point cancel to + //the new cancellation function which will remove all of the + //animation properties from the active animation + cancel = animateAfter(animationEvent, element, className, animationComplete, options.to); + }); + + return function(cancelled) { + (cancel || noop)(cancelled); + }; + } + + function animateClose(element, className) { + $$jqLite.removeClass(element, className); + var data = element.data(NG_ANIMATE_CSS_DATA_KEY); + if (data) { + if (data.running) { + data.running--; + } + if (!data.running || data.running === 0) { + element.removeData(NG_ANIMATE_CSS_DATA_KEY); + } + } + } + + return { + animate: function(element, className, from, to, animationCompleted, options) { + options = options || {}; + options.from = from; + options.to = to; + return animate('animate', element, className, animationCompleted, options); + }, + + enter: function(element, animationCompleted, options) { + options = options || {}; + return animate('enter', element, 'ng-enter', animationCompleted, options); + }, + + leave: function(element, animationCompleted, options) { + options = options || {}; + return animate('leave', element, 'ng-leave', animationCompleted, options); + }, + + move: function(element, animationCompleted, options) { + options = options || {}; + return animate('move', element, 'ng-move', animationCompleted, options); + }, + + beforeSetClass: function(element, add, remove, animationCompleted, options) { + options = options || {}; + var className = suffixClasses(remove, '-remove') + ' ' + + suffixClasses(add, '-add'); + var cancellationMethod = animateBefore('setClass', element, className, options.from); + if (cancellationMethod) { + afterReflow(element, animationCompleted); + return cancellationMethod; + } + clearCacheAfterReflow(); + animationCompleted(); + }, + + beforeAddClass: function(element, className, animationCompleted, options) { + options = options || {}; + var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), options.from); + if (cancellationMethod) { + afterReflow(element, animationCompleted); + return cancellationMethod; + } + clearCacheAfterReflow(); + animationCompleted(); + }, + + beforeRemoveClass: function(element, className, animationCompleted, options) { + options = options || {}; + var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), options.from); + if (cancellationMethod) { + afterReflow(element, animationCompleted); + return cancellationMethod; + } + clearCacheAfterReflow(); + animationCompleted(); + }, + + setClass: function(element, add, remove, animationCompleted, options) { + options = options || {}; + remove = suffixClasses(remove, '-remove'); + add = suffixClasses(add, '-add'); + var className = remove + ' ' + add; + return animateAfter('setClass', element, className, animationCompleted, options.to); + }, + + addClass: function(element, className, animationCompleted, options) { + options = options || {}; + return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted, options.to); + }, + + removeClass: function(element, className, animationCompleted, options) { + options = options || {}; + return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted, options.to); + } + }; + + function suffixClasses(classes, suffix) { + var className = ''; + classes = isArray(classes) ? classes : classes.split(/\s+/); + forEach(classes, function(klass, i) { + if (klass && klass.length > 0) { + className += (i > 0 ? ' ' : '') + klass + suffix; + } + }); + return className; + } + }]); + }]); + + +})(window, window.angular); diff --git a/vendor/assets/components/angular-animate/angular-animate.min.js b/vendor/assets/components/angular-animate/angular-animate.min.js new file mode 100644 index 000000000..70c25a381 --- /dev/null +++ b/vendor/assets/components/angular-animate/angular-animate.min.js @@ -0,0 +1,33 @@ +/* + AngularJS v1.3.20 + (c) 2010-2014 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(N,f,W){'use strict';f.module("ngAnimate",["ng"]).directive("ngAnimateChildren",function(){return function(X,r,g){g=g.ngAnimateChildren;f.isString(g)&&0===g.length?r.data("$$ngAnimateChildren",!0):X.$watch(g,function(f){r.data("$$ngAnimateChildren",!!f)})}}).factory("$$animateReflow",["$$rAF","$document",function(f,r){var g=r[0].body;return function(r){return f(function(){r(g.offsetWidth)})}}]).config(["$provide","$animateProvider",function(X,r){function g(f){for(var n=0;n=B&&b>=y&&c()}var m=g(e);a=e.data("$$ngAnimateCSS3Data");if(-1!=m.getAttribute("class").indexOf(b)&& +a){var l="",t="";n(b.split(" "),function(a,b){var e=(0", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/angular.js/issues" + }, + "homepage": "http://angularjs.org" +} diff --git a/vendor/assets/components/angular-base64-upload/.bower.json b/vendor/assets/components/angular-base64-upload/.bower.json new file mode 100644 index 000000000..982b3f4e1 --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/.bower.json @@ -0,0 +1,46 @@ +{ + "name": "angular-base64-upload", + "main": "src/angular-base64-upload.js", + "version": "0.0.8", + "homepage": "https://github.com/adonespitogo/angular-base64-upload", + "authors": [ + "Adones Pitogo " + ], + "description": "Angular directive for uploading base64-encoded files that you can pass along with the resource model.", + "moduleType": [ + "globals" + ], + "keywords": [ + "angularjs", + "angular", + "upload", + "base-64", + "upload", + "file", + "upload", + "ajax", + "upload" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "devDependencies": { + "angular": "~1.2.25", + "angular-mocks": "~1.2.25" + }, + "_release": "0.0.8", + "_resolution": { + "type": "version", + "tag": "v0.0.8", + "commit": "6aa47b064d4e15c42e13dc048d88069bc9d70d0b" + }, + "_source": "git://github.com/adonespitogo/angular-base64-upload.git", + "_target": "~0.0.8", + "_originalSource": "angular-base64-upload", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-base64-upload/Gruntfile.js b/vendor/assets/components/angular-base64-upload/Gruntfile.js new file mode 100644 index 000000000..89fc3ccef --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/Gruntfile.js @@ -0,0 +1,86 @@ +/*global module:false*/ +module.exports = function(grunt) { + + // Project configuration. + grunt.initConfig({ + // Metadata. + pkg: grunt.file.readJSON('package.json'), + banner: '/*! <%= pkg.title || pkg.name %> - <%= pkg.version %> - ' + + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + + '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + + '* Copyright (c) <%= pkg.author %> <%= grunt.template.today("yyyy") %>;' + + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n', + config: { + dist:'./dist', + src: './src', + js: [ + '<%= config.src %>/**/*.js' + ] + }, + clean: ['<%= config.dist %>'], + + // Task configuration. + concat: { + options: { + banner: '<%= banner %>', + stripBanners: true + }, + dist: { + src: ['<%= config.js %>'], + dest: '<%= config.dist %>/<%= pkg.name %>.js' + } + }, + uglify: { + options: { + banner: '<%= banner %>' + }, + dist: { + src: '<%= concat.dist.dest %>', + dest: '<%= config.dist %>/<%= pkg.name %>.min.js' + } + }, + jshint: { + options: { + curly: true, + eqeqeq: true, + immed: true, + latedef: true, + newcap: true, + noarg: true, + sub: true, + undef: true, + unused: true, + boss: true, + eqnull: true, + browser: true, + globals: { + angular: true + } + }, + gruntfile: { + src: 'Gruntfile.js' + }, + 'angular-base64-upload': { + src: 'src/angular-base64-upload.js' + } + }, + karma: { + unit: { + configFile: 'karma-unit.js', + background: false, + singleRun: true + } + } + }); + + // These plugins provide necessary tasks. + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-karma'); + + grunt.registerTask('build', ['clean', 'jshint', 'concat', 'uglify']); + grunt.registerTask('default', ['karma:unit']); + +}; diff --git a/vendor/assets/components/angular-base64-upload/README.md b/vendor/assets/components/angular-base64-upload/README.md new file mode 100644 index 000000000..f505b1951 --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/README.md @@ -0,0 +1,92 @@ +angular-base64-upload +===================== + +![alt tag](https://raw.github.com/adonespitogo/angular-base64-upload/master/banner.png) + +Angular directive for uploading base64-encoded files that you can pass along with the resource model. This directive is based from one of the answers in this [SO question](http://stackoverflow.com/questions/20521366/rails-4-angularjs-paperclip-how-to-upload-file). + +Note: This directive only supports single file selection. + +Installation +------------- +Bower: `bower install angular-base64-upload` + +Example +-------------------------- +See the README.md on [demo folder](https://github.com/adonespitogo/angular-base64-upload/tree/master/demo). + +Usage +------- + +Include `angular.js` and `angular-base64-upload.js` in your application and add `naif.base64` as dependency to your main module: + +``` +angular.module('myApp', ['naif.base64']); +``` + +HTML:
    +```html +
    + +
    +``` + +Sample `yourModel` value after selecting a file: +```json +{ + "filesize": 54836 (bytes), + "filetype": "image/jpeg", + "filename": "profile.jpg", + "base64": "/9j/4AAQSkZJRgABAgAAAQABAAD//gAEKgD/4gIcSUNDX1BST0ZJTEUAAQEAAAIMbGNtcwIQA..." +} +``` + +You can use the `base-sixty-four-image` directive to display image preview: + + + +If you also want to display a placeholder image, you can additionally use `base-sixty-four-image-placeholder` directive: + + + +Server-Side +--------------- + +You will have to decode the base64 file in your backend on your own. +Sample PHP code for decoding base64 file in +[demo folder](https://github.com/adonespitogo/angular-base64-upload/tree/master/demo). +Start it by cd-ing to this directory and running: + + php -S 0.0.0.0:8000 + +Then point your browser to [http://localhost:8000](). + +Below is a ruby code for decoding the base64-encoded file to be passed to paperclip: +```ruby +def create + @resource.attachment = decode_base64 + # save resource and render response ... +end + +def decode_base64 + # decode base64 string + Rails.logger.info 'decoding base64 file' + decoded_data = Base64.decode64(params[:your_model][:base64]) + # create 'file' understandable by Paperclip + data = StringIO.new(decoded_data) + data.class_eval do + attr_accessor :content_type, :original_filename + end + + # set file properties + data.content_type = params[:your_model][:filetype] + data.original_filename = params[:your_model][:filename] + + # return data to be used as the attachment file (paperclip) + data +end +``` + +## License + +Released under the terms of MIT License. diff --git a/vendor/assets/components/angular-base64-upload/banner.png b/vendor/assets/components/angular-base64-upload/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..affc1f211dd0218e518a64e54f75f8500f4905c5 GIT binary patch literal 77739 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
    (`;hJUo8L- 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

    AsI0%H1siufg%rvak#|`z8kA? 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" + ], + "description": "Angular directive for uploading base64-encoded files that you can pass along with the resource model.", + "moduleType": [ + "globals" + ], + "keywords": [ + "angularjs", + "angular", + "upload", + "base-64", + "upload", + "file", + "upload", + "ajax", + "upload" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "devDependencies": { + "angular": "~1.2.25", + "angular-mocks": "~1.2.25" + } +} diff --git a/vendor/assets/components/angular-base64-upload/demo/README.md b/vendor/assets/components/angular-base64-upload/demo/README.md new file mode 100644 index 000000000..dfd8f0d1e --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/demo/README.md @@ -0,0 +1,13 @@ +angular-base-64-upload-demo +=========================== + +![alt tag](https://raw.github.com/adonespitogo/angular-base64-upload/master/banner.png) + +Running +------- +

    Inside your clone's directory:

    + +``` +$ cd demo/ +$ php -S 127.0.0.1:8000 +``` diff --git a/vendor/assets/components/angular-base64-upload/demo/index.html b/vendor/assets/components/angular-base64-upload/demo/index.html new file mode 100644 index 000000000..56407a378 --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/demo/index.html @@ -0,0 +1,48 @@ + + + + + Angular Base64 Upload Demo + + + + + +
    + + + +
    +
    Image preview:
    + +

    File Info

    +

    + File Name: {{image.filename}} +

    +

    + File type: {{image.filetype}} +

    +

    + File Size: {{image.filesize}} bytes +

    +

    + Base 64 encoded: +

    + +

    +

    + + diff --git a/vendor/assets/components/angular-base64-upload/demo/placeholder.png b/vendor/assets/components/angular-base64-upload/demo/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..316813539a00e0056e4414347d27c3d54a844fad GIT binary patch literal 3479 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 AssI20 literal 0 HcmV?d00001 diff --git a/vendor/assets/components/angular-base64-upload/demo/server.php b/vendor/assets/components/angular-base64-upload/demo/server.php new file mode 100644 index 000000000..6463930da --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/demo/server.php @@ -0,0 +1,43 @@ +base64 = $attrs['base64']; + $this->setFileName($attrs['filename']); + $this->decodeBase64File(); + return $this; + } + + function setFileName($fileName){ + $this->fileName = $fileName; + return $this->fileName; + } + + function decodeBase64File() { + $ifp = fopen($this->fileName, 'w'); + fwrite( $ifp, base64_decode( $this->base64) ); + fclose($ifp); + return $ifp; + } + + function getFileName(){ + return $this->fileName; + } + +} + +//parse request payload +$postdata = file_get_contents("php://input"); +$request = json_decode($postdata, true); +//end parse + +$file = new Base64File($request); +echo $file->getFileName(); + +?> \ No newline at end of file diff --git a/vendor/assets/components/angular-base64-upload/dist/angular-base64-upload.js b/vendor/assets/components/angular-base64-upload/dist/angular-base64-upload.js new file mode 100644 index 000000000..fc8082c66 --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/dist/angular-base64-upload.js @@ -0,0 +1,65 @@ +/*! angular-base64-upload - v0.0.8 - 2015-04-27 +* https://github.com/adonespitogo/angular-base64-upload +* Copyright (c) Adones Pitogo 2015; Licensed */ +angular.module('naif.base64', []) +.directive('baseSixtyFourInput', ['$window', function ($window) { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, elem, attrs, ngModel) { + var fileObject = {}; + + scope.readerOnload = function(e){ + var base64 = _arrayBufferToBase64(e.target.result); + fileObject.base64 = base64; + scope.$apply(function(){ + ngModel.$setViewValue(angular.copy(fileObject)); + }); + }; + + var reader = new FileReader(); + reader.onload = scope.readerOnload; + + elem.on('change', function() { + if(!elem[0].files.length) { + return; + } + + var file = elem[0].files[0]; + fileObject.filetype = file.type; + fileObject.filename = file.name; + fileObject.filesize = file.size; + reader.readAsArrayBuffer(file); + }); + + //http://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string + function _arrayBufferToBase64( buffer ) { + var binary = ''; + var bytes = new Uint8Array( buffer ); + var len = bytes.byteLength; + for (var i = 0; i < len; i++) { + binary += String.fromCharCode( bytes[ i ] ); + } + return $window.btoa( binary ); + } + } + }; +}]) +.directive('baseSixtyFourImage', [function() { + return { + restrict: 'A', + link: function(scope, elem, attrs) { + scope.$watch(attrs.baseSixtyFourImage, function(fileObject) { + if(fileObject && fileObject.filetype && fileObject.filetype.indexOf("image") === 0) { + elem.attr("src", _assemble_data_uri(fileObject)); + } else { + elem.attr("src", attrs.baseSixtyFourImagePlaceholder); + } + }); + + function _assemble_data_uri(fileObject){ + return "data:" + fileObject.filetype + ";base64," + fileObject.base64; + } + } + }; +}]); diff --git a/vendor/assets/components/angular-base64-upload/dist/angular-base64-upload.min.js b/vendor/assets/components/angular-base64-upload/dist/angular-base64-upload.min.js new file mode 100644 index 000000000..9fab4c951 --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/dist/angular-base64-upload.min.js @@ -0,0 +1,4 @@ +/*! angular-base64-upload - v0.0.8 - 2015-04-27 +* https://github.com/adonespitogo/angular-base64-upload +* Copyright (c) Adones Pitogo 2015; Licensed */ +angular.module("naif.base64",[]).directive("baseSixtyFourInput",["$window",function(a){return{restrict:"A",require:"ngModel",link:function(b,c,d,e){function f(b){for(var c="",d=new Uint8Array(b),e=d.byteLength,f=0;e>f;f++)c+=String.fromCharCode(d[f]);return a.btoa(c)}var g={};b.readerOnload=function(a){var c=f(a.target.result);g.base64=c,b.$apply(function(){e.$setViewValue(angular.copy(g))})};var h=new FileReader;h.onload=b.readerOnload,c.on("change",function(){if(c[0].files.length){var a=c[0].files[0];g.filetype=a.type,g.filename=a.name,g.filesize=a.size,h.readAsArrayBuffer(a)}})}}}]).directive("baseSixtyFourImage",[function(){return{restrict:"A",link:function(a,b,c){function d(a){return"data:"+a.filetype+";base64,"+a.base64}a.$watch(c.baseSixtyFourImage,function(a){a&&a.filetype&&0===a.filetype.indexOf("image")?b.attr("src",d(a)):b.attr("src",c.baseSixtyFourImagePlaceholder)})}}}]); \ No newline at end of file diff --git a/vendor/assets/components/angular-base64-upload/karma-unit.js b/vendor/assets/components/angular-base64-upload/karma-unit.js new file mode 100644 index 000000000..42687f918 --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/karma-unit.js @@ -0,0 +1,64 @@ +var grunt = require('grunt'); +module.exports = function ( karma ) { + karma.set({ + /** + * From where to look for files, starting with the location of this file. + */ + basePath: '.', + + /** + * This is the list of file patterns to load into the browser during testing. + */ + files: [ + "bower_components/angular/angular.min.js", + "bower_components/angular-mocks/angular-mocks.js", + "src/angular-base64-upload.js", + "test/angular-base64-upload.spec.js" + ], + + preprocessors: { + 'src/angular-base64-upload.js': ['coverage'] + }, + + frameworks: [ 'jasmine' ], + plugins: [ 'karma-jasmine', 'karma-chrome-launcher', 'karma-coverage'], + + logLevel: 'WARN', + /** + * How to report, by default. + */ + reporters: ['progress', 'coverage'], + + coverageReporter: { + type : 'html', + dir : 'coverage/' + }, + /** + * On which port should the browser connect, on which port is the test runner + * operating, and what is the URL path for the browser to use. + */ + port: 7019, + urlRoot: '/', + + /** + * Disable file watching by default. + */ + autoWatch: false, + + /** + * The list of browsers to launch to test ondest * default, but other browser names include: + * Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS + * + * Note that you can also use the executable name of the browser, like "chromium" + * or "firefox", but that these vary based on your operating system. + * + * You may also leave this blank and manually navigate your browser to + * http://localhost:9018/ when you're running tests. The window/tab can be left + * open and the tests will automatically occur there during the build. This has + * the aesthetic advantage of not launching a browser every time you save. + */ + browsers: [ + 'Chrome' + ] + }); +}; diff --git a/vendor/assets/components/angular-base64-upload/package.json b/vendor/assets/components/angular-base64-upload/package.json new file mode 100644 index 000000000..f45e6e0e5 --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/package.json @@ -0,0 +1,43 @@ +{ + "name": "angular-base64-upload", + "version": "v0.0.8", + "description": "Angular directive for uploading base64-encoded files that you can pass along with the resource model.", + "main": "angular-base64-upload.js", + "dependencies": { + "bower": "^1.3.12", + "grunt-cli": "^0.1.13" + }, + "devDependencies": { + "grunt": "^0.4.5", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-concat": "^0.5.0", + "grunt-contrib-jshint": "^0.10.0", + "grunt-contrib-uglify": "^0.6.0", + "grunt-karma": "^0.9.0", + "karma": "^0.12.23", + "karma-chrome-launcher": "^0.1.4", + "karma-coverage": "^0.2.6", + "karma-jasmine": "^0.1.5" + }, + "repository": { + "type": "git", + "url": "https://github.com/adonespitogo/angular-base64-upload.git" + }, + "keywords": [ + "angularjs", + "angular", + "upload", + "base-64", + "upload", + "file", + "upload", + "ajax", + "upload" + ], + "author": "Adones Pitogo ", + "license": "MIT", + "bugs": { + "url": "https://github.com/adonespitogo/angular-base64-upload/issues" + }, + "homepage": "https://github.com/adonespitogo/angular-base64-upload" +} diff --git a/vendor/assets/components/angular-base64-upload/src/angular-base64-upload.js b/vendor/assets/components/angular-base64-upload/src/angular-base64-upload.js new file mode 100644 index 000000000..f2d58f862 --- /dev/null +++ b/vendor/assets/components/angular-base64-upload/src/angular-base64-upload.js @@ -0,0 +1,62 @@ +angular.module('naif.base64', []) +.directive('baseSixtyFourInput', ['$window', function ($window) { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, elem, attrs, ngModel) { + var fileObject = {}; + + scope.readerOnload = function(e){ + var base64 = _arrayBufferToBase64(e.target.result); + fileObject.base64 = base64; + scope.$apply(function(){ + ngModel.$setViewValue(angular.copy(fileObject)); + }); + }; + + var reader = new FileReader(); + reader.onload = scope.readerOnload; + + elem.on('change', function() { + if(!elem[0].files.length) { + return; + } + + var file = elem[0].files[0]; + fileObject.filetype = file.type; + fileObject.filename = file.name; + fileObject.filesize = file.size; + reader.readAsArrayBuffer(file); + }); + + //http://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string + function _arrayBufferToBase64( buffer ) { + var binary = ''; + var bytes = new Uint8Array( buffer ); + var len = bytes.byteLength; + for (var i = 0; i < len; i++) { + binary += String.fromCharCode( bytes[ i ] ); + } + return $window.btoa( binary ); + } + } + }; +}]) +.directive('baseSixtyFourImage', [function() { + return { + restrict: 'A', + link: function(scope, elem, attrs) { + scope.$watch(attrs.baseSixtyFourImage, function(fileObject) { + if(fileObject && fileObject.filetype && fileObject.filetype.indexOf("image") === 0) { + elem.attr("src", _assemble_data_uri(fileObject)); + } else { + elem.attr("src", attrs.baseSixtyFourImagePlaceholder); + } + }); + + function _assemble_data_uri(fileObject){ + return "data:" + fileObject.filetype + ";base64," + fileObject.base64; + } + } + }; +}]); diff --git a/vendor/assets/components/angular-bootstrap-.gitignore b/vendor/assets/components/angular-bootstrap-.gitignore new file mode 100644 index 000000000..496ee2ca6 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap-.npmignore b/vendor/assets/components/angular-bootstrap-.npmignore new file mode 100644 index 000000000..d62f9b6e0 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-.npmignore @@ -0,0 +1 @@ +bower.json \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap-README.md b/vendor/assets/components/angular-bootstrap-README.md new file mode 100644 index 000000000..9607c65f6 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-README.md @@ -0,0 +1,120 @@ +### UI Bootstrap - [AngularJS](http://angularjs.org/) directives specific to [Bootstrap](http://getbootstrap.com) + +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular-ui/bootstrap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Build Status](https://secure.travis-ci.org/angular-ui/bootstrap.svg)](http://travis-ci.org/angular-ui/bootstrap) +[![devDependency Status](https://david-dm.org/angular-ui/bootstrap/dev-status.svg?branch=master)](https://david-dm.org/angular-ui/bootstrap#info=devDependencies) + +### Quick links +- [Demo](#demo) +- [Installation](#installation) + - [NPM](#install-with-npm) + - [Bower](#install-with-bower) + - [NuGet](#install-with-nuget) + - [Custom](#custom-build) + - [Manual](#manual-download) +- [Support](#support) + - [FAQ](#faq) + - [Supported browsers](#supported-browsers) + - [Need help?](#need-help) + - [Found a bug?](#found-a-bug) +- [Contributing to the project](#contributing-to-the-project) +- [Development, meeting minutes, roadmap and more.](#development-meeting-minutes-roadmap-and-more) + + +# Demo + +Do you want to see directives in action? Visit http://angular-ui.github.io/bootstrap/! + +# Installation + +Installation is easy as UI Bootstrap has minimal dependencies - only the AngularJS and Twitter Bootstrap's CSS are required. +Note: Since version 0.13.0, UI Bootstrap depends on [ngAnimate](https://docs.angularjs.org/api/ngAnimate) for transitions and animations, such as the accordion, carousel, etc. Include `ngAnimate` in the module dependencies for your app in order to enable animation. + +#### Install with NPM + +```sh +$ npm install angular-ui-bootstrap +``` + +This will install AngularJS and Bootstrap NPM packages. + +#### Install with Bower +```sh +$ bower install angular-bootstrap +``` + +Note: do not install 'angular-ui-bootstrap'. A separate repository - [bootstrap-bower](https://github.com/angular-ui/bootstrap-bower) - hosts the compiled javascript file and bower.json. + +#### Install with NuGet +To install AngularJS UI Bootstrap, run the following command in the Package Manager Console + +```sh +PM> Install-Package Angular.UI.Bootstrap +``` + +#### Custom build + +Head over to http://angular-ui.github.io/bootstrap/ and hit the *Custom build* button to create your own custom UI Bootstrap build, just the way you like it. + +#### Manual download + +After downloading dependencies (or better yet, referencing them from your favorite CDN) you need to download build version of this project. All the files and their purposes are described here: +https://github.com/angular-ui/bootstrap/tree/gh-pages#build-files +Don't worry, if you are not sure which file to take, opt for `ui-bootstrap-tpls-[version].min.js`. + +### Adding dependency to your project + +When you are done downloading all the dependencies and project files the only remaining part is to add dependencies on the `ui.bootstrap` AngularJS module: + +```js +angular.module('myModule', ['ui.bootstrap']); +``` + +If you're a Browserify or Webpack user, you can do: + +```js +var uibs = require('angular-ui-bootstrap'); + +angular.module('myModule', [uibs]); +``` + +# Support + +## FAQ + +https://github.com/angular-ui/bootstrap/wiki/FAQ + +## Supported browsers + +Directives from this repository are automatically tested with the following browsers: +* Chrome (stable and canary channel) +* Firefox +* IE 9 and 10 +* Opera +* Safari + +Modern mobile browsers should work without problems. + + +## Need help? +Need help using UI Bootstrap? + +* Live help in the IRC (`#angularjs` channel at the `freenode` network). Use this [webchat](https://webchat.freenode.net/) or your own IRC client. +* Ask a question in [StackOverflow](http://stackoverflow.com/) under the [angular-ui-bootstrap](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) tag. + +**Please do not create new issues in this repository to ask questions about using UI Bootstrap** + +## Found a bug? +Please take a look at [CONTRIBUTING.md](CONTRIBUTING.md#you-think-youve-found-a-bug) and submit your issue [here](https://github.com/angular-ui/bootstrap/issues/new). + + +---- + + +# Contributing to the project + +We are always looking for the quality contributions! Please check the [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guidelines. + +# Development, meeting minutes, roadmap and more. + +Head over to the [Wiki](https://github.com/angular-ui/bootstrap/wiki) for notes on development for UI Bootstrap, meeting minutes from the UI Bootstrap team, roadmap plans, project philosophy and more. diff --git a/vendor/assets/components/angular-bootstrap-index.js b/vendor/assets/components/angular-bootstrap-index.js new file mode 100644 index 000000000..a174f26d4 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-index.js @@ -0,0 +1,2 @@ +require('./ui-bootstrap-tpls'); +module.exports = 'ui.bootstrap'; diff --git a/vendor/assets/components/angular-bootstrap-package.json b/vendor/assets/components/angular-bootstrap-package.json new file mode 100644 index 000000000..f7800479c --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-package.json @@ -0,0 +1,23 @@ +{ + "name": "angular-ui-bootstrap", + "version": "0.14.3", + "description": "Bootstrap widgets for Angular", + "main": "index.js", + "homepage": "http://angular-ui.github.io/bootstrap/", + "repository": { + "type": "git", + "url": "https://github.com/angular-ui/bootstrap.git" + }, + "keywords": [ + "angular", + "bootstrap", + "angular-ui", + "components", + "client-side" + ], + "author": "https://github.com/angular-ui/bootstrap/graphs/contributors", + "peerDependencies": { + "angular": "^1.3.x || >= 1.4.0-beta.0 || >= 1.5.0-beta.0" + }, + "license": "MIT" +} diff --git a/vendor/assets/components/angular-bootstrap-switch/.bower.json b/vendor/assets/components/angular-bootstrap-switch/.bower.json new file mode 100644 index 000000000..d6565896a --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/.bower.json @@ -0,0 +1,41 @@ +{ + "name": "angular-bootstrap-switch", + "version": "0.4.0", + "author": { + "name": "Francesco Pontillo", + "email": "francescopontillo@gmail.com", + "url": "https://github.com/frapontillo" + }, + "homepage": "https://github.com/frapontillo/angular-bootstrap-switch", + "repository": { + "type": "git", + "url": "git@github.com:frapontillo/angular-bootstrap-switch.git" + }, + "licenses": [ + { + "type": "Apache License 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + ], + "main": "./dist/angular-bootstrap-switch.js", + "dependencies": { + "angular": "~1.3.15", + "jquery": ">=1.9.0", + "bootstrap": ">=2.3.2", + "bootstrap-switch": "3.3.2" + }, + "devDependencies": { + "angular-mocks": "~1.3.15", + "angular-scenario": "~1.3.15" + }, + "_release": "0.4.0", + "_resolution": { + "type": "version", + "tag": "0.4.0", + "commit": "59c45c6e289d421afd9b43614da85f5c887b4bc7" + }, + "_source": "git://github.com/frapontillo/angular-bootstrap-switch.git", + "_target": "~0.4.0", + "_originalSource": "angular-bootstrap-switch", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap-switch/.bowerrc b/vendor/assets/components/angular-bootstrap-switch/.bowerrc new file mode 100644 index 000000000..44491d3df --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "bower_components" +} diff --git a/vendor/assets/components/angular-bootstrap-switch/.editorconfig b/vendor/assets/components/angular-bootstrap-switch/.editorconfig new file mode 100644 index 000000000..c2cdfb8ad --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/vendor/assets/components/angular-bootstrap-switch/.gitattributes b/vendor/assets/components/angular-bootstrap-switch/.gitattributes new file mode 100644 index 000000000..212566614 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-select2/.gitignore b/vendor/assets/components/angular-bootstrap-switch/.gitignore similarity index 55% rename from vendor/assets/components/angular-ui-select2/.gitignore rename to vendor/assets/components/angular-bootstrap-switch/.gitignore index a088b6f05..971bfabcc 100644 --- a/vendor/assets/components/angular-ui-select2/.gitignore +++ b/vendor/assets/components/angular-bootstrap-switch/.gitignore @@ -1,2 +1,5 @@ node_modules +.tmp +.sass-cache bower_components +.idea/ diff --git a/vendor/assets/components/angular-bootstrap-switch/.jshintrc b/vendor/assets/components/angular-bootstrap-switch/.jshintrc new file mode 100644 index 000000000..861a058c3 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/.jshintrc @@ -0,0 +1,26 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 2, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "globals": { + "angular": false, + "$": false, + "jQuery": false + } +} diff --git a/vendor/assets/components/angular-bootstrap-switch/.npmignore b/vendor/assets/components/angular-bootstrap-switch/.npmignore new file mode 100644 index 000000000..474568833 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/.npmignore @@ -0,0 +1,12 @@ +.git/ +.idea/ +.tmp/ +bower_components/ +common/ +src/ +node_modules/ +.editorconfig +.gitattributes +.gitignore +.jshintrc +.travis.yml \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap-switch/.travis.yml b/vendor/assets/components/angular-bootstrap-switch/.travis.yml new file mode 100644 index 000000000..a7c550470 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/.travis.yml @@ -0,0 +1,16 @@ +language: node_js + +node_js: + - '0.10' + +before_script: + - export DISPLAY=:99.0 + - export PHANTOMJS_BIN=/usr/local/phantomjs/bin/phantomjs + - sh -e /etc/init.d/xvfb start + - sleep 3 # give xvfb some time to start + - 'npm install -g bower grunt-cli' + - 'npm install' + - 'bower install' + +script: + - grunt diff --git a/vendor/assets/components/angular-bootstrap-switch/CHANGELOG.md b/vendor/assets/components/angular-bootstrap-switch/CHANGELOG.md new file mode 100644 index 000000000..0ca6bdb36 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/CHANGELOG.md @@ -0,0 +1,89 @@ +CHANGELOG +========= + +## 0.4.0-alpha.2 (2015-04-01) + +- Add new parameters + - `switch-inverse` + - `switch-readonly` +- Fix for radio switches +- Handle models using getterSetter option +- `'use strict'` to module-level only +- Update to `angular` 1.3.15 + +## 0.4.0-alpha.1 (2014-11-21) + +- Update to `bootstrap-switch` 3.2.2 +- Update to `angular` 1.3.3 +- Add new parameters + - `switch-label-width` + - `switch-handle-width` +- Multiple bug fixes +- Code optimization improvements + +## 0.3.0 (2014-06-27) + +- Update to `bootstrap-switch` 3.0.2 +- Update to `angular` 1.2.18 +- Support for `jquery` > 1.9.0 +- Promotion to stable + +## 0.3.0-alpha.2 (2014-03-30) + +- Update to `bootstrap-switch` 3.0.0 stable +- Update to `angular` 1.2.15 +- **bsSwitch**: add `switch-wrapper` property + +## 0.3.0-alpha.1 (2014-02-22) + +### Breaking changes + +This is an alpha release based on the `HEAD` of the `bootstrap-switch` `3.0` branch. Therefore, specifications +have slightly changed in order to reflect the original API. Use in production environment is discouraged, since the API +may change unexpectedly. + +- Handle text: + - Use `switch-on-text` instead of `switch-on-label` + - Use `switch-off-text` instead of `switch-off-label` +- Handle color: + - Use `switch-on-color` instead of `switch-on` + - Use `switch-off-color` instead of `switch-off` + +- When setting `switch-icon`, `bootstrap-switch~2` used to inject an `` tag with a predefined `icon` class, +while it now injects a `` tag without any additional classes other than the ones you specify. + +### Other changes + +- Update to `angular` 1.2.13 +- Update to `bootstrap-switch#3.0.0` +- Update to `jquery` 2.1.0 + +## 0.2.1 (2013-12-31) + +- Update to `angular` 1.2.6 +- Update to `bootstrap-switch` 2.0.0 +- **bsSwitch**: fix for `type` enforcing +- **bsSwitchSpec**: fix tests +- Add `CHANGELOG.md` + +## 0.2.0 (2013-12-16) + +- Improve build process +- **bsSwitchSpec**: fix stop test-travis +- **bsSwitch**: fix `$apply already in progress`, default active state +- Update to `angular` 1.2.5 +- Update to `bootstrap-switch` 1.9.0 +- Add example page +- Add contribution guidelines +- **bsSwitchSpec**: Add test file (24 tests) +- **bsSwitch**: fix class size (thanks to [@bardusco](https://github.com/bardusco)) + +## 2.0.0 (2013-09-24) + +- Update to `angular` 1.2.0-rc.1 +- Update to `develop` branch of `bootstrap-switch` +- **bsSwitch**: handle undefined `ngModel` + +## 0.1.0 (2013-08-12) + +- First release diff --git a/vendor/assets/components/angular-bootstrap-switch/CONTRIBUTING.md b/vendor/assets/components/angular-bootstrap-switch/CONTRIBUTING.md new file mode 100644 index 000000000..e905a165f --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/CONTRIBUTING.md @@ -0,0 +1,34 @@ +Contributing +============ + +##
    Report an Issue + +If you have found an issue with `angular-bootstrap-switch` and want to report it, **please make a live demo** first +so that the misbehaviour can be reproduced. If you don't know how to do it, simply fork and edit +**[this plnkr template](http://plnkr.co/edit/SWy8YmrVi8IsTa4FuqSZ)**. + +Issues with no live demo can get automatically closed. + +Also, make sure to: + - look for **similar issues** in the repository bug tracker + - specify the `angular-bootstrap-switch` **version** showing the issue + - check if the issue was already fixed in an `alpha`/`beta` release or in the latest commit of the `develop` branch + (commits on the `develop` branch don't generate a single file in the `build` directory, you need to check against + files in the `src` directory) + - clearly describe how the plugin should be changed to address your request + +## Submit a Pull Request + +If you want to submit a Pull Request, please follow the same rules as in [Report an Issue](#report-issue), plus all the +**[submission guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#submitting-a-pull-request)**, +**[coding rules](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#rules)** and +**[commit message rules](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit)** that apply to the +main angular.js project. + +**IMPORTANT**: Before submitting your PR, write new tests for it (where applicable) and test everything by running: + +```shell +$ grunt test-travis +``` + +Previously existing tests *should* never break. diff --git a/vendor/assets/components/angular-bootstrap-switch/Gruntfile.js b/vendor/assets/components/angular-bootstrap-switch/Gruntfile.js new file mode 100644 index 000000000..694b86c1d --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/Gruntfile.js @@ -0,0 +1,133 @@ +'use strict'; + +module.exports = function (grunt) { + // load all grunt tasks + require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); + + // configurable paths + var yeomanConfig = { + src: 'src', + dist: 'dist', + test: 'test', + temp: '.temp' + }; + + try { + yeomanConfig.src = require('./bower.json').appPath || yeomanConfig.src; + } catch (e) {} + + grunt.initConfig({ + yeoman: yeomanConfig, + pkg: grunt.file.readJSON('bower.json'), + meta: { + banner: + '/**\n' + + ' * <%= pkg.name %>\n' + + ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + + ' * @author <%= pkg.author.name %> (<%= pkg.author.email %>)\n' + + ' * @link <%= pkg.homepage %>\n' + + ' * @license <%= _.map(pkg.licenses, function(l) { return l.type + "(" + l.url + ")"; }).join(", ") %>\n' + + '**/\n\n' + }, + jshint: { + options: { + jshintrc: '.jshintrc' + }, + all: [ + 'Gruntfile.js', + '<%= yeoman.src %>/**/*.js' + ], + test: { + src: ['<%= yeoman.test %>/spec/**/*.js'], + options: { + jshintrc: '<%= yeoman.test %>/.jshintrc' + } + } + }, + karma: { + options: { + configFile: 'karma.conf.js' + }, + unit: { + options: { + singleRun: false + } + }, + final: { + options: { + singleRun: true + } + }, + travis: { + browsers: ['PhantomJS'], + options: { + singleRun: true + } + } + }, + clean: { + dist: { + files: [{ + dot: true, + src: [ + '<%= yeoman.dist %>/*', + '!<%= yeoman.dist %>/.git*' + ] + }] + }, + temp: { + src: ['<%= yeoman.dist %>/<%= yeoman.temp %>'] + } + }, + ngmin: { + dist: { + expand: true, + cwd: '<%= yeoman.src %>', + src: ['**/*.js'], + dest: '<%= yeoman.dist %>/<%= yeoman.temp %>' + } + }, + concat: { + options: { + banner: '<%= meta.banner %>', + process: function(src, filepath) { + // don't strip 'use strict' in the prefix + if (filepath === 'bsSwitch.prefix') { + return src; + } + return '// Source: ' + filepath + '\n' + + src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1'); + } + }, + dist: { + src: ['bsSwitch.prefix', 'common/*.js', '<%= yeoman.dist %>/<%= yeoman.temp %>/**/*.js', 'bsSwitch.suffix'], + dest: '<%= yeoman.dist %>/<%= pkg.name %>.js' + } + }, + uglify: { + options: { + banner: '<%= meta.banner %>' + }, + min: { + files: { + '<%= yeoman.dist %>/<%= pkg.name %>.min.js': '<%= concat.dist.dest %>' + } + } + } + }); + + // Test the directive + grunt.registerTask('test', ['jshint', 'karma:unit']); + grunt.registerTask('test-travis', ['jshint', 'karma:travis']); + + // Build the directive + // - clean, cleans the output directory + // - ngmin, prepares the angular files + // - concat, concatenates and adds a banner to the debug file + // - uglify, minifies and adds a banner to the minified file + // - clean:temp, cleans the ngmin-ified directory + grunt.registerTask('build', ['clean', 'ngmin', 'concat', 'uglify', 'clean:temp']); + + // Default task, do everything + grunt.registerTask('default', ['test-travis', 'build']); +}; diff --git a/vendor/assets/components/angular-bootstrap-switch/LICENSE b/vendor/assets/components/angular-bootstrap-switch/LICENSE new file mode 100644 index 000000000..37ec93a14 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/assets/components/angular-bootstrap-switch/README.md b/vendor/assets/components/angular-bootstrap-switch/README.md new file mode 100644 index 000000000..4a4a07d94 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/README.md @@ -0,0 +1,148 @@ +angular-bootstrap-switch +======================== + +[![Bower version][bower-version-image]][bower-url] +[![NPM version][npm-version-image]][npm-url] +[![Build Status][travis-image]][travis-url] +[![Apache License][license-image]][license-url] + +AngularJS directive for the [bootstrap-switch](https://github.com/nostalgiaz/bootstrap-switch) jQuery plugin. + +##Usage + +###Installation +```shell +$ bower install angular-bootstrap-switch +``` + +or + +```shell +$ npm install angular-bootstrap-switch +``` + +This will install AngularJS, jQuery, and the original bootstrap-switch. + +###Registration + +To be able to use the directive, you need to register the `angular-bootstrap-switch` module as a dependency: + +```javascript +angular.module('yourModule', ['frapontillo.bootstrap-switch' + // other dependencies +]); +``` + +###Directive +The directive can work on both element and attribute levels. The following example contains all of the supported attributes: + +```html + +``` + +Short doc for all of the attributes: + +* `ng-model`, the value to bind the switch to +* `type`, has to be one of `checkbox` and `radio`. +This value is mandatory and must be a string, as it cannot be changed once set (see [this answer on StackOverflow](http://stackoverflow.com/a/15155407/801065)). +If you choose `radio`, be sure to follow the [AngularJS radio specs](https://docs.angularjs.org/api/ng/input/input%5Bradio%5D), +meaning you have to specify the same `ngModel` and a different `value` or `ng-value` attribute for each radio +* `switch-active`, determines if the switch is enabled or not (changes the inner input's `disabled` attribute) +* `switch-readonly`, determines if the switch is read-only or not (changes the inner input's `readonly` attribute) +* `switch-size`, can be the empty string as default, `mini`, `small`, `large` +* `switch-animate`, determines if the switch animates when toggled +* `switch-on-text`, sets the positive (checked) text +* `switch-off-text`, sets the negative (unchecked) text +* `switch-on-color`, sets the positive (checked) class, can be `primary` (as default), `default`, `info`, `success`, `warning`, `danger` +* `switch-off-color`, sets the negative (unchecked) class, can be `default` (as default), `primary`, `info`, `success`, `warning`, `danger` +* `switch-label`, sets the toggle label +* `switch-icon`, sets the toggle icon (e.g. `icon-save`) +* `switch-wrapper`, sets the main container class, use a falsy value to fall back to the default one +* `switch-radio-off`, allows a radio button to be unchecked by the user (from `true` to `false`) +* `switch-label-width`, sets the width of the middle label +* `switch-handle-width`, sets the width of both handles +* `switch-inverse`, inverts the on/off handles + +###Migrating from bootstrap-switch~2 + +Read the [CHANGELOG](CHANGELOG.md#030-alpha1-2014-02-22) information to learn what's different in `0.3.0`. + +###Examples + +The `example` folder shows a simple working demo of the switch. + +###Compatibility + +IE8 requires you to attach the directive to an `` or ``. Due to some incompatibilities it is not possible to use a custom tag or `div` instead. + +##Development + +###Test and build + +To build the directive yourself you need to have NodeJS. Then do the following: + +```shell +$ npm install -g grunt-cli bower karma +$ npm install +$ bower install +$ grunt test-travis +$ grunt build +``` + +###Contribute + +To contribute, please follow the generic [AngularJS Contributing Guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md), with the only exception to send the PR to the `develop` branch instead of `master`. + +##Author + +Francesco Pontillo () + +##License + +``` + Copyright 2014-2015 Francesco Pontillo + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +``` + +[license-image]: http://img.shields.io/badge/license-Apache_2.0-blue.svg?style=flat +[license-url]: LICENSE + +[bower-version-image]: http://img.shields.io/bower/v/angular-bootstrap-switch.svg?style=flat +[bower-url]: http://bower.io/search/?q=angular-bootstrap-switch + +[npm-url]: https://npmjs.org/package/angular-bootstrap-switch +[npm-version-image]: http://img.shields.io/npm/v/angular-bootstrap-switch.svg?style=flat + +[travis-image]: http://img.shields.io/travis/frapontillo/angular-bootstrap-switch/develop.svg?style=flat +[travis-url]: https://travis-ci.org/frapontillo/angular-bootstrap-switch diff --git a/vendor/assets/components/angular-bootstrap-switch/bower.json b/vendor/assets/components/angular-bootstrap-switch/bower.json new file mode 100644 index 000000000..ef18da121 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/bower.json @@ -0,0 +1,31 @@ +{ + "name": "angular-bootstrap-switch", + "version": "0.4.0", + "author": { + "name": "Francesco Pontillo", + "email": "francescopontillo@gmail.com", + "url": "https://github.com/frapontillo" + }, + "homepage": "https://github.com/frapontillo/angular-bootstrap-switch", + "repository": { + "type": "git", + "url": "git@github.com:frapontillo/angular-bootstrap-switch.git" + }, + "licenses": [ + { + "type": "Apache License 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + ], + "main": "./dist/angular-bootstrap-switch.js", + "dependencies": { + "angular": "~1.3.15", + "jquery": ">=1.9.0", + "bootstrap": ">=2.3.2", + "bootstrap-switch": "3.3.2" + }, + "devDependencies": { + "angular-mocks": "~1.3.15", + "angular-scenario": "~1.3.15" + } +} diff --git a/vendor/assets/components/angular-bootstrap-switch/bsSwitch.prefix b/vendor/assets/components/angular-bootstrap-switch/bsSwitch.prefix new file mode 100644 index 000000000..5908bdb6b --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/bsSwitch.prefix @@ -0,0 +1,2 @@ +(function() { +'use strict'; diff --git a/vendor/assets/components/angular-bootstrap-switch/bsSwitch.suffix b/vendor/assets/components/angular-bootstrap-switch/bsSwitch.suffix new file mode 100644 index 000000000..158693a02 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/bsSwitch.suffix @@ -0,0 +1 @@ +})(); \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap-switch/common/module.js b/vendor/assets/components/angular-bootstrap-switch/common/module.js new file mode 100644 index 000000000..a89bd880a --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/common/module.js @@ -0,0 +1,3 @@ +'use strict'; + +angular.module('frapontillo.bootstrap-switch', []); diff --git a/vendor/assets/components/angular-bootstrap-switch/dist/angular-bootstrap-switch.js b/vendor/assets/components/angular-bootstrap-switch/dist/angular-bootstrap-switch.js new file mode 100644 index 000000000..6584e8771 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/dist/angular-bootstrap-switch.js @@ -0,0 +1,250 @@ +/** + * angular-bootstrap-switch + * @version v0.4.0 - 2015-04-13 + * @author Francesco Pontillo (francescopontillo@gmail.com) + * @link https://github.com/frapontillo/angular-bootstrap-switch + * @license Apache License 2.0(http://www.apache.org/licenses/LICENSE-2.0.html) +**/ + +(function() { +'use strict'; + +// Source: common/module.js +angular.module('frapontillo.bootstrap-switch', []); + +// Source: dist/.temp/directives/bsSwitch.js +angular.module('frapontillo.bootstrap-switch').directive('bsSwitch', [ + '$parse', + '$timeout', + function ($parse, $timeout) { + return { + restrict: 'A', + require: 'ngModel', + link: function link(scope, element, attrs, controller) { + var isInit = false; + /** + * Return the true value for this specific checkbox. + * @returns {Object} representing the true view value; if undefined, returns true. + */ + var getTrueValue = function () { + if (attrs.type === 'radio') { + return attrs.value || $parse(attrs.ngValue)(scope) || true; + } + var trueValue = $parse(attrs.ngTrueValue)(scope); + if (!angular.isString(trueValue)) { + trueValue = true; + } + return trueValue; + }; + /** + * Get a boolean value from a boolean-like string, evaluating it on the current scope. + * @param value The input object + * @returns {boolean} A boolean value + */ + var getBooleanFromString = function (value) { + return scope.$eval(value) === true; + }; + /** + * Get a boolean value from a boolean-like string, defaulting to true if undefined. + * @param value The input object + * @returns {boolean} A boolean value + */ + var getBooleanFromStringDefTrue = function (value) { + return value === true || value === 'true' || !value; + }; + /** + * Returns the value if it is truthy, or undefined. + * + * @param value The value to check. + * @returns the original value if it is truthy, {@link undefined} otherwise. + */ + var getValueOrUndefined = function (value) { + return value ? value : undefined; + }; + /** + * Get the value of the angular-bound attribute, given its name. + * The returned value may or may not equal the attribute value, as it may be transformed by a function. + * + * @param attrName The angular-bound attribute name to get the value for + * @returns {*} The attribute value + */ + var getSwitchAttrValue = function (attrName) { + var map = { + 'switchRadioOff': getBooleanFromStringDefTrue, + 'switchActive': function (value) { + return !getBooleanFromStringDefTrue(value); + }, + 'switchAnimate': getBooleanFromStringDefTrue, + 'switchLabel': function (value) { + return value ? value : ' '; + }, + 'switchIcon': function (value) { + if (value) { + return ''; + } + }, + 'switchWrapper': function (value) { + return value || 'wrapper'; + }, + 'switchInverse': getBooleanFromString, + 'switchReadonly': getBooleanFromString + }; + var transFn = map[attrName] || getValueOrUndefined; + return transFn(attrs[attrName]); + }; + /** + * Set a bootstrapSwitch parameter according to the angular-bound attribute. + * The parameter will be changed only if the switch has already been initialized + * (to avoid creating it before the model is ready). + * + * @param element The switch to apply the parameter modification to + * @param attr The name of the switch parameter + * @param modelAttr The name of the angular-bound parameter + */ + var setSwitchParamMaybe = function (element, attr, modelAttr) { + if (!isInit) { + return; + } + var newValue = getSwitchAttrValue(modelAttr); + element.bootstrapSwitch(attr, newValue); + }; + var setActive = function () { + setSwitchParamMaybe(element, 'disabled', 'switchActive'); + }; + /** + * If the directive has not been initialized yet, do so. + */ + var initMaybe = function () { + // if it's the first initialization + if (!isInit) { + var viewValue = controller.$modelValue === getTrueValue(); + isInit = !isInit; + // Bootstrap the switch plugin + element.bootstrapSwitch({ + radioAllOff: getSwitchAttrValue('switchRadioOff'), + disabled: getSwitchAttrValue('switchActive'), + state: viewValue, + onText: getSwitchAttrValue('switchOnText'), + offText: getSwitchAttrValue('switchOffText'), + onColor: getSwitchAttrValue('switchOnColor'), + offColor: getSwitchAttrValue('switchOffColor'), + animate: getSwitchAttrValue('switchAnimate'), + size: getSwitchAttrValue('switchSize'), + labelText: attrs.switchLabel ? getSwitchAttrValue('switchLabel') : getSwitchAttrValue('switchIcon'), + wrapperClass: getSwitchAttrValue('switchWrapper'), + handleWidth: getSwitchAttrValue('switchHandleWidth'), + labelWidth: getSwitchAttrValue('switchLabelWidth'), + inverse: getSwitchAttrValue('switchInverse'), + readonly: getSwitchAttrValue('switchReadonly') + }); + if (attrs.type === 'radio') { + controller.$setViewValue(controller.$modelValue); + } else { + controller.$setViewValue(viewValue); + } + } + }; + /** + * Listen to model changes. + */ + var listenToModel = function () { + attrs.$observe('switchActive', function (newValue) { + var active = getBooleanFromStringDefTrue(newValue); + // if we are disabling the switch, delay the deactivation so that the toggle can be switched + if (!active) { + $timeout(function () { + setActive(active); + }); + } else { + // if we are enabling the switch, set active right away + setActive(active); + } + }); + function modelValue() { + return controller.$modelValue; + } + // When the model changes + scope.$watch(modelValue, function (newValue) { + initMaybe(); + if (newValue !== undefined) { + element.bootstrapSwitch('state', newValue === getTrueValue(), false); + } + }, true); + // angular attribute to switch property bindings + var bindings = { + 'switchRadioOff': 'radioAllOff', + 'switchOnText': 'onText', + 'switchOffText': 'offText', + 'switchOnColor': 'onColor', + 'switchOffColor': 'offColor', + 'switchAnimate': 'animate', + 'switchSize': 'size', + 'switchLabel': 'labelText', + 'switchIcon': 'labelText', + 'switchWrapper': 'wrapperClass', + 'switchHandleWidth': 'handleWidth', + 'switchLabelWidth': 'labelWidth', + 'switchInverse': 'inverse', + 'switchReadonly': 'readonly' + }; + var observeProp = function (prop, bindings) { + return function () { + attrs.$observe(prop, function () { + setSwitchParamMaybe(element, bindings[prop], prop); + }); + }; + }; + // for every angular-bound attribute, observe it and trigger the appropriate switch function + for (var prop in bindings) { + attrs.$observe(prop, observeProp(prop, bindings)); + } + }; + /** + * Listen to view changes. + */ + var listenToView = function () { + if (attrs.type === 'radio') { + // when the switch is clicked + element.on('change.bootstrapSwitch', function (e) { + // discard not real change events + if (controller.$modelValue === controller.$viewValue && e.target.checked !== $(e.target).bootstrapSwitch('state')) { + // $setViewValue --> $viewValue --> $parsers --> $modelValue + // if the switch is indeed selected + if (e.target.checked) { + // set its value into the view + controller.$setViewValue(getTrueValue()); + } else if (getTrueValue() === controller.$viewValue) { + // otherwise if it's been deselected, delete the view value + controller.$setViewValue(undefined); + } + } + }); + } else { + // When the checkbox switch is clicked, set its value into the ngModel + element.on('switchChange.bootstrapSwitch', function (e) { + // $setViewValue --> $viewValue --> $parsers --> $modelValue + controller.$setViewValue(e.target.checked); + }); + } + }; + // Listen and respond to view changes + listenToView(); + // Listen and respond to model changes + listenToModel(); + // On destroy, collect ya garbage + scope.$on('$destroy', function () { + element.bootstrapSwitch('destroy'); + }); + } + }; + } +]).directive('bsSwitch', function () { + return { + restrict: 'E', + require: 'ngModel', + template: '', + replace: true + }; +}); +// Source: bsSwitch.suffix +})(); \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap-switch/dist/angular-bootstrap-switch.min.js b/vendor/assets/components/angular-bootstrap-switch/dist/angular-bootstrap-switch.min.js new file mode 100644 index 000000000..50dd5c39f --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/dist/angular-bootstrap-switch.min.js @@ -0,0 +1,9 @@ +/** + * angular-bootstrap-switch + * @version v0.4.0 - 2015-04-13 + * @author Francesco Pontillo (francescopontillo@gmail.com) + * @link https://github.com/frapontillo/angular-bootstrap-switch + * @license Apache License 2.0(http://www.apache.org/licenses/LICENSE-2.0.html) +**/ + +!function(){"use strict";angular.module("frapontillo.bootstrap-switch",[]),angular.module("frapontillo.bootstrap-switch").directive("bsSwitch",["$parse","$timeout",function(a,b){return{restrict:"A",require:"ngModel",link:function(c,d,e,f){var g=!1,h=function(){if("radio"===e.type)return e.value||a(e.ngValue)(c)||!0;var b=a(e.ngTrueValue)(c);return angular.isString(b)||(b=!0),b},i=function(a){return c.$eval(a)===!0},j=function(a){return a===!0||"true"===a||!a},k=function(a){return a?a:void 0},l=function(a){var b={switchRadioOff:j,switchActive:function(a){return!j(a)},switchAnimate:j,switchLabel:function(a){return a?a:" "},switchIcon:function(a){return a?"":void 0},switchWrapper:function(a){return a||"wrapper"},switchInverse:i,switchReadonly:i},c=b[a]||k;return c(e[a])},m=function(a,b,c){if(g){var d=l(c);a.bootstrapSwitch(b,d)}},n=function(){m(d,"disabled","switchActive")},o=function(){if(!g){var a=f.$modelValue===h();g=!g,d.bootstrapSwitch({radioAllOff:l("switchRadioOff"),disabled:l("switchActive"),state:a,onText:l("switchOnText"),offText:l("switchOffText"),onColor:l("switchOnColor"),offColor:l("switchOffColor"),animate:l("switchAnimate"),size:l("switchSize"),labelText:l(e.switchLabel?"switchLabel":"switchIcon"),wrapperClass:l("switchWrapper"),handleWidth:l("switchHandleWidth"),labelWidth:l("switchLabelWidth"),inverse:l("switchInverse"),readonly:l("switchReadonly")}),f.$setViewValue("radio"===e.type?f.$modelValue:a)}},p=function(){function a(){return f.$modelValue}e.$observe("switchActive",function(a){var c=j(a);c?n(c):b(function(){n(c)})}),c.$watch(a,function(a){o(),void 0!==a&&d.bootstrapSwitch("state",a===h(),!1)},!0);var g={switchRadioOff:"radioAllOff",switchOnText:"onText",switchOffText:"offText",switchOnColor:"onColor",switchOffColor:"offColor",switchAnimate:"animate",switchSize:"size",switchLabel:"labelText",switchIcon:"labelText",switchWrapper:"wrapperClass",switchHandleWidth:"handleWidth",switchLabelWidth:"labelWidth",switchInverse:"inverse",switchReadonly:"readonly"},i=function(a,b){return function(){e.$observe(a,function(){m(d,b[a],a)})}};for(var k in g)e.$observe(k,i(k,g))},q=function(){"radio"===e.type?d.on("change.bootstrapSwitch",function(a){f.$modelValue===f.$viewValue&&a.target.checked!==$(a.target).bootstrapSwitch("state")&&(a.target.checked?f.$setViewValue(h()):h()===f.$viewValue&&f.$setViewValue(void 0))}):d.on("switchChange.bootstrapSwitch",function(a){f.$setViewValue(a.target.checked)})};q(),p(),c.$on("$destroy",function(){d.bootstrapSwitch("destroy")})}}}]).directive("bsSwitch",function(){return{restrict:"E",require:"ngModel",template:"",replace:!0}})}(); \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap-switch/example/index.html b/vendor/assets/components/angular-bootstrap-switch/example/index.html new file mode 100644 index 000000000..082b89870 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/example/index.html @@ -0,0 +1,91 @@ + + + + + + + + + + AngularJS Bootstrap Switch example + + + + + + + + + +
    + + + + + + {{ isSelected }} + + + +
    +
    + + + + + + + + + + + + + diff --git a/vendor/assets/components/angular-bootstrap-switch/example/scripts/app.js b/vendor/assets/components/angular-bootstrap-switch/example/scripts/app.js new file mode 100644 index 000000000..aa464f673 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/example/scripts/app.js @@ -0,0 +1,3 @@ +'use strict'; + +angular.module('bsSwitchApp', ['frapontillo.bootstrap-switch']); diff --git a/vendor/assets/components/angular-bootstrap-switch/example/scripts/controllers/main.js b/vendor/assets/components/angular-bootstrap-switch/example/scripts/controllers/main.js new file mode 100644 index 000000000..7321ecb2a --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/example/scripts/controllers/main.js @@ -0,0 +1,27 @@ +'use strict'; + +angular.module('bsSwitchApp') + .controller('MainCtrl', function ($scope, $log) { + $scope.isSelected = 'nope'; + $scope.onText = 'Y'; + $scope.offText = 'N'; + $scope.isActive = true; + $scope.size = 'normal'; + $scope.animate = true; + $scope.radioOff = true; + $scope.handleWidth = "auto"; + $scope.labelWidth = "auto"; + $scope.inverse = true; + + $scope.$watch('isSelected', function() { + $log.info('Selection changed.'); + }); + + $scope.toggle = function() { + $scope.isSelected = $scope.isSelected === 'yep' ? 'nope' : 'yep'; + }; + + $scope.toggleActivation = function() { + $scope.isActive = !$scope.isActive; + } + }); diff --git a/vendor/assets/components/angular-bootstrap-switch/example/styles/main.css b/vendor/assets/components/angular-bootstrap-switch/example/styles/main.css new file mode 100644 index 000000000..c754fddc3 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/example/styles/main.css @@ -0,0 +1,22 @@ +body { + background: #fafafa; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #333; +} + +.hero-unit { + margin: 50px auto 0 auto; + width: 300px; + font-size: 18px; + font-weight: 200; + line-height: 30px; + background-color: #eee; + border-radius: 6px; + padding: 60px; +} + +.hero-unit h1 { + font-size: 60px; + line-height: 1; + letter-spacing: -1px; +} diff --git a/vendor/assets/components/angular-bootstrap-switch/karma-chrome.conf.js b/vendor/assets/components/angular-bootstrap-switch/karma-chrome.conf.js new file mode 100644 index 000000000..f438fec98 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/karma-chrome.conf.js @@ -0,0 +1,73 @@ +// Karma configuration +// Generated on Thu Mar 27 2014 09:18:43 GMT+0100 (ora solare Europa occidentale) + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'bower_components/jquery/dist/jquery.js', + 'bower_components/angular/angular.js', + 'bower_components/angular-mocks/angular-mocks.js', + 'bower_components/bootstrap-switch/dist/js/bootstrap-switch.js', + 'common/module.js', + 'src/**/*.js', + 'test/**/*.js' + ], + + + // list of files to exclude + exclude: [ + + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false + }); +}; diff --git a/vendor/assets/components/angular-bootstrap-switch/karma.conf.js b/vendor/assets/components/angular-bootstrap-switch/karma.conf.js new file mode 100644 index 000000000..4dacf2dae --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/karma.conf.js @@ -0,0 +1,73 @@ +// Karma configuration +// Generated on Thu Mar 27 2014 09:18:43 GMT+0100 (ora solare Europa occidentale) + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'bower_components/jquery/dist/jquery.js', + 'bower_components/angular/angular.js', + 'bower_components/angular-mocks/angular-mocks.js', + 'bower_components/bootstrap-switch/dist/js/bootstrap-switch.js', + 'common/module.js', + 'src/**/*.js', + 'test/**/*.js' + ], + + + // list of files to exclude + exclude: [ + + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome', 'Firefox', 'IE', 'PhantomJS'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false + }); +}; diff --git a/vendor/assets/components/angular-bootstrap-switch/package.json b/vendor/assets/components/angular-bootstrap-switch/package.json new file mode 100644 index 000000000..43e143e92 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/package.json @@ -0,0 +1,43 @@ +{ + "name": "angular-bootstrap-switch", + "version": "0.4.0", + "main": "dist/angular-bootstrap-switch.js", + "author": { + "name": "Francesco Pontillo", + "email": "francescopontillo@gmail.com", + "url": "https://github.com/frapontillo" + }, + "homepage": "https://github.com/frapontillo/angular-bootstrap-switch", + "repository": { + "type": "git", + "url": "git@github.com:frapontillo/angular-bootstrap-switch.git" + }, + "licenses": [ + { + "type": "Apache License 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + ], + "dependencies": {}, + "devDependencies": { + "grunt": "~0.4.1", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-uglify": "~0.4.0", + "grunt-contrib-jshint": "~0.9.2", + "grunt-contrib-clean": "~0.5.0", + "grunt-karma": "~0.8.2", + "matchdep": "~0.3.0", + "grunt-ngmin": "~0.0.3", + "karma-jasmine": "~0.2.2", + "karma-chrome-launcher": "~0.1.4", + "karma-firefox-launcher": "~0.1.3", + "karma-ie-launcher": "~0.1.5", + "karma-phantomjs-launcher": "~0.1.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "grunt test-travis" + } +} diff --git a/vendor/assets/components/angular-bootstrap-switch/src/directives/bsSwitch.js b/vendor/assets/components/angular-bootstrap-switch/src/directives/bsSwitch.js new file mode 100644 index 000000000..b93c60fe0 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/src/directives/bsSwitch.js @@ -0,0 +1,252 @@ +'use strict'; + +angular.module('frapontillo.bootstrap-switch') + .directive('bsSwitch', function ($parse, $timeout) { + return { + restrict: 'A', + require: 'ngModel', + link: function link(scope, element, attrs, controller) { + var isInit = false; + + /** + * Return the true value for this specific checkbox. + * @returns {Object} representing the true view value; if undefined, returns true. + */ + var getTrueValue = function() { + if (attrs.type === 'radio') { + return attrs.value || $parse(attrs.ngValue)(scope) || true; + } + var trueValue = ($parse(attrs.ngTrueValue)(scope)); + if (!angular.isString(trueValue)) { + trueValue = true; + } + return trueValue; + }; + + /** + * Get a boolean value from a boolean-like string, evaluating it on the current scope. + * @param value The input object + * @returns {boolean} A boolean value + */ + var getBooleanFromString = function(value) { + return scope.$eval(value) === true; + }; + + /** + * Get a boolean value from a boolean-like string, defaulting to true if undefined. + * @param value The input object + * @returns {boolean} A boolean value + */ + var getBooleanFromStringDefTrue = function(value) { + return (value === true || value === 'true' || !value); + }; + + /** + * Returns the value if it is truthy, or undefined. + * + * @param value The value to check. + * @returns the original value if it is truthy, {@link undefined} otherwise. + */ + var getValueOrUndefined = function (value) { + return (value ? value : undefined); + }; + + /** + * Get the value of the angular-bound attribute, given its name. + * The returned value may or may not equal the attribute value, as it may be transformed by a function. + * + * @param attrName The angular-bound attribute name to get the value for + * @returns {*} The attribute value + */ + var getSwitchAttrValue = function(attrName) { + var map = { + 'switchRadioOff': getBooleanFromStringDefTrue, + 'switchActive': function(value) { + return !getBooleanFromStringDefTrue(value); + }, + 'switchAnimate': getBooleanFromStringDefTrue, + 'switchLabel': function(value) { + return value ? value : ' '; + }, + 'switchIcon': function(value) { + if (value) { + return ''; + } + }, + 'switchWrapper': function(value) { + return value || 'wrapper'; + }, + 'switchInverse': getBooleanFromString, + 'switchReadonly': getBooleanFromString + }; + var transFn = map[attrName] || getValueOrUndefined; + return transFn(attrs[attrName]); + }; + + /** + * Set a bootstrapSwitch parameter according to the angular-bound attribute. + * The parameter will be changed only if the switch has already been initialized + * (to avoid creating it before the model is ready). + * + * @param element The switch to apply the parameter modification to + * @param attr The name of the switch parameter + * @param modelAttr The name of the angular-bound parameter + */ + var setSwitchParamMaybe = function(element, attr, modelAttr) { + if (!isInit) { + return; + } + var newValue = getSwitchAttrValue(modelAttr); + element.bootstrapSwitch(attr, newValue); + }; + + var setActive = function() { + setSwitchParamMaybe(element, 'disabled', 'switchActive'); + }; + + /** + * If the directive has not been initialized yet, do so. + */ + var initMaybe = function() { + // if it's the first initialization + if (!isInit) { + var viewValue = (controller.$modelValue === getTrueValue()); + isInit = !isInit; + // Bootstrap the switch plugin + element.bootstrapSwitch({ + radioAllOff: getSwitchAttrValue('switchRadioOff'), + disabled: getSwitchAttrValue('switchActive'), + state: viewValue, + onText: getSwitchAttrValue('switchOnText'), + offText: getSwitchAttrValue('switchOffText'), + onColor: getSwitchAttrValue('switchOnColor'), + offColor: getSwitchAttrValue('switchOffColor'), + animate: getSwitchAttrValue('switchAnimate'), + size: getSwitchAttrValue('switchSize'), + labelText: attrs.switchLabel ? getSwitchAttrValue('switchLabel') : getSwitchAttrValue('switchIcon'), + wrapperClass: getSwitchAttrValue('switchWrapper'), + handleWidth: getSwitchAttrValue('switchHandleWidth'), + labelWidth: getSwitchAttrValue('switchLabelWidth'), + inverse: getSwitchAttrValue('switchInverse'), + readonly: getSwitchAttrValue('switchReadonly') + }); + if (attrs.type === 'radio') { + controller.$setViewValue(controller.$modelValue); + } else { + controller.$setViewValue(viewValue); + } + } + }; + + /** + * Listen to model changes. + */ + var listenToModel = function () { + + attrs.$observe('switchActive', function (newValue) { + var active = getBooleanFromStringDefTrue(newValue); + // if we are disabling the switch, delay the deactivation so that the toggle can be switched + if (!active) { + $timeout(function() { + setActive(active); + }); + } else { + // if we are enabling the switch, set active right away + setActive(active); + } + }); + + function modelValue() { + return controller.$modelValue; + } + + // When the model changes + scope.$watch(modelValue, function(newValue) { + initMaybe(); + if (newValue !== undefined) { + element.bootstrapSwitch('state', newValue === getTrueValue(), false); + } + }, true); + + // angular attribute to switch property bindings + var bindings = { + 'switchRadioOff': 'radioAllOff', + 'switchOnText': 'onText', + 'switchOffText': 'offText', + 'switchOnColor': 'onColor', + 'switchOffColor': 'offColor', + 'switchAnimate': 'animate', + 'switchSize': 'size', + 'switchLabel': 'labelText', + 'switchIcon': 'labelText', + 'switchWrapper': 'wrapperClass', + 'switchHandleWidth': 'handleWidth', + 'switchLabelWidth': 'labelWidth', + 'switchInverse': 'inverse', + 'switchReadonly': 'readonly' + }; + + var observeProp = function(prop, bindings) { + return function() { + attrs.$observe(prop, function () { + setSwitchParamMaybe(element, bindings[prop], prop); + }); + }; + }; + + // for every angular-bound attribute, observe it and trigger the appropriate switch function + for (var prop in bindings) { + attrs.$observe(prop, observeProp(prop, bindings)); + } + }; + + /** + * Listen to view changes. + */ + var listenToView = function () { + if (attrs.type === 'radio') { + // when the switch is clicked + element.on('change.bootstrapSwitch', function (e) { + // discard not real change events + if ((controller.$modelValue === controller.$viewValue) && (e.target.checked !== $(e.target).bootstrapSwitch('state'))) { + // $setViewValue --> $viewValue --> $parsers --> $modelValue + // if the switch is indeed selected + if (e.target.checked) { + // set its value into the view + controller.$setViewValue(getTrueValue()); + } else if (getTrueValue() === controller.$viewValue) { + // otherwise if it's been deselected, delete the view value + controller.$setViewValue(undefined); + } + } + }); + } else { + // When the checkbox switch is clicked, set its value into the ngModel + element.on('switchChange.bootstrapSwitch', function (e) { + // $setViewValue --> $viewValue --> $parsers --> $modelValue + controller.$setViewValue(e.target.checked); + }); + } + }; + + // Listen and respond to view changes + listenToView(); + + // Listen and respond to model changes + listenToModel(); + + // On destroy, collect ya garbage + scope.$on('$destroy', function () { + element.bootstrapSwitch('destroy'); + }); + } + }; + }) + .directive('bsSwitch', function () { + return { + restrict: 'E', + require: 'ngModel', + template: '', + replace: true + }; + }); diff --git a/vendor/assets/components/angular-bootstrap-switch/test/.jshintrc b/vendor/assets/components/angular-bootstrap-switch/test/.jshintrc new file mode 100644 index 000000000..518a90dce --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/test/.jshintrc @@ -0,0 +1,37 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 2, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "globals": { + "jQuery": false, + "after": false, + "afterEach": false, + "angular": false, + "before": false, + "beforeEach": false, + "browser": false, + "describe": false, + "expect": false, + "inject": false, + "it": false, + "spyOn": false, + "jasmine": false + } +} + diff --git a/vendor/assets/components/angular-bootstrap-switch/test/runner.html b/vendor/assets/components/angular-bootstrap-switch/test/runner.html new file mode 100644 index 000000000..f4a00a12b --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/test/runner.html @@ -0,0 +1,10 @@ + + + + End2end Test Runner + + + + + + \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap-switch/test/spec/directives/bsSwitchSpec.js b/vendor/assets/components/angular-bootstrap-switch/test/spec/directives/bsSwitchSpec.js new file mode 100644 index 000000000..bc01f3056 --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-switch/test/spec/directives/bsSwitchSpec.js @@ -0,0 +1,619 @@ +'use strict'; + +describe('Directive: bsSwitch', function () { + var scope, $sandbox, $compile, $timeout; + + beforeEach(module('frapontillo.bootstrap-switch')); + + /* jshint camelcase: false */ + beforeEach(inject(function ($injector, $rootScope, _$compile_, _$timeout_) { + scope = $rootScope; + $compile = _$compile_; + $timeout = _$timeout_; + $sandbox = angular.element('
    ').appendTo(angular.element.find('body')); + })); + /* jshint camelcase: true */ + + afterEach(function() { + $sandbox.remove(); + scope.$destroy(); + }); + + var templates = { + 'default': { + scope: {model:true}, + element: 'ng-model="model" type="checkbox"' + }, + 'multipleRadios': { + scope: {model:''}, + element: [ + 'ng-model="model" name="radio" type="radio" value="uno"', + 'ng-model="model" name="radio" type="radio" value="dos"', + 'ng-model="model" name="radio" type="radio" value="tres"' + ] + }, + 'radio': { + scope: {model:true}, + element: 'ng-model="model" name="radio" type="radio"' + }, + 'radioOff': { + scope: {model:true, radioOff:false}, + element: 'ng-model="model" name="radio" type="radio" switch-radio-off="{{ radioOff }}"' + }, + 'active': { + scope: {model:true, isActive:true}, + element: 'ng-model="model" type="checkbox" switch-active="{{ isActive }}"' + }, + 'unactivated': { + scope: {model:true, isActive:false}, + element: 'ng-model="model" type="checkbox" switch-active="{{ isActive }}"' + }, + 'readonly': { + scope: {model:true}, + element: 'ng-model="model" type="checkbox" switch-readonly="{{ isReadonly }}"' + }, + 'size': { + scope: {model:true, size:'large'}, + element: 'ng-model="model" type="checkbox" switch-size="{{ size }}" switch-label-width="{{ labelWidth }}" switch-handle-width="{{ handleWidth }}"' + }, + 'color': { + scope: {model:true, on:'info', off:'warning'}, + element: 'ng-model="model" type="checkbox" switch-on-color="{{ on }}" switch-off-color="{{ off }}"' + }, + 'label': { + scope: {model:true}, + element: 'ng-model="model" type="checkbox" switch-on-text="{{ on }}" switch-off-text="{{ off }}" switch-label="{{ label }}"' + }, + 'icon': { + scope: {model:true, icon:'icon-youtube'}, + element: 'ng-model="model" type="checkbox" switch-icon="{{ icon }}"' + }, + 'animation': { + scope: {model:true}, + element: 'ng-model="model" type="checkbox" switch-animate="{{ animate }}"' + }, + 'modifier': { + scope: {model:true}, + element: 'ng-model="model" type="checkbox" switch-wrapper="{{ modifier }}"' + }, + 'customValues': { + scope: {model:'something'}, + element: 'ng-model="model" type="checkbox" ng-true-value="\'yep\'" ng-false-value="\'nope\'"' + }, + 'inverse': { + scope: {model:true}, + element: 'ng-model="model" type="checkbox" switch-inverse="{{ inverse }}"' + }, + 'getterSetter': { + scope: {}, + element: 'ng-model="modelGetterSetter" ng-model-options="{getterSetter: true}" type="checkbox"' + } + }; + + var CONST = { + SWITCH_CLASS: 'bootstrap-switch', + SWITCH_WRAPPER_CLASS: 'bootstrap-switch-wrapper', + SWITCH_CONTAINER_CLASS: 'bootstrap-switch-container', + SWITCH_INVERSE_CLASS: 'bootstrap-switch-inverse', + SWITCH_ON_CLASS: 'bootstrap-switch-on', + SWITCH_OFF_CLASS: 'bootstrap-switch-off', + SWITCH_DISABLED_CLASS: 'bootstrap-switch-disabled', + SWITCH_READONLY_CLASS: 'bootstrap-switch-readonly', + SWITCH_MINI_CLASS: 'bootstrap-switch-mini', + SWITCH_INFO_CLASS: 'bootstrap-switch-info', + SWITCH_WARNING_CLASS: 'bootstrap-switch-warning', + SWITCH_SUCCESS_CLASS: 'bootstrap-switch-success', + SWITCH_ERROR_CLASS: 'bootstrap-switch-error', + SWITCH_ANIMATED_CLASS: 'bootstrap-switch-animate', + SWITCH_LEFT_SELECTOR: '.bootstrap-switch-handle-on', + SWITCH_RIGHT_SELECTOR: '.bootstrap-switch-handle-off', + LABEL_SELECTOR: '.bootstrap-switch-label', + INPUT_SELECTOR: 'input', + ICON_SELECTOR: '.bootstrap-switch-label span', + DEFAULT_TRUE_TEXT: 'ON', + DEFAULT_FALSE_TEXT: 'OFF' + }; + + /** + * Build an element string. + * @param template The template element to be used + * @param input true if the element must be an `input` tag, anything falsy for `bs-switch` + * @returns {string} The HTML element as a string + */ + function buildElement(template, input) { + var elementContent = template.element; + var realElement; + if (angular.isArray(elementContent)) { + realElement = '
    '; + for (var c in elementContent) { + realElement += buildSingleElement(elementContent[c], input); + } + realElement += '
    '; + return realElement; + } + return buildSingleElement(elementContent, input); + } + + function buildSingleElement(content, isInput) { + var singleElement = (isInput ? ''; + if (!isInput) { + singleElement += ''; + } + return singleElement; + } + + /** + * Compile a given template object as an `input` or a `bs-switch`. + * @param template The template object + * @param input true if the element must be an `input` tag, anything falsy for `bs-switch` + * @returns {*} compiled angular element + */ + function compileDirective(template, input) { + template = template ? templates[template] : templates['default']; + angular.extend(scope, template.scope || templates['default'].scope); + var content = buildElement(template, input); + var $element = angular.element(content).appendTo($sandbox); + $compile($element)(scope); + scope.$apply(); + $element = $sandbox.find('> *:first-child'); + return $element; + } + + // Test the widget creation and defaults + function makeTestCreateSwitch(input) { + return function() { + var element = compileDirective(undefined, input); + expect(element).not.toBe(undefined); + expect(element.hasClass(CONST.SWITCH_CLASS)).toBe(true); + expect(element.find(CONST.SWITCH_LEFT_SELECTOR).html()).toBe(CONST.DEFAULT_TRUE_TEXT); + expect(element.find(CONST.SWITCH_RIGHT_SELECTOR).html()).toBe(CONST.DEFAULT_FALSE_TEXT); + expect(element.find(CONST.LABEL_SELECTOR).html()).toBe(' '); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeFalsy(); + expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeFalsy(); + }; + } + it('should create a switch', inject(makeTestCreateSwitch())); + it('should create a switch (input)', inject(makeTestCreateSwitch(true))); + + // Test the switch type + function makeTestRadio(input) { + return function () { + var element = compileDirective('radio', input); + expect(element.find(CONST.INPUT_SELECTOR).attr('type')).toBe('radio'); + }; + } + it('should create a radio switch', inject(makeTestRadio())); + it('should create a radio switch (input)', inject(makeTestRadio(true))); + + // Test the change of a radio switch from true to false + function makeTestRadioOffFalse(input) { + return function () { + var element = compileDirective('radioOff', input); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = false; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + }; + } + it('should not change a radio from true to false', inject(makeTestRadioOffFalse())); + it('should not change a radio from true to false (input)', inject(makeTestRadioOffFalse(true))); + + // Test the change of a radio switch from true to false + function makeTestRadioOffTrue(input) { + return function () { + var element = compileDirective('radioOff', input); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.radioOff = true; + scope.model = false; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + }; + } + it('should change a radio from true to false', inject(makeTestRadioOffTrue())); + it('should change a radio from true to false (input)', inject(makeTestRadioOffTrue(true))); + + + function expectNothing(el1, el2, el3) { + expect(el1.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(el1.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + expect(el2.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(el2.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + expect(el3.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(el3.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + } + + function makeTestMultipleRadios(input) { + return function () { + var element = compileDirective('multipleRadios', input); + var elements = element.find('.bootstrap-switch'); + var el1 = angular.element(elements[0]); + var el2 = angular.element(elements[1]); + var el3 = angular.element(elements[2]); + expectNothing(el1, el2, el3); + scope.model = 'wat'; + scope.$apply(); + expectNothing(el1, el2, el3); + scope.model = 'dos'; + scope.$apply(); + expect(el2.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(el2.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + expect(scope.model).toEqual('dos'); + expect(el1.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(el1.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + expect(el3.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(el3.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + }; + } + it('should set the proper model with multiple radios', inject(makeTestMultipleRadios())); + it('should set the proper model with multiple radios (input)', inject(makeTestMultipleRadios(true))); + + function makeTestMultipleRadiosOff(input) { + return function () { + var element = compileDirective('multipleRadios', input); + var elements = element.find('.bootstrap-switch'); + expect(scope.model).toEqual(''); + var el1 = angular.element(elements[0]); + var el2 = angular.element(elements[1]); + var el3 = angular.element(elements[2]); + expectNothing(el1, el2, el3); + jQuery(el3).find('input').bootstrapSwitch('toggleState'); + scope.$apply(); + expect(scope.model).toEqual('tres'); + jQuery(el3).find('input').bootstrapSwitch('toggleState'); + scope.$apply(); + expect(scope.model).toEqual(undefined); + expectNothing(el1, el2, el3); + }; + } + it('should set the proper model to undefined when a radio is turned off', inject(makeTestMultipleRadiosOff())); + it('should set the proper model to undefined when a radio is turned off (input)', inject(makeTestMultipleRadiosOff(true))); + + // Test the model change + function makeTestChangeModel(input) { + return function () { + var element = compileDirective(undefined, input); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = false; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + }; + } + it('should move the switch when the model changes', inject(makeTestChangeModel())); + it('should move the switch when the model changes (input)', inject(makeTestChangeModel(true))); + + // Test the view change + function makeTestChangeView(input) { + return function () { + var element = compileDirective(undefined, input); + expect(scope.model).toBeTruthy(); + element.find('input').bootstrapSwitch('toggleState'); + scope.$apply(); + expect(scope.model).toBeFalsy(); + element.find('input').bootstrapSwitch('toggleState'); + scope.$apply(); + expect(scope.model).toBeTruthy(); + }; + } + it('should change the model when the switch is clicked', inject(makeTestChangeView())); + it('should change the model when the switch is clicked (input)', inject(makeTestChangeView(true))); + + // Test the deactivation + function makeTestDeactivate(input) { + return function () { + var element = compileDirective('active', input); + expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeFalsy(); + expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeFalsy(); + scope.isActive = false; + scope.$apply(); + $timeout.flush(); + expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeTruthy(); + expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeTruthy(); + }; + } + it('should deactivate the switch', inject(makeTestDeactivate())); + it('should deactivate the switch (input)', inject(makeTestDeactivate(true))); + + // Test a model change followed by a deactivation + function makeTestChangeModelThenDeactivate(input) { + return function () { + var element = compileDirective('active', input); + // test the active state, should be true + expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeFalsy(); + expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeFalsy(); + // test the model, should be false + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = false; + scope.isActive = false; + scope.$apply(); + $timeout.flush(); + // test the active state, should be false + expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeTruthy(); + expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeTruthy(); + // test the model, should be false + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + }; + } + it('should change the model, then deactivate the switch', inject(makeTestChangeModelThenDeactivate())); + it('should change the model, deactivate the switch (input)', inject(makeTestChangeModelThenDeactivate(true))); + + // Test the activation + function makeTestActivate(input) { + return function () { + var element = compileDirective('unactivated', input); + // need to flush since the element starts as deactivated + $timeout.flush(); + expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeTruthy(); + scope.isActive = true; + scope.$apply(); + // no need to flush here since we are activating the switch + expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeFalsy(); + }; + } + it('should activate the switch', inject(makeTestActivate())); + it('should activate the switch (input)', inject(makeTestActivate(true))); + + // Test the readonly + function makeTestReadonly(input) { + return function () { + var element = compileDirective('readonly', input); + expect(element.hasClass(CONST.SWITCH_READONLY_CLASS)).toBeFalsy(); + expect(element.find(CONST.INPUT_SELECTOR).attr('readonly')).toBeFalsy(); + scope.isReadonly = true; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_READONLY_CLASS)).toBeTruthy(); + expect(element.find(CONST.INPUT_SELECTOR).attr('readonly')).toBeTruthy(); + }; + } + it('should set the switch as read only', inject(makeTestReadonly())); + it('should set the switch as read only (input)', inject(makeTestReadonly(true))); + + // Test the size change + function makeTestChangeSize(input) { + return function () { + var element = compileDirective('size', input); + expect(element.hasClass(CONST.SWITCH_MINI_CLASS)).toBeFalsy(); + scope.size = 'mini'; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_MINI_CLASS)).toBeTruthy(); + }; + } + it('should change the switch size', inject(makeTestChangeSize())); + it('should change the switch size (input)', inject(makeTestChangeSize(true))); + + // Test the label width change + function makeTestChangeLabelWidth(input) { + return function () { + var element = compileDirective('size', input); + scope.labelWidth = '600'; + scope.$apply(); + expect(element.find(CONST.INPUT_SELECTOR).bootstrapSwitch('labelWidth')).toEqual('600'); + }; + } + it('should change the label width', inject(makeTestChangeLabelWidth())); + it('should change the label width (input)', inject(makeTestChangeLabelWidth(true))); + + // Test the handle width change + function makeTestChangeHandleWidth(input) { + return function () { + var element = compileDirective('size', input); + scope.handleWidth = '600'; + scope.$apply(); + expect(element.find(CONST.INPUT_SELECTOR).bootstrapSwitch('handleWidth')).toEqual('600'); + }; + } + it('should change the handle width', inject(makeTestChangeHandleWidth())); + it('should change the handle width (input)', inject(makeTestChangeHandleWidth(true))); + + // Test the "on" and "off" color change + function makeTestChangeColor(input) { + return function () { + var element = compileDirective('color', input); + expect(element.find(CONST.SWITCH_LEFT_SELECTOR).hasClass(CONST.SWITCH_INFO_CLASS)).toBeTruthy(); + expect(element.find(CONST.SWITCH_RIGHT_SELECTOR).hasClass(CONST.SWITCH_WARNING_CLASS)).toBeTruthy(); + scope.on = 'success'; + scope.off = 'error'; + scope.$apply(); + expect(element.find(CONST.SWITCH_LEFT_SELECTOR).hasClass(CONST.SWITCH_SUCCESS_CLASS)).toBeTruthy(); + expect(element.find(CONST.SWITCH_RIGHT_SELECTOR).hasClass(CONST.SWITCH_ERROR_CLASS)).toBeTruthy(); + }; + } + it('should change the switch colors', inject(makeTestChangeColor())); + it('should change the switch colors (input)', inject(makeTestChangeColor(true))); + + // Test the "on" and "off" label change + function makeTestChangeLabel(input) { + return function () { + var element = compileDirective('label', input); + expect(element.find(CONST.SWITCH_LEFT_SELECTOR).html()).toBe(CONST.DEFAULT_TRUE_TEXT); + expect(element.find(CONST.SWITCH_RIGHT_SELECTOR).html()).toBe(CONST.DEFAULT_FALSE_TEXT); + scope.on = 'Yay'; + scope.off = 'Nay'; + scope.$apply(); + expect(element.find(CONST.SWITCH_LEFT_SELECTOR).html()).toBe('Yay'); + expect(element.find(CONST.SWITCH_RIGHT_SELECTOR).html()).toBe('Nay'); + }; + } + it('should change the switch labels', inject(makeTestChangeLabel())); + it('should change the switch labels (input)', inject(makeTestChangeLabel(true))); + + // Test the middle label change + function makeTestChangeMiddleLabel(input) { + return function () { + var element = compileDirective('label', input); + expect(element.find(CONST.LABEL_SELECTOR).html()).toBe(' '); + scope.label = 'XYZ'; + scope.$apply(); + expect(element.find(CONST.LABEL_SELECTOR).html()).toBe('XYZ'); + }; + } + it('should change the switch middle label', inject(makeTestChangeMiddleLabel())); + it('should change the switch middle label (input)', inject(makeTestChangeMiddleLabel(true))); + + // Test the middle icon change + function makeTestChangeMiddleIcon(input) { + return function () { + var element = compileDirective('icon', input); + expect(element.find(CONST.ICON_SELECTOR).hasClass('icon-youtube')).toBeTruthy(); + scope.icon = 'icon-fullscreen'; + scope.$apply(); + expect(element.find(CONST.ICON_SELECTOR).hasClass('icon-youtube')).toBeFalsy(); + expect(element.find(CONST.ICON_SELECTOR).hasClass('icon-fullscreen')).toBe(true); + }; + } + it('should change the switch middle icon', inject(makeTestChangeMiddleIcon())); + it('should change the switch middle icon (input)', inject(makeTestChangeMiddleIcon(true))); + + // Test the animation deactivation and reactivation + function makeTestAnimation(input) { + return function () { + jasmine.clock().install(); + var element = compileDirective('animation', input); + jasmine.clock().tick(50); + expect(element.hasClass(CONST.SWITCH_ANIMATED_CLASS)).toBeTruthy(); + scope.animate = false; + scope.$apply(); + jasmine.clock().tick(50); + expect(element.hasClass(CONST.SWITCH_ANIMATED_CLASS)).toBeFalsy(); + scope.animate = true; + scope.$apply(); + jasmine.clock().tick(50); + expect(element.hasClass(CONST.SWITCH_ANIMATED_CLASS)).toBeTruthy(); + jasmine.clock().uninstall(); + }; + } + it('should change the switch animation mode', inject(makeTestAnimation())); + it('should change the switch animation mode (input)', inject(makeTestAnimation(true))); + + // Test the custom class modifiers + function makeTestClassModifiers(input) { + return function () { + var element = compileDirective('modifier', input); + expect(element.hasClass(CONST.SWITCH_WRAPPER_CLASS)).toBeTruthy(); + scope.modifier = 'flat-switch'; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_WRAPPER_CLASS)).toBeFalsy(); + expect(element.hasClass('bootstrap-switch-flat-switch')).toBeTruthy(); + scope.modifier = ''; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_WRAPPER_CLASS)).toBeTruthy(); + scope.modifier = undefined; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_WRAPPER_CLASS)).toBeTruthy(); + }; + } + it('should change the custom wrapper class', inject(makeTestClassModifiers())); + it('should change the custom wrapper class (input)', inject(makeTestClassModifiers(true))); + + // Test the non-replacement if already an input element given + // to ensure IE8 compatibility + function makeTestReplacement(useInputElement) { + return function () { + var beforeCompile, + afterCompile, + content, + template = templates['default']; + + angular.extend(scope, template.scope); + content = buildElement(template, useInputElement); + beforeCompile = angular.element(content).appendTo($sandbox); + + $compile(beforeCompile)(scope); + afterCompile = $sandbox.find('input'); + scope.$apply(); + + expect(beforeCompile.length).toBe(1); + expect(afterCompile.length).toBe(1); + expect(beforeCompile[0] === afterCompile[0]).toBe(true); + }; + } + it('should replace non-input elements', inject(makeTestReplacement())); + it('should not replace input elements', inject(makeTestReplacement(true))); + + // Test the custom true/false values + function makeTestCustomValues(input) { + return function () { + var element = compileDirective('customValues', input); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + scope.model = 'yep'; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + }; + } + it('should use "yep" and "nope" instead of true and false', inject(makeTestCustomValues())); + it('should use "yep" and "nope" instead of true and false (input)', inject(makeTestCustomValues(true))); + + // Test the inverse default option + function makeTestInverseUndefined(input) { + return function () { + var element = compileDirective('inverse', input); + expect(element.hasClass(CONST.SWITCH_INVERSE_CLASS)).toBeFalsy(); + var children = element.find('.' + CONST.SWITCH_CONTAINER_CLASS).find('*[class^=\'bootstrap-switch-handle-\']'); + expect(children.length).toBe(2); + expect(angular.element(children[0]).hasClass(CONST.SWITCH_LEFT_SELECTOR.substr(1))).toBeTruthy(); + expect(angular.element(children[1]).hasClass(CONST.SWITCH_RIGHT_SELECTOR.substr(1))).toBeTruthy(); + }; + } + it('should default to inverse false when not defined', inject(makeTestInverseUndefined())); + it('should default to inverse false when not defined (input)', inject(makeTestInverseUndefined(true))); + + // Test the inverse option + function makeTestInverse(input) { + return function () { + var element = compileDirective('inverse', input); + expect(element.hasClass(CONST.SWITCH_INVERSE_CLASS)).toBeFalsy(); + // invert + scope.inverse = true; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INVERSE_CLASS)).toBeTruthy(); + var children = element.find('.' + CONST.SWITCH_CONTAINER_CLASS).find('*[class^=\'bootstrap-switch-handle-\']'); + expect(children.length).toBe(2); + expect(angular.element(children[1]).hasClass(CONST.SWITCH_LEFT_SELECTOR.substr(1))).toBeTruthy(); + expect(angular.element(children[0]).hasClass(CONST.SWITCH_RIGHT_SELECTOR.substr(1))).toBeTruthy(); + // reset + scope.inverse = false; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INVERSE_CLASS)).toBeFalsy(); + children = element.find('.' + CONST.SWITCH_CONTAINER_CLASS).find('*[class^=\'bootstrap-switch-handle-\']'); + expect(children.length).toBe(2); + expect(angular.element(children[0]).hasClass(CONST.SWITCH_LEFT_SELECTOR.substr(1))).toBeTruthy(); + expect(angular.element(children[1]).hasClass(CONST.SWITCH_RIGHT_SELECTOR.substr(1))).toBeTruthy(); + }; + } + it('should invert the on and off switches and then reset them', inject(makeTestInverse())); + it('should invert the on and off switches and then reset them (input)', inject(makeTestInverse(true))); + + // Test the getterSetter ng-model option + function makeTestGetterSetter(input) { + return function () { + var element = compileDirective('getterSetter', input); + var localValue = false; + + scope.modelGetterSetter = function() { + return localValue; + }; + scope.$apply(); + + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + + localValue = true; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + }; + } + it('should watch updates in getterSetter', inject(makeTestGetterSetter())); + it('should watch updates in getterSetter', inject(makeTestGetterSetter(true))); + +}); diff --git a/vendor/assets/components/angular-bootstrap-ui-bootstrap-csp.css b/vendor/assets/components/angular-bootstrap-ui-bootstrap-csp.css new file mode 100644 index 000000000..d772f786d --- /dev/null +++ b/vendor/assets/components/angular-bootstrap-ui-bootstrap-csp.css @@ -0,0 +1,6 @@ +/* Include this file in your html if you are using the CSP mode. */ + +.ng-animate.item:not(.left):not(.right) { + -webkit-transition: 0s ease-in-out left; + transition: 0s ease-in-out left +} \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap/.bower.json b/vendor/assets/components/angular-bootstrap/.bower.json index 288fbd50a..7f6da6277 100644 --- a/vendor/assets/components/angular-bootstrap/.bower.json +++ b/vendor/assets/components/angular-bootstrap/.bower.json @@ -11,21 +11,21 @@ "license": "MIT", "ignore": [], "description": "Native AngularJS (Angular) directives for Bootstrap.", - "version": "0.12.1", + "version": "0.14.3", "main": [ "./ui-bootstrap-tpls.js" ], "dependencies": { - "angular": ">=1 <1.3.0" + "angular": ">=1.3.0" }, "homepage": "https://github.com/angular-ui/bootstrap-bower", - "_release": "0.12.1", + "_release": "0.14.3", "_resolution": { "type": "version", - "tag": "0.12.1", - "commit": "ab14fbaaf3d592f8e76018f0666c5af6f68ebaa3" + "tag": "0.14.3", + "commit": "306d1a30b4a8e8144741bb9c0126331ac884126a" }, "_source": "git://github.com/angular-ui/bootstrap-bower.git", - "_target": ">=0.11", + "_target": ">=0.13.1", "_originalSource": "angular-bootstrap" } \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap/bower.json b/vendor/assets/components/angular-bootstrap/bower.json index 8f6991bed..03d4eb272 100644 --- a/vendor/assets/components/angular-bootstrap/bower.json +++ b/vendor/assets/components/angular-bootstrap/bower.json @@ -11,9 +11,9 @@ "license": "MIT", "ignore": [], "description": "Native AngularJS (Angular) directives for Bootstrap.", - "version": "0.12.1", + "version": "0.14.3", "main": ["./ui-bootstrap-tpls.js"], "dependencies": { - "angular": ">=1 <1.3.0" - } + "angular": ">=1.3.0" + } } diff --git a/vendor/assets/components/angular-bootstrap/ui-bootstrap-tpls.js b/vendor/assets/components/angular-bootstrap/ui-bootstrap-tpls.js index eda964c39..f72df4966 100644 --- a/vendor/assets/components/angular-bootstrap/ui-bootstrap-tpls.js +++ b/vendor/assets/components/angular-bootstrap/ui-bootstrap-tpls.js @@ -2,160 +2,161 @@ * angular-ui-bootstrap * http://angular-ui.github.io/bootstrap/ - * Version: 0.12.1 - 2015-02-20 + * Version: 0.14.3 - 2015-10-23 * License: MIT */ -angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); -angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]); -angular.module('ui.bootstrap.transition', []) - -/** - * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. - * @param {DOMElement} element The DOMElement that will be animated. - * @param {string|object|function} trigger The thing that will cause the transition to start: - * - As a string, it represents the css class to be added to the element. - * - As an object, it represents a hash of style attributes to be applied to the element. - * - As a function, it represents a function to be called that will cause the transition to occur. - * @return {Promise} A promise that is resolved when the transition finishes. - */ -.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { - - var $transition = function(element, trigger, options) { - options = options || {}; - var deferred = $q.defer(); - var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; - - var transitionEndHandler = function(event) { - $rootScope.$apply(function() { - element.unbind(endEventName, transitionEndHandler); - deferred.resolve(element); - }); - }; - - if (endEventName) { - element.bind(endEventName, transitionEndHandler); - } - - // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur - $timeout(function() { - if ( angular.isString(trigger) ) { - element.addClass(trigger); - } else if ( angular.isFunction(trigger) ) { - trigger(element); - } else if ( angular.isObject(trigger) ) { - element.css(trigger); - } - //If browser does not support transitions, instantly resolve - if ( !endEventName ) { - deferred.resolve(element); - } - }); - - // Add our custom cancel function to the promise that is returned - // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, - // i.e. it will therefore never raise a transitionEnd event for that transition - deferred.promise.cancel = function() { - if ( endEventName ) { - element.unbind(endEventName, transitionEndHandler); - } - deferred.reject('Transition cancelled'); - }; - - return deferred.promise; - }; - - // Work out the name of the transitionEnd event - var transElement = document.createElement('trans'); - var transitionEndEventNames = { - 'WebkitTransition': 'webkitTransitionEnd', - 'MozTransition': 'transitionend', - 'OTransition': 'oTransitionEnd', - 'transition': 'transitionend' - }; - var animationEndEventNames = { - 'WebkitTransition': 'webkitAnimationEnd', - 'MozTransition': 'animationend', - 'OTransition': 'oAnimationEnd', - 'transition': 'animationend' - }; - function findEndEventName(endEventNames) { - for (var name in endEventNames){ - if (transElement.style[name] !== undefined) { - return endEventNames[name]; - } - } - } - $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); - $transition.animationEndEventName = findEndEventName(animationEndEventNames); - return $transition; -}]); - -angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) - - .directive('collapse', ['$transition', function ($transition) { +angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); +angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-popup.html","template/tooltip/tooltip-popup.html","template/tooltip/tooltip-template-popup.html","template/popover/popover-html.html","template/popover/popover-template.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]); +angular.module('ui.bootstrap.collapse', []) + .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) { + var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null; return { - link: function (scope, element, attrs) { - - var initialAnimSkip = true; - var currentTransition; - - function doTransition(change) { - var newTransition = $transition(element, change); - if (currentTransition) { - currentTransition.cancel(); - } - currentTransition = newTransition; - newTransition.then(newTransitionDone, newTransitionDone); - return newTransition; - - function newTransitionDone() { - // Make sure it's this transition, otherwise, leave it alone. - if (currentTransition === newTransition) { - currentTransition = undefined; - } - } - } - + link: function(scope, element, attrs) { function expand() { - if (initialAnimSkip) { - initialAnimSkip = false; - expandDone(); + element.removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', true) + .attr('aria-hidden', false); + + if ($animateCss) { + $animateCss(element, { + addClass: 'in', + easing: 'ease', + to: { height: element[0].scrollHeight + 'px' } + }).start().finally(expandDone); } else { - element.removeClass('collapse').addClass('collapsing'); - doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); + $animate.addClass(element, 'in', { + to: { height: element[0].scrollHeight + 'px' } + }).then(expandDone); } } function expandDone() { - element.removeClass('collapsing'); - element.addClass('collapse in'); - element.css({height: 'auto'}); + element.removeClass('collapsing') + .addClass('collapse') + .css({height: 'auto'}); } function collapse() { - if (initialAnimSkip) { - initialAnimSkip = false; - collapseDone(); - element.css({height: 0}); + if (!element.hasClass('collapse') && !element.hasClass('in')) { + return collapseDone(); + } + + element + // IMPORTANT: The height must be set before adding "collapsing" class. + // Otherwise, the browser attempts to animate from height 0 (in + // collapsing class) to the given height here. + .css({height: element[0].scrollHeight + 'px'}) + // initially all panel collapse have the collapse class, this removal + // prevents the animation from jumping to collapsed state + .removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', false) + .attr('aria-hidden', true); + + if ($animateCss) { + $animateCss(element, { + removeClass: 'in', + to: {height: '0'} + }).start().finally(collapseDone); } else { - // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value - element.css({ height: element[0].scrollHeight + 'px' }); - //trigger reflow so a browser realizes that height was updated from auto to a specific value - var x = element[0].offsetWidth; - - element.removeClass('collapse in').addClass('collapsing'); - - doTransition({ height: 0 }).then(collapseDone); + $animate.removeClass(element, 'in', { + to: {height: '0'} + }).then(collapseDone); } } function collapseDone() { - element.removeClass('collapsing'); - element.addClass('collapse'); + element.css({height: '0'}); // Required so that collapse works when animation is disabled + element.removeClass('collapsing') + .addClass('collapse'); } - scope.$watch(attrs.collapse, function (shouldCollapse) { + scope.$watch(attrs.uibCollapse, function(shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +/* Deprecated collapse below */ + +angular.module('ui.bootstrap.collapse') + + .value('$collapseSuppressWarning', false) + + .directive('collapse', ['$animate', '$injector', '$log', '$collapseSuppressWarning', function($animate, $injector, $log, $collapseSuppressWarning) { + var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null; + return { + link: function(scope, element, attrs) { + if (!$collapseSuppressWarning) { + $log.warn('collapse is now deprecated. Use uib-collapse instead.'); + } + + function expand() { + element.removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', true) + .attr('aria-hidden', false); + + if ($animateCss) { + $animateCss(element, { + easing: 'ease', + to: { height: element[0].scrollHeight + 'px' } + }).start().done(expandDone); + } else { + $animate.animate(element, {}, { + height: element[0].scrollHeight + 'px' + }).then(expandDone); + } + } + + function expandDone() { + element.removeClass('collapsing') + .addClass('collapse in') + .css({height: 'auto'}); + } + + function collapse() { + if (!element.hasClass('collapse') && !element.hasClass('in')) { + return collapseDone(); + } + + element + // IMPORTANT: The height must be set before adding "collapsing" class. + // Otherwise, the browser attempts to animate from height 0 (in + // collapsing class) to the given height here. + .css({height: element[0].scrollHeight + 'px'}) + // initially all panel collapse have the collapse class, this removal + // prevents the animation from jumping to collapsed state + .removeClass('collapse in') + .addClass('collapsing') + .attr('aria-expanded', false) + .attr('aria-hidden', true); + + if ($animateCss) { + $animateCss(element, { + to: {height: '0'} + }).start().done(collapseDone); + } else { + $animate.animate(element, {}, { + height: '0' + }).then(collapseDone); + } + } + + function collapseDone() { + element.css({height: '0'}); // Required so that collapse works when animation is disabled + element.removeClass('collapsing') + .addClass('collapse'); + } + + scope.$watch(attrs.collapse, function(shouldCollapse) { if (shouldCollapse) { collapse(); } else { @@ -168,21 +169,21 @@ angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) -.constant('accordionConfig', { +.constant('uibAccordionConfig', { closeOthers: true }) -.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { - +.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) { // This array keeps track of the accordion groups this.groups = []; // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to this.closeOthers = function(openGroup) { - var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; - if ( closeOthers ) { - angular.forEach(this.groups, function (group) { - if ( group !== openGroup ) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? + $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if (closeOthers) { + angular.forEach(this.groups, function(group) { + if (group !== openGroup) { group.isOpen = false; } }); @@ -194,7 +195,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) var that = this; this.groups.push(groupScope); - groupScope.$on('$destroy', function (event) { + groupScope.$on('$destroy', function(event) { that.removeGroup(groupScope); }); }; @@ -202,7 +203,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) // This is called from the accordion-group directive when to remove itself this.removeGroup = function(group) { var index = this.groups.indexOf(group); - if ( index !== -1 ) { + if (index !== -1) { this.groups.splice(index, 1); } }; @@ -211,24 +212,26 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) // The accordion directive simply sets up the directive controller // and adds an accordion CSS class to itself element. -.directive('accordion', function () { +.directive('uibAccordion', function() { return { - restrict:'EA', - controller:'AccordionController', + controller: 'UibAccordionController', + controllerAs: 'accordion', transclude: true, - replace: false, - templateUrl: 'template/accordion/accordion.html' + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/accordion/accordion.html'; + } }; }) // The accordion-group directive indicates a block of html that will expand and collapse in an accordion -.directive('accordionGroup', function() { +.directive('uibAccordionGroup', function() { return { - require:'^accordion', // We need this directive to be inside an accordion - restrict:'EA', - transclude:true, // It transcludes the contents of the directive into the template + require: '^uibAccordion', // We need this directive to be inside an accordion + transclude: true, // It transcludes the contents of the directive into the template replace: true, // The element containing the directive will be replaced with the template - templateUrl:'template/accordion/accordion-group.html', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/accordion/accordion-group.html'; + }, scope: { heading: '@', // Interpolate the heading attribute onto this scope isOpen: '=?', @@ -242,15 +245,20 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) link: function(scope, element, attrs, accordionCtrl) { accordionCtrl.addGroup(scope); + scope.openClass = attrs.openClass || 'panel-open'; + scope.panelClass = attrs.panelClass; scope.$watch('isOpen', function(value) { - if ( value ) { + element.toggleClass(scope.openClass, !!value); + if (value) { accordionCtrl.closeOthers(scope); } }); - scope.toggleOpen = function() { - if ( !scope.isDisabled ) { - scope.isOpen = !scope.isOpen; + scope.toggleOpen = function($event) { + if (!scope.isDisabled) { + if (!$event || $event.which === 32) { + scope.isOpen = !scope.isOpen; + } } }; } @@ -258,118 +266,264 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) }) // Use accordion-heading below an accordion-group to provide a heading containing HTML -// -// Heading containing HTML - -// -.directive('accordionHeading', function() { +.directive('uibAccordionHeading', function() { return { - restrict: 'EA', transclude: true, // Grab the contents to be used as the heading template: '', // In effect remove this element! replace: true, - require: '^accordionGroup', - link: function(scope, element, attr, accordionGroupCtrl, transclude) { + require: '^uibAccordionGroup', + link: function(scope, element, attrs, accordionGroupCtrl, transclude) { // Pass the heading to the accordion-group controller // so that it can be transcluded into the right place in the template // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] - accordionGroupCtrl.setHeading(transclude(scope, function() {})); + accordionGroupCtrl.setHeading(transclude(scope, angular.noop)); } }; }) // Use in the accordion-group template to indicate where you want the heading to be transcluded // You must provide the property on the accordion-group controller that will hold the transcluded element -//
    -// -// ... -//
    -.directive('accordionTransclude', function() { +.directive('uibAccordionTransclude', function() { return { - require: '^accordionGroup', - link: function(scope, element, attr, controller) { - scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { - if ( heading ) { - element.html(''); - element.append(heading); + require: ['?^uibAccordionGroup', '?^accordionGroup'], + link: function(scope, element, attrs, controller) { + controller = controller[0] ? controller[0] : controller[1]; // Delete after we remove deprecation + scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) { + if (heading) { + element.find('span').html(''); + element.find('span').append(heading); } }); } }; }); +/* Deprecated accordion below */ + +angular.module('ui.bootstrap.accordion') + + .value('$accordionSuppressWarning', false) + + .controller('AccordionController', ['$scope', '$attrs', '$controller', '$log', '$accordionSuppressWarning', function($scope, $attrs, $controller, $log, $accordionSuppressWarning) { + if (!$accordionSuppressWarning) { + $log.warn('AccordionController is now deprecated. Use UibAccordionController instead.'); + } + + angular.extend(this, $controller('UibAccordionController', { + $scope: $scope, + $attrs: $attrs + })); + }]) + + .directive('accordion', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { + return { + restrict: 'EA', + controller: 'AccordionController', + controllerAs: 'accordion', + transclude: true, + replace: false, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/accordion/accordion.html'; + }, + link: function() { + if (!$accordionSuppressWarning) { + $log.warn('accordion is now deprecated. Use uib-accordion instead.'); + } + } + }; + }]) + + .directive('accordionGroup', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { + return { + require: '^accordion', // We need this directive to be inside an accordion + restrict: 'EA', + transclude: true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/accordion/accordion-group.html'; + }, + scope: { + heading: '@', // Interpolate the heading attribute onto this scope + isOpen: '=?', + isDisabled: '=?' + }, + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + if (!$accordionSuppressWarning) { + $log.warn('accordion-group is now deprecated. Use uib-accordion-group instead.'); + } + + accordionCtrl.addGroup(scope); + + scope.openClass = attrs.openClass || 'panel-open'; + scope.panelClass = attrs.panelClass; + scope.$watch('isOpen', function(value) { + element.toggleClass(scope.openClass, !!value); + if (value) { + accordionCtrl.closeOthers(scope); + } + }); + + scope.toggleOpen = function($event) { + if (!scope.isDisabled) { + if (!$event || $event.which === 32) { + scope.isOpen = !scope.isOpen; + } + } + }; + } + }; + }]) + + .directive('accordionHeading', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { + return { + restrict: 'EA', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + link: function(scope, element, attr, accordionGroupCtrl, transclude) { + if (!$accordionSuppressWarning) { + $log.warn('accordion-heading is now deprecated. Use uib-accordion-heading instead.'); + } + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, angular.noop)); + } + }; + }]) + + .directive('accordionTransclude', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { + return { + require: '^accordionGroup', + link: function(scope, element, attr, controller) { + if (!$accordionSuppressWarning) { + $log.warn('accordion-transclude is now deprecated. Use uib-accordion-transclude instead.'); + } + + scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { + if (heading) { + element.find('span').html(''); + element.find('span').append(heading); + } + }); + } + }; + }]); + + angular.module('ui.bootstrap.alert', []) -.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { - $scope.closeable = 'close' in $attrs; - this.close = $scope.close; +.controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) { + $scope.closeable = !!$attrs.close; + + var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ? + $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null; + + if (dismissOnTimeout) { + $timeout(function() { + $scope.close(); + }, parseInt(dismissOnTimeout, 10)); + } }]) -.directive('alert', function () { +.directive('uibAlert', function() { return { - restrict:'EA', - controller:'AlertController', - templateUrl:'template/alert/alert.html', - transclude:true, - replace:true, + controller: 'UibAlertController', + controllerAs: 'alert', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/alert/alert.html'; + }, + transclude: true, + replace: true, scope: { type: '@', close: '&' } }; -}) +}); -.directive('dismissOnTimeout', ['$timeout', function($timeout) { - return { - require: 'alert', - link: function(scope, element, attrs, alertCtrl) { - $timeout(function(){ - alertCtrl.close(); - }, parseInt(attrs.dismissOnTimeout, 10)); +/* Deprecated alert below */ + +angular.module('ui.bootstrap.alert') + + .value('$alertSuppressWarning', false) + + .controller('AlertController', ['$scope', '$attrs', '$controller', '$log', '$alertSuppressWarning', function($scope, $attrs, $controller, $log, $alertSuppressWarning) { + if (!$alertSuppressWarning) { + $log.warn('AlertController is now deprecated. Use UibAlertController instead.'); } - }; -}]); -angular.module('ui.bootstrap.bindHtml', []) + angular.extend(this, $controller('UibAlertController', { + $scope: $scope, + $attrs: $attrs + })); + }]) - .directive('bindHtmlUnsafe', function () { - return function (scope, element, attr) { - element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); - scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { - element.html(value || ''); - }); + .directive('alert', ['$log', '$alertSuppressWarning', function($log, $alertSuppressWarning) { + return { + controller: 'AlertController', + controllerAs: 'alert', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/alert/alert.html'; + }, + transclude: true, + replace: true, + scope: { + type: '@', + close: '&' + }, + link: function() { + if (!$alertSuppressWarning) { + $log.warn('alert is now deprecated. Use uib-alert instead.'); + } + } }; - }); + }]); + angular.module('ui.bootstrap.buttons', []) -.constant('buttonConfig', { +.constant('uibButtonConfig', { activeClass: 'active', toggleEvent: 'click' }) -.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { +.controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) { this.activeClass = buttonConfig.activeClass || 'active'; this.toggleEvent = buttonConfig.toggleEvent || 'click'; }]) -.directive('btnRadio', function () { +.directive('uibBtnRadio', function() { return { - require: ['btnRadio', 'ngModel'], - controller: 'ButtonsController', - link: function (scope, element, attrs, ctrls) { + require: ['uibBtnRadio', 'ngModel'], + controller: 'UibButtonsController', + controllerAs: 'buttons', + link: function(scope, element, attrs, ctrls) { var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + element.find('input').css({display: 'none'}); + //model -> UI - ngModelCtrl.$render = function () { - element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + ngModelCtrl.$render = function() { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio))); }; //ui->model - element.bind(buttonsCtrl.toggleEvent, function () { + element.on(buttonsCtrl.toggleEvent, function() { + if (attrs.disabled) { + return; + } + var isActive = element.hasClass(buttonsCtrl.activeClass); if (!isActive || angular.isDefined(attrs.uncheckable)) { - scope.$apply(function () { - ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); + scope.$apply(function() { + ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio)); ngModelCtrl.$render(); }); } @@ -378,13 +532,16 @@ angular.module('ui.bootstrap.buttons', []) }; }) -.directive('btnCheckbox', function () { +.directive('uibBtnCheckbox', function() { return { - require: ['btnCheckbox', 'ngModel'], - controller: 'ButtonsController', - link: function (scope, element, attrs, ctrls) { + require: ['uibBtnCheckbox', 'ngModel'], + controller: 'UibButtonsController', + controllerAs: 'button', + link: function(scope, element, attrs, ctrls) { var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + element.find('input').css({display: 'none'}); + function getTrueValue() { return getCheckboxValue(attrs.btnCheckboxTrue, true); } @@ -393,19 +550,22 @@ angular.module('ui.bootstrap.buttons', []) return getCheckboxValue(attrs.btnCheckboxFalse, false); } - function getCheckboxValue(attributeValue, defaultValue) { - var val = scope.$eval(attributeValue); - return angular.isDefined(val) ? val : defaultValue; + function getCheckboxValue(attribute, defaultValue) { + return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue; } //model -> UI - ngModelCtrl.$render = function () { + ngModelCtrl.$render = function() { element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); }; //ui->model - element.bind(buttonsCtrl.toggleEvent, function () { - scope.$apply(function () { + element.on(buttonsCtrl.toggleEvent, function() { + if (attrs.disabled) { + return; + } + + scope.$apply(function() { ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); ngModelCtrl.$render(); }); @@ -414,18 +574,134 @@ angular.module('ui.bootstrap.buttons', []) }; }); +/* Deprecated buttons below */ + +angular.module('ui.bootstrap.buttons') + + .value('$buttonsSuppressWarning', false) + + .controller('ButtonsController', ['$controller', '$log', '$buttonsSuppressWarning', function($controller, $log, $buttonsSuppressWarning) { + if (!$buttonsSuppressWarning) { + $log.warn('ButtonsController is now deprecated. Use UibButtonsController instead.'); + } + + angular.extend(this, $controller('UibButtonsController')); + }]) + + .directive('btnRadio', ['$log', '$buttonsSuppressWarning', function($log, $buttonsSuppressWarning) { + return { + require: ['btnRadio', 'ngModel'], + controller: 'ButtonsController', + controllerAs: 'buttons', + link: function(scope, element, attrs, ctrls) { + if (!$buttonsSuppressWarning) { + $log.warn('btn-radio is now deprecated. Use uib-btn-radio instead.'); + } + + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + element.find('input').css({display: 'none'}); + + //model -> UI + ngModelCtrl.$render = function() { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function() { + if (attrs.disabled) { + return; + } + + var isActive = element.hasClass(buttonsCtrl.activeClass); + + if (!isActive || angular.isDefined(attrs.uncheckable)) { + scope.$apply(function() { + ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); + ngModelCtrl.$render(); + }); + } + }); + } + }; + }]) + + .directive('btnCheckbox', ['$document', '$log', '$buttonsSuppressWarning', function($document, $log, $buttonsSuppressWarning) { + return { + require: ['btnCheckbox', 'ngModel'], + controller: 'ButtonsController', + controllerAs: 'button', + link: function(scope, element, attrs, ctrls) { + if (!$buttonsSuppressWarning) { + $log.warn('btn-checkbox is now deprecated. Use uib-btn-checkbox instead.'); + } + + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + element.find('input').css({display: 'none'}); + + function getTrueValue() { + return getCheckboxValue(attrs.btnCheckboxTrue, true); + } + + function getFalseValue() { + return getCheckboxValue(attrs.btnCheckboxFalse, false); + } + + function getCheckboxValue(attributeValue, defaultValue) { + var val = scope.$eval(attributeValue); + return angular.isDefined(val) ? val : defaultValue; + } + + //model -> UI + ngModelCtrl.$render = function() { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function() { + if (attrs.disabled) { + return; + } + + scope.$apply(function() { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + + //accessibility + element.on('keypress', function(e) { + if (attrs.disabled || e.which !== 32 || $document[0].activeElement !== element[0]) { + return; + } + + scope.$apply(function() { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + } + }; + }]); + + /** -* @ngdoc overview -* @name ui.bootstrap.carousel -* -* @description -* AngularJS version of an image carousel. -* -*/ -angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) -.controller('CarouselController', ['$scope', '$timeout', '$interval', '$transition', function ($scope, $timeout, $interval, $transition) { + * @ngdoc overview + * @name ui.bootstrap.carousel + * + * @description + * AngularJS version of an image carousel. + * + */ +angular.module('ui.bootstrap.carousel', []) + +.controller('UibCarouselController', ['$scope', '$element', '$interval', '$animate', function($scope, $element, $interval, $animate) { var self = this, slides = self.slides = $scope.slides = [], + NEW_ANIMATE = angular.version.minor >= 4, + NO_TRANSITION = 'uib-noTransition', + SLIDE_DIRECTION = 'uib-slideDirection', currentIndex = -1, currentInterval, isPlaying; self.currentSlide = null; @@ -433,83 +709,100 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) var destroyed = false; /* direction: "prev" or "next" */ self.select = $scope.select = function(nextSlide, direction) { - var nextIndex = slides.indexOf(nextSlide); + var nextIndex = $scope.indexOfSlide(nextSlide); //Decide direction if it's not given if (direction === undefined) { - direction = nextIndex > currentIndex ? 'next' : 'prev'; + direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; } - if (nextSlide && nextSlide !== self.currentSlide) { - if ($scope.$currentTransition) { - $scope.$currentTransition.cancel(); - //Timeout so ng-class in template has time to fix classes for finished slide - $timeout(goNext); - } else { - goNext(); - } - } - function goNext() { - // Scope has been destroyed, stop here. - if (destroyed) { return; } - //If we have a slide to transition from and we have a transition type and we're allowed, go - if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { - //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime - nextSlide.$element.addClass(direction); - var reflow = nextSlide.$element[0].offsetWidth; //force reflow - - //Set all other slides to stop doing their stuff for the new transition - angular.forEach(slides, function(slide) { - angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); - }); - angular.extend(nextSlide, {direction: direction, active: true, entering: true}); - angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); - - $scope.$currentTransition = $transition(nextSlide.$element, {}); - //We have to create new pointers inside a closure since next & current will change - (function(next,current) { - $scope.$currentTransition.then( - function(){ transitionDone(next, current); }, - function(){ transitionDone(next, current); } - ); - }(nextSlide, self.currentSlide)); - } else { - transitionDone(nextSlide, self.currentSlide); - } - self.currentSlide = nextSlide; - currentIndex = nextIndex; - //every time you change slides, reset the timer - restartTimer(); - } - function transitionDone(next, current) { - angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); - angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); - $scope.$currentTransition = null; + //Prevent this user-triggered transition from occurring if there is already one in progress + if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) { + goNext(nextSlide, nextIndex, direction); } }; - $scope.$on('$destroy', function () { + + function goNext(slide, index, direction) { + // Scope has been destroyed, stop here. + if (destroyed) { return; } + + angular.extend(slide, {direction: direction, active: true}); + angular.extend(self.currentSlide || {}, {direction: direction, active: false}); + if ($animate.enabled() && !$scope.noTransition && !$scope.$currentTransition && + slide.$element && self.slides.length > 1) { + slide.$element.data(SLIDE_DIRECTION, slide.direction); + if (self.currentSlide && self.currentSlide.$element) { + self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction); + } + + $scope.$currentTransition = true; + if (NEW_ANIMATE) { + $animate.on('addClass', slide.$element, function(element, phase) { + if (phase === 'close') { + $scope.$currentTransition = null; + $animate.off('addClass', element); + } + }); + } else { + slide.$element.one('$animate:close', function closeFn() { + $scope.$currentTransition = null; + }); + } + } + + self.currentSlide = slide; + currentIndex = index; + + //every time you change slides, reset the timer + restartTimer(); + } + + $scope.$on('$destroy', function() { destroyed = true; }); + function getSlideByIndex(index) { + if (angular.isUndefined(slides[index].index)) { + return slides[index]; + } + var i, len = slides.length; + for (i = 0; i < slides.length; ++i) { + if (slides[i].index == index) { + return slides[i]; + } + } + } + + self.getCurrentIndex = function() { + if (self.currentSlide && angular.isDefined(self.currentSlide.index)) { + return +self.currentSlide.index; + } + return currentIndex; + }; + /* Allow outside people to call indexOf on slides array */ - self.indexOfSlide = function(slide) { - return slides.indexOf(slide); + $scope.indexOfSlide = function(slide) { + return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide); }; $scope.next = function() { - var newIndex = (currentIndex + 1) % slides.length; + var newIndex = (self.getCurrentIndex() + 1) % slides.length; - //Prevent this user-triggered transition from occurring if there is already one in progress - if (!$scope.$currentTransition) { - return self.select(slides[newIndex], 'next'); + if (newIndex === 0 && $scope.noWrap()) { + $scope.pause(); + return; } + + return self.select(getSlideByIndex(newIndex), 'next'); }; $scope.prev = function() { - var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1; - //Prevent this user-triggered transition from occurring if there is already one in progress - if (!$scope.$currentTransition) { - return self.select(slides[newIndex], 'prev'); + if ($scope.noWrap() && newIndex === slides.length - 1) { + $scope.pause(); + return; } + + return self.select(getSlideByIndex(newIndex), 'prev'); }; $scope.isActive = function(slide) { @@ -517,6 +810,7 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) }; $scope.$watch('interval', restartTimer); + $scope.$watchCollection('slides', resetTransition); $scope.$on('$destroy', resetTimer); function restartTimer() { @@ -536,13 +830,19 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) function timerFn() { var interval = +$scope.interval; - if (isPlaying && !isNaN(interval) && interval > 0) { + if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) { $scope.next(); } else { $scope.pause(); } } + function resetTransition(slides) { + if (!slides.length) { + $scope.$currentTransition = null; + } + } + $scope.play = function() { if (!isPlaying) { isPlaying = true; @@ -560,9 +860,9 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) slide.$element = element; slides.push(slide); //if this is the first slide or the slide is set to active, select it - if(slides.length === 1 || slide.active) { - self.select(slides[slides.length-1]); - if (slides.length == 1) { + if (slides.length === 1 || slide.active) { + self.select(slides[slides.length - 1]); + if (slides.length === 1) { $scope.play(); } } else { @@ -571,20 +871,34 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) }; self.removeSlide = function(slide) { + if (angular.isDefined(slide.index)) { + slides.sort(function(a, b) { + return +a.index > +b.index; + }); + } //get the index of the slide inside the carousel var index = slides.indexOf(slide); slides.splice(index, 1); if (slides.length > 0 && slide.active) { if (index >= slides.length) { - self.select(slides[index-1]); + self.select(slides[index - 1]); } else { self.select(slides[index]); } } else if (currentIndex > index) { currentIndex--; } + + //clean the currentSlide when no more slide + if (slides.length === 0) { + self.currentSlide = null; + } }; + $scope.$watch('noTransition', function(noTransition) { + $element.data(NO_TRANSITION, noTransition); + }); + }]) /** @@ -602,20 +916,20 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) * @example - - + + - - + + - - + + .carousel-indicators { @@ -625,18 +939,21 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) */ -.directive('carousel', [function() { +.directive('uibCarousel', [function() { return { - restrict: 'EA', transclude: true, replace: true, - controller: 'CarouselController', + controller: 'UibCarouselController', + controllerAs: 'carousel', require: 'carousel', - templateUrl: 'template/carousel/carousel.html', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/carousel/carousel.html'; + }, scope: { interval: '=', noTransition: '=', - noPause: '=' + noPause: '=', + noWrap: '&' } }; }]) @@ -650,20 +967,21 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. * * @param {boolean=} active Model binding, whether or not this slide is currently active. + * @param {number=} index The index of the slide. The slides will be sorted by this parameter. * * @example
    - - + + - - + + Interval, in milliseconds:
    Enter a negative number to stop the interval.
    @@ -682,15 +1000,19 @@ function CarouselDemoCtrl($scope) {
    */ -.directive('slide', function() { +.directive('uibSlide', function() { return { - require: '^carousel', + require: '^uibCarousel', restrict: 'EA', transclude: true, replace: true, - templateUrl: 'template/carousel/slide.html', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/carousel/slide.html'; + }, scope: { - active: '=?' + active: '=?', + actual: '=?', + index: '=?' }, link: function (scope, element, attrs, carouselCtrl) { carouselCtrl.addSlide(scope, element); @@ -706,59 +1028,271 @@ function CarouselDemoCtrl($scope) { }); } }; -}); +}) + +.animation('.item', [ + '$injector', '$animate', +function ($injector, $animate) { + var NO_TRANSITION = 'uib-noTransition', + SLIDE_DIRECTION = 'uib-slideDirection', + $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + + function removeClass(element, className, callback) { + element.removeClass(className); + if (callback) { + callback(); + } + } + + return { + beforeAddClass: function(element, className, done) { + // Due to transclusion, noTransition property is on parent's scope + if (className == 'active' && element.parent() && element.parent().parent() && + !element.parent().parent().data(NO_TRANSITION)) { + var stopped = false; + var direction = element.data(SLIDE_DIRECTION); + var directionClass = direction == 'next' ? 'left' : 'right'; + var removeClassFn = removeClass.bind(this, element, + directionClass + ' ' + direction, done); + element.addClass(direction); + + if ($animateCss) { + $animateCss(element, {addClass: directionClass}) + .start() + .done(removeClassFn); + } else { + $animate.addClass(element, directionClass).then(function () { + if (!stopped) { + removeClassFn(); + } + done(); + }); + } + + return function () { + stopped = true; + }; + } + done(); + }, + beforeRemoveClass: function (element, className, done) { + // Due to transclusion, noTransition property is on parent's scope + if (className === 'active' && element.parent() && element.parent().parent() && + !element.parent().parent().data(NO_TRANSITION)) { + var stopped = false; + var direction = element.data(SLIDE_DIRECTION); + var directionClass = direction == 'next' ? 'left' : 'right'; + var removeClassFn = removeClass.bind(this, element, directionClass, done); + + if ($animateCss) { + $animateCss(element, {addClass: directionClass}) + .start() + .done(removeClassFn); + } else { + $animate.addClass(element, directionClass).then(function() { + if (!stopped) { + removeClassFn(); + } + done(); + }); + } + return function() { + stopped = true; + }; + } + done(); + } + }; +}]); + +/* deprecated carousel below */ + +angular.module('ui.bootstrap.carousel') + +.value('$carouselSuppressWarning', false) + +.controller('CarouselController', ['$scope', '$element', '$controller', '$log', '$carouselSuppressWarning', function($scope, $element, $controller, $log, $carouselSuppressWarning) { + if (!$carouselSuppressWarning) { + $log.warn('CarouselController is now deprecated. Use UibCarouselController instead.'); + } + + angular.extend(this, $controller('UibCarouselController', { + $scope: $scope, + $element: $element + })); +}]) + +.directive('carousel', ['$log', '$carouselSuppressWarning', function($log, $carouselSuppressWarning) { + return { + transclude: true, + replace: true, + controller: 'CarouselController', + controllerAs: 'carousel', + require: 'carousel', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/carousel/carousel.html'; + }, + scope: { + interval: '=', + noTransition: '=', + noPause: '=', + noWrap: '&' + }, + link: function() { + if (!$carouselSuppressWarning) { + $log.warn('carousel is now deprecated. Use uib-carousel instead.'); + } + } + }; +}]) + +.directive('slide', ['$log', '$carouselSuppressWarning', function($log, $carouselSuppressWarning) { + return { + require: '^carousel', + transclude: true, + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/carousel/slide.html'; + }, + scope: { + active: '=?', + actual: '=?', + index: '=?' + }, + link: function (scope, element, attrs, carouselCtrl) { + if (!$carouselSuppressWarning) { + $log.warn('slide is now deprecated. Use uib-slide instead.'); + } + + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}]); angular.module('ui.bootstrap.dateparser', []) -.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) { +.service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) { + // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js + var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; - this.parsers = {}; + var localeId; + var formatCodeToRegex; - var formatCodeToRegex = { - 'yyyy': { - regex: '\\d{4}', - apply: function(value) { this.year = +value; } - }, - 'yy': { - regex: '\\d{2}', - apply: function(value) { this.year = +value + 2000; } - }, - 'y': { - regex: '\\d{1,4}', - apply: function(value) { this.year = +value; } - }, - 'MMMM': { - regex: $locale.DATETIME_FORMATS.MONTH.join('|'), - apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } - }, - 'MMM': { - regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), - apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } - }, - 'MM': { - regex: '0[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } - }, - 'M': { - regex: '[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } - }, - 'dd': { - regex: '[0-2][0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } - }, - 'd': { - regex: '[1-2]?[0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } - }, - 'EEEE': { - regex: $locale.DATETIME_FORMATS.DAY.join('|') - }, - 'EEE': { - regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') - } + this.init = function() { + localeId = $locale.id; + + this.parsers = {}; + + formatCodeToRegex = { + 'yyyy': { + regex: '\\d{4}', + apply: function(value) { this.year = +value; } + }, + 'yy': { + regex: '\\d{2}', + apply: function(value) { this.year = +value + 2000; } + }, + 'y': { + regex: '\\d{1,4}', + apply: function(value) { this.year = +value; } + }, + 'MMMM': { + regex: $locale.DATETIME_FORMATS.MONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } + }, + 'MMM': { + regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } + }, + 'MM': { + regex: '0[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'M': { + regex: '[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'dd': { + regex: '[0-2][0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'd': { + regex: '[1-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'EEEE': { + regex: $locale.DATETIME_FORMATS.DAY.join('|') + }, + 'EEE': { + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') + }, + 'HH': { + regex: '(?:0|1)[0-9]|2[0-3]', + apply: function(value) { this.hours = +value; } + }, + 'hh': { + regex: '0[0-9]|1[0-2]', + apply: function(value) { this.hours = +value; } + }, + 'H': { + regex: '1?[0-9]|2[0-3]', + apply: function(value) { this.hours = +value; } + }, + 'h': { + regex: '[0-9]|1[0-2]', + apply: function(value) { this.hours = +value; } + }, + 'mm': { + regex: '[0-5][0-9]', + apply: function(value) { this.minutes = +value; } + }, + 'm': { + regex: '[0-9]|[1-5][0-9]', + apply: function(value) { this.minutes = +value; } + }, + 'sss': { + regex: '[0-9][0-9][0-9]', + apply: function(value) { this.milliseconds = +value; } + }, + 'ss': { + regex: '[0-5][0-9]', + apply: function(value) { this.seconds = +value; } + }, + 's': { + regex: '[0-9]|[1-5][0-9]', + apply: function(value) { this.seconds = +value; } + }, + 'a': { + regex: $locale.DATETIME_FORMATS.AMPMS.join('|'), + apply: function(value) { + if (this.hours === 12) { + this.hours = 0; + } + + if (value === 'PM') { + this.hours += 12; + } + } + } + }; }; + this.init(); + function createParser(format) { var map = [], regex = format.split(''); @@ -786,14 +1320,19 @@ angular.module('ui.bootstrap.dateparser', []) }; } - this.parse = function(input, format) { - if ( !angular.isString(input) || !format ) { + this.parse = function(input, format, baseDate) { + if (!angular.isString(input) || !format) { return input; } format = $locale.DATETIME_FORMATS[format] || format; + format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); - if ( !this.parsers[format] ) { + if ($locale.id !== localeId) { + this.init(); + } + + if (!this.parsers[format]) { this.parsers[format] = createParser(format); } @@ -802,18 +1341,43 @@ angular.module('ui.bootstrap.dateparser', []) map = parser.map, results = input.match(regex); - if ( results && results.length ) { - var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt; + if (results && results.length) { + var fields, dt; + if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { + fields = { + year: baseDate.getFullYear(), + month: baseDate.getMonth(), + date: baseDate.getDate(), + hours: baseDate.getHours(), + minutes: baseDate.getMinutes(), + seconds: baseDate.getSeconds(), + milliseconds: baseDate.getMilliseconds() + }; + } else { + if (baseDate) { + $log.warn('dateparser:', 'baseDate is not a valid date'); + } + fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; + } - for( var i = 1, n = results.length; i < n; i++ ) { + for (var i = 1, n = results.length; i < n; i++) { var mapper = map[i-1]; - if ( mapper.apply ) { + if (mapper.apply) { mapper.apply.call(fields, results[i]); } } - if ( isValid(fields.year, fields.month, fields.date) ) { - dt = new Date( fields.year, fields.month, fields.date, fields.hours); + if (isValid(fields.year, fields.month, fields.date)) { + if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { + dt = new Date(baseDate); + dt.setFullYear(fields.year, fields.month, fields.date, + fields.hours, fields.minutes, fields.seconds, + fields.milliseconds || 0); + } else { + dt = new Date(fields.year, fields.month, fields.date, + fields.hours, fields.minutes, fields.seconds, + fields.milliseconds || 0); + } } return dt; @@ -823,18 +1387,36 @@ angular.module('ui.bootstrap.dateparser', []) // Check if date is valid for specific month (and year for February). // Month: 0 = Jan, 1 = Feb, etc function isValid(year, month, date) { - if ( month === 1 && date > 28) { - return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); + if (date < 1) { + return false; } - if ( month === 3 || month === 5 || month === 8 || month === 10) { - return date < 31; + if (month === 1 && date > 28) { + return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); + } + + if (month === 3 || month === 5 || month === 8 || month === 10) { + return date < 31; } return true; } }]); +/* Deprecated dateparser below */ + +angular.module('ui.bootstrap.dateparser') + +.value('$dateParserSuppressWarning', false) + +.service('dateParser', ['$log', '$dateParserSuppressWarning', 'uibDateParser', function($log, $dateParserSuppressWarning, uibDateParser) { + if (!$dateParserSuppressWarning) { + $log.warn('dateParser is now deprecated. Use uibDateParser instead.'); + } + + angular.extend(this, uibDateParser); +}]); + angular.module('ui.bootstrap.position', []) /** @@ -843,8 +1425,7 @@ angular.module('ui.bootstrap.position', []) * relation to other, existing elements (this is the case for tooltips, popovers, * typeahead suggestions etc.). */ - .factory('$position', ['$document', '$window', function ($document, $window) { - + .factory('$uibPosition', ['$document', '$window', function($document, $window) { function getStyle(el, cssprop) { if (el.currentStyle) { //IE return el.currentStyle[cssprop]; @@ -867,7 +1448,7 @@ angular.module('ui.bootstrap.position', []) * returns the closest, non-statically positioned parentOffset of a given element * @param element */ - var parentOffsetEl = function (element) { + var parentOffsetEl = function(element) { var docDomEl = $document[0]; var offsetParent = element.offsetParent || docDomEl; while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { @@ -881,7 +1462,7 @@ angular.module('ui.bootstrap.position', []) * Provides read-only equivalent of jQuery's position function: * http://api.jquery.com/position/ */ - position: function (element) { + position: function(element) { var elBCR = this.offset(element); var offsetParentBCR = { top: 0, left: 0 }; var offsetParentEl = parentOffsetEl(element[0]); @@ -904,7 +1485,7 @@ angular.module('ui.bootstrap.position', []) * Provides read-only equivalent of jQuery's offset function: * http://api.jquery.com/offset/ */ - offset: function (element) { + offset: function(element) { var boundingClientRect = element[0].getBoundingClientRect(); return { width: boundingClientRect.width || element.prop('offsetWidth'), @@ -917,8 +1498,7 @@ angular.module('ui.bootstrap.position', []) /** * Provides coordinates for the targetEl in relation to hostEl */ - positionElements: function (hostEl, targetEl, positionStr, appendToBody) { - + positionElements: function(hostEl, targetEl, positionStr, appendToBody) { var positionStrParts = positionStr.split('-'); var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; @@ -933,25 +1513,25 @@ angular.module('ui.bootstrap.position', []) targetElHeight = targetEl.prop('offsetHeight'); var shiftWidth = { - center: function () { + center: function() { return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; }, - left: function () { + left: function() { return hostElPos.left; }, - right: function () { + right: function() { return hostElPos.left + hostElPos.width; } }; var shiftHeight = { - center: function () { + center: function() { return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; }, - top: function () { + top: function() { return hostElPos.top; }, - bottom: function () { + bottom: function() { return hostElPos.top + hostElPos.height; } }; @@ -988,9 +1568,25 @@ angular.module('ui.bootstrap.position', []) }; }]); +/* Deprecated position below */ + +angular.module('ui.bootstrap.position') + +.value('$positionSuppressWarning', false) + +.service('$position', ['$log', '$positionSuppressWarning', '$uibPosition', function($log, $positionSuppressWarning, $uibPosition) { + if (!$positionSuppressWarning) { + $log.warn('$position is now deprecated. Use $uibPosition instead.'); + } + + angular.extend(this, $uibPosition); +}]); + angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) -.constant('datepickerConfig', { +.value('$datepickerSuppressError', false) + +.constant('uibDatepickerConfig', { formatDay: 'dd', formatMonth: 'MMMM', formatYear: 'yyyy', @@ -1004,10 +1600,11 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst startingDay: 0, yearRange: 20, minDate: null, - maxDate: null + maxDate: null, + shortcutPropagation: false }) -.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { +.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError) { var self = this, ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; @@ -1016,13 +1613,13 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst // Configuration attributes angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', - 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) { - self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; + 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) { + self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; }); // Watchable date attributes - angular.forEach(['minDate', 'maxDate'], function( key ) { - if ( $attrs[key] ) { + angular.forEach(['minDate', 'maxDate'], function(key) { + if ($attrs[key]) { $scope.$parent.$watch($parse($attrs[key]), function(value) { self[key] = value ? new Date(value) : null; self.refreshView(); @@ -1032,9 +1629,35 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst } }); + angular.forEach(['minMode', 'maxMode'], function(key) { + if ($attrs[key]) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = angular.isDefined(value) ? value : $attrs[key]; + $scope[key] = self[key]; + if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) { + $scope.datepickerMode = self[key]; + } + }); + } else { + self[key] = datepickerConfig[key] || null; + $scope[key] = self[key]; + } + }); + $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); - this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date(); + + if (angular.isDefined($attrs.initDate)) { + this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date(); + $scope.$parent.$watch($attrs.initDate, function(initDate) { + if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { + self.activeDate = initDate; + self.refreshView(); + } + }); + } else { + this.activeDate = new Date(); + } $scope.isActive = function(dateObject) { if (self.compare(dateObject.date, self.activeDate) === 0) { @@ -1044,7 +1667,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst return false; }; - this.init = function( ngModelCtrl_ ) { + this.init = function(ngModelCtrl_) { ngModelCtrl = ngModelCtrl_; ngModelCtrl.$render = function() { @@ -1053,44 +1676,48 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst }; this.render = function() { - if ( ngModelCtrl.$modelValue ) { - var date = new Date( ngModelCtrl.$modelValue ), + if (ngModelCtrl.$viewValue) { + var date = new Date(ngModelCtrl.$viewValue), isValid = !isNaN(date); - if ( isValid ) { + if (isValid) { this.activeDate = date; - } else { + } else if (!$datepickerSuppressError) { $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); } - ngModelCtrl.$setValidity('date', isValid); } this.refreshView(); }; this.refreshView = function() { - if ( this.element ) { + if (this.element) { this._refreshView(); - var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; - ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); + var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date))); } }; this.createDateObject = function(date, format) { - var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; return { date: date, label: dateFilter(date, format), selected: model && this.compare(date, model) === 0, disabled: this.isDisabled(date), - current: this.compare(date, new Date()) === 0 + current: this.compare(date, new Date()) === 0, + customClass: this.customClass(date) }; }; - this.isDisabled = function( date ) { + this.isDisabled = function(date) { return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); }; + this.customClass = function(date) { + return $scope.customClass({date: date, mode: $scope.datepickerMode}); + }; + // Split array into smaller arrays this.split = function(arr, size) { var arrays = []; @@ -1100,66 +1727,64 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst return arrays; }; - $scope.select = function( date ) { - if ( $scope.datepickerMode === self.minMode ) { - var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); - dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); - ngModelCtrl.$setViewValue( dt ); + $scope.select = function(date) { + if ($scope.datepickerMode === self.minMode) { + var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + ngModelCtrl.$setViewValue(dt); ngModelCtrl.$render(); } else { self.activeDate = date; - $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ]; + $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1]; } }; - $scope.move = function( direction ) { + $scope.move = function(direction) { var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), month = self.activeDate.getMonth() + direction * (self.step.months || 0); self.activeDate.setFullYear(year, month, 1); self.refreshView(); }; - $scope.toggleMode = function( direction ) { + $scope.toggleMode = function(direction) { direction = direction || 1; if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { return; } - $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ]; + $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction]; }; // Key event mapper - $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' }; + $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; var focusElement = function() { - $timeout(function() { - self.element[0].focus(); - }, 0 , false); + self.element[0].focus(); }; // Listen for focus requests from popup directive - $scope.$on('datepicker.focus', focusElement); + $scope.$on('uib:datepicker.focus', focusElement); - $scope.keydown = function( evt ) { + $scope.keydown = function(evt) { var key = $scope.keys[evt.which]; - if ( !key || evt.shiftKey || evt.altKey ) { + if (!key || evt.shiftKey || evt.altKey) { return; } evt.preventDefault(); - evt.stopPropagation(); + if (!self.shortcutPropagation) { + evt.stopPropagation(); + } if (key === 'enter' || key === 'space') { - if ( self.isDisabled(self.activeDate)) { + if (self.isDisabled(self.activeDate)) { return; // do nothing } $scope.select(self.activeDate); - focusElement(); } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { $scope.toggleMode(key === 'up' ? 1 : -1); - focusElement(); } else { self.handleKeyDown(key, evt); self.refreshView(); @@ -1167,532 +1792,1067 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst }; }]) -.directive( 'datepicker', function () { +.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + this.step = { months: 1 }; + this.element = $element; + function getDaysInMonth(year, month) { + return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; + } + + this.init = function(ctrl) { + angular.extend(ctrl, this); + scope.showWeeks = ctrl.showWeeks; + ctrl.refreshView(); + }; + + this.getDates = function(startDate, n) { + var dates = new Array(n), current = new Date(startDate), i = 0, date; + while (i < n) { + date = new Date(current); + dates[i++] = date; + current.setDate(current.getDate() + 1); + } + return dates; + }; + + this._refreshView = function() { + var year = this.activeDate.getFullYear(), + month = this.activeDate.getMonth(), + firstDayOfMonth = new Date(this.activeDate); + + firstDayOfMonth.setFullYear(year, month, 1); + + var difference = this.startingDay - firstDayOfMonth.getDay(), + numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, + firstDate = new Date(firstDayOfMonth); + + if (numDisplayedFromPreviousMonth > 0) { + firstDate.setDate(-numDisplayedFromPreviousMonth + 1); + } + + // 42 is the number of days on a six-month calendar + var days = this.getDates(firstDate, 42); + for (var i = 0; i < 42; i ++) { + days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), { + secondary: days[i].getMonth() !== month, + uid: scope.uniqueId + '-' + i + }); + } + + scope.labels = new Array(7); + for (var j = 0; j < 7; j++) { + scope.labels[j] = { + abbr: dateFilter(days[j].date, this.formatDayHeader), + full: dateFilter(days[j].date, 'EEEE') + }; + } + + scope.title = dateFilter(this.activeDate, this.formatDayTitle); + scope.rows = this.split(days, 7); + + if (scope.showWeeks) { + scope.weekNumbers = []; + var thursdayIndex = (4 + 7 - this.startingDay) % 7, + numWeeks = scope.rows.length; + for (var curWeek = 0; curWeek < numWeeks; curWeek++) { + scope.weekNumbers.push( + getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date)); + } + } + }; + + this.compare = function(date1, date2) { + return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())); + }; + + function getISO8601WeekNumber(date) { + var checkDate = new Date(date); + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + } + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getDate(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 7; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 7; + } else if (key === 'pageup' || key === 'pagedown') { + var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); + this.activeDate.setMonth(month, 1); + date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date); + } else if (key === 'home') { + date = 1; + } else if (key === 'end') { + date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()); + } + this.activeDate.setDate(date); + }; +}]) + +.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + this.step = { years: 1 }; + this.element = $element; + + this.init = function(ctrl) { + angular.extend(ctrl, this); + ctrl.refreshView(); + }; + + this._refreshView = function() { + var months = new Array(12), + year = this.activeDate.getFullYear(), + date; + + for (var i = 0; i < 12; i++) { + date = new Date(this.activeDate); + date.setFullYear(year, i, 1); + months[i] = angular.extend(this.createDateObject(date, this.formatMonth), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = dateFilter(this.activeDate, this.formatMonthTitle); + scope.rows = this.split(months, 3); + }; + + this.compare = function(date1, date2) { + return new Date(date1.getFullYear(), date1.getMonth()) - new Date(date2.getFullYear(), date2.getMonth()); + }; + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getMonth(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 3; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 3; + } else if (key === 'pageup' || key === 'pagedown') { + var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); + this.activeDate.setFullYear(year); + } else if (key === 'home') { + date = 0; + } else if (key === 'end') { + date = 11; + } + this.activeDate.setMonth(date); + }; +}]) + +.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + var range; + this.element = $element; + + function getStartingYear(year) { + return parseInt((year - 1) / range, 10) * range + 1; + } + + this.yearpickerInit = function() { + range = this.yearRange; + this.step = { years: range }; + }; + + this._refreshView = function() { + var years = new Array(range), date; + + for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) { + date = new Date(this.activeDate); + date.setFullYear(start + i, 0, 1); + years[i] = angular.extend(this.createDateObject(date, this.formatYear), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = [years[0].label, years[range - 1].label].join(' - '); + scope.rows = this.split(years, 5); + }; + + this.compare = function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }; + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getFullYear(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 5; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 5; + } else if (key === 'pageup' || key === 'pagedown') { + date += (key === 'pageup' ? - 1 : 1) * this.step.years; + } else if (key === 'home') { + date = getStartingYear(this.activeDate.getFullYear()); + } else if (key === 'end') { + date = getStartingYear(this.activeDate.getFullYear()) + range - 1; + } + this.activeDate.setFullYear(date); + }; +}]) + +.directive('uibDatepicker', function() { return { - restrict: 'EA', replace: true, - templateUrl: 'template/datepicker/datepicker.html', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/datepicker.html'; + }, scope: { datepickerMode: '=?', - dateDisabled: '&' + dateDisabled: '&', + customClass: '&', + shortcutPropagation: '&?' }, - require: ['datepicker', '?^ngModel'], - controller: 'DatepickerController', + require: ['uibDatepicker', '^ngModel'], + controller: 'UibDatepickerController', + controllerAs: 'datepicker', link: function(scope, element, attrs, ctrls) { var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - if ( ngModelCtrl ) { - datepickerCtrl.init( ngModelCtrl ); - } + datepickerCtrl.init(ngModelCtrl); } }; }) -.directive('daypicker', ['dateFilter', function (dateFilter) { +.directive('uibDaypicker', function() { return { - restrict: 'EA', replace: true, - templateUrl: 'template/datepicker/day.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - scope.showWeeks = ctrl.showWeeks; + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/day.html'; + }, + require: ['^?uibDatepicker', 'uibDaypicker', '^?datepicker'], + controller: 'UibDaypickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0] || ctrls[2], + daypickerCtrl = ctrls[1]; - ctrl.step = { months: 1 }; - ctrl.element = element; + daypickerCtrl.init(datepickerCtrl); + } + }; +}) - var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - function getDaysInMonth( year, month ) { - return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; - } +.directive('uibMonthpicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/month.html'; + }, + require: ['^?uibDatepicker', 'uibMonthpicker', '^?datepicker'], + controller: 'UibMonthpickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0] || ctrls[2], + monthpickerCtrl = ctrls[1]; - function getDates(startDate, n) { - var dates = new Array(n), current = new Date(startDate), i = 0; - current.setHours(12); // Prevent repeated dates because of timezone bug - while ( i < n ) { - dates[i++] = new Date(current); - current.setDate( current.getDate() + 1 ); - } - return dates; - } + monthpickerCtrl.init(datepickerCtrl); + } + }; +}) - ctrl._refreshView = function() { - var year = ctrl.activeDate.getFullYear(), - month = ctrl.activeDate.getMonth(), - firstDayOfMonth = new Date(year, month, 1), - difference = ctrl.startingDay - firstDayOfMonth.getDay(), - numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, - firstDate = new Date(firstDayOfMonth); - - if ( numDisplayedFromPreviousMonth > 0 ) { - firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); - } - - // 42 is the number of days on a six-month calendar - var days = getDates(firstDate, 42); - for (var i = 0; i < 42; i ++) { - days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), { - secondary: days[i].getMonth() !== month, - uid: scope.uniqueId + '-' + i - }); - } - - scope.labels = new Array(7); - for (var j = 0; j < 7; j++) { - scope.labels[j] = { - abbr: dateFilter(days[j].date, ctrl.formatDayHeader), - full: dateFilter(days[j].date, 'EEEE') - }; - } - - scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); - scope.rows = ctrl.split(days, 7); - - if ( scope.showWeeks ) { - scope.weekNumbers = []; - var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ), - numWeeks = scope.rows.length; - while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {} - } - }; - - ctrl.compare = function(date1, date2) { - return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); - }; - - function getISO8601WeekNumber(date) { - var checkDate = new Date(date); - checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday - var time = checkDate.getTime(); - checkDate.setMonth(0); // Compare with Jan 1 - checkDate.setDate(1); - return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; - } - - ctrl.handleKeyDown = function( key, evt ) { - var date = ctrl.activeDate.getDate(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 7; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 7; - } else if (key === 'pageup' || key === 'pagedown') { - var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); - ctrl.activeDate.setMonth(month, 1); - date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); - } else if (key === 'home') { - date = 1; - } else if (key === 'end') { - date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); - } - ctrl.activeDate.setDate(date); - }; +.directive('uibYearpicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/year.html'; + }, + require: ['^?uibDatepicker', 'uibYearpicker', '^?datepicker'], + controller: 'UibYearpickerController', + link: function(scope, element, attrs, ctrls) { + var ctrl = ctrls[0] || ctrls[2]; + angular.extend(ctrl, ctrls[1]); + ctrl.yearpickerInit(); ctrl.refreshView(); } }; -}]) +}) -.directive('monthpicker', ['dateFilter', function (dateFilter) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/month.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - ctrl.step = { years: 1 }; - ctrl.element = element; - - ctrl._refreshView = function() { - var months = new Array(12), - year = ctrl.activeDate.getFullYear(); - - for ( var i = 0; i < 12; i++ ) { - months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), { - uid: scope.uniqueId + '-' + i - }); - } - - scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); - scope.rows = ctrl.split(months, 3); - }; - - ctrl.compare = function(date1, date2) { - return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); - }; - - ctrl.handleKeyDown = function( key, evt ) { - var date = ctrl.activeDate.getMonth(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 3; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 3; - } else if (key === 'pageup' || key === 'pagedown') { - var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); - ctrl.activeDate.setFullYear(year); - } else if (key === 'home') { - date = 0; - } else if (key === 'end') { - date = 11; - } - ctrl.activeDate.setMonth(date); - }; - - ctrl.refreshView(); - } - }; -}]) - -.directive('yearpicker', ['dateFilter', function (dateFilter) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/year.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - var range = ctrl.yearRange; - - ctrl.step = { years: range }; - ctrl.element = element; - - function getStartingYear( year ) { - return parseInt((year - 1) / range, 10) * range + 1; - } - - ctrl._refreshView = function() { - var years = new Array(range); - - for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) { - years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), { - uid: scope.uniqueId + '-' + i - }); - } - - scope.title = [years[0].label, years[range - 1].label].join(' - '); - scope.rows = ctrl.split(years, 5); - }; - - ctrl.compare = function(date1, date2) { - return date1.getFullYear() - date2.getFullYear(); - }; - - ctrl.handleKeyDown = function( key, evt ) { - var date = ctrl.activeDate.getFullYear(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 5; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 5; - } else if (key === 'pageup' || key === 'pagedown') { - date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; - } else if (key === 'home') { - date = getStartingYear( ctrl.activeDate.getFullYear() ); - } else if (key === 'end') { - date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1; - } - ctrl.activeDate.setFullYear(date); - }; - - ctrl.refreshView(); - } - }; -}]) - -.constant('datepickerPopupConfig', { +.constant('uibDatepickerPopupConfig', { datepickerPopup: 'yyyy-MM-dd', + datepickerPopupTemplateUrl: 'template/datepicker/popup.html', + datepickerTemplateUrl: 'template/datepicker/datepicker.html', + html5Types: { + date: 'yyyy-MM-dd', + 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss', + 'month': 'yyyy-MM' + }, currentText: 'Today', clearText: 'Clear', closeText: 'Done', closeOnDateSelection: true, appendToBody: false, - showButtonBar: true + showButtonBar: true, + onOpenFocus: true }) -.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', -function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { +.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', +function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) { + var self = this; + var cache = {}, + isHtml5DateInput = false; + var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus, + datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, + ngModel, $popup; + + scope.watchData = {}; + + this.init = function(_ngModel_) { + ngModel = _ngModel_; + closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection; + appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; + datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl; + datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl; + + scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; + + if (datepickerPopupConfig.html5Types[attrs.type]) { + dateFormat = datepickerPopupConfig.html5Types[attrs.type]; + isHtml5DateInput = true; + } else { + dateFormat = attrs.datepickerPopup || attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; + attrs.$observe('uibDatepickerPopup', function(value, oldValue) { + var newDateFormat = value || datepickerPopupConfig.datepickerPopup; + // Invalidate the $modelValue to ensure that formatters re-run + // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764 + if (newDateFormat !== dateFormat) { + dateFormat = newDateFormat; + ngModel.$modelValue = null; + + if (!dateFormat) { + throw new Error('uibDatepickerPopup must have a date format specified.'); + } + } + }); + } + + if (!dateFormat) { + throw new Error('uibDatepickerPopup must have a date format specified.'); + } + + if (isHtml5DateInput && attrs.datepickerPopup) { + throw new Error('HTML5 date input types do not support custom formats.'); + } + + // popup element used to display calendar + popupEl = angular.element('
    '); + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection(date)', + 'template-url': datepickerPopupTemplateUrl + }); + + // datepicker element + datepickerEl = angular.element(popupEl.children()[0]); + datepickerEl.attr('template-url', datepickerTemplateUrl); + + if (isHtml5DateInput) { + if (attrs.type === 'month') { + datepickerEl.attr('datepicker-mode', '"month"'); + datepickerEl.attr('min-mode', 'month'); + } + } + + if (attrs.datepickerOptions) { + var options = scope.$parent.$eval(attrs.datepickerOptions); + if (options && options.initDate) { + scope.initDate = options.initDate; + datepickerEl.attr('init-date', 'initDate'); + delete options.initDate; + } + angular.forEach(options, function(value, option) { + datepickerEl.attr(cameltoDash(option), value); + }); + } + + angular.forEach(['minMode', 'maxMode', 'minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function(key) { + if (attrs[key]) { + var getAttribute = $parse(attrs[key]); + scope.$parent.$watch(getAttribute, function(value) { + scope.watchData[key] = value; + if (key === 'minDate' || key === 'maxDate') { + cache[key] = new Date(value); + } + }); + datepickerEl.attr(cameltoDash(key), 'watchData.' + key); + + // Propagate changes from datepicker to outside + if (key === 'datepickerMode') { + var setAttribute = getAttribute.assign; + scope.$watch('watchData.' + key, function(value, oldvalue) { + if (angular.isFunction(setAttribute) && value !== oldvalue) { + setAttribute(scope.$parent, value); + } + }); + } + } + }); + if (attrs.dateDisabled) { + datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); + } + + if (attrs.showWeeks) { + datepickerEl.attr('show-weeks', attrs.showWeeks); + } + + if (attrs.customClass) { + datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })'); + } + + if (!isHtml5DateInput) { + // Internal API to maintain the correct ng-invalid-[key] class + ngModel.$$parserName = 'date'; + ngModel.$validators.date = validator; + ngModel.$parsers.unshift(parseDate); + ngModel.$formatters.push(function(value) { + scope.date = value; + return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat); + }); + } else { + ngModel.$formatters.push(function(value) { + scope.date = value; + return value; + }); + } + + // Detect changes in the view from the text box + ngModel.$viewChangeListeners.push(function() { + scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date); + }); + + element.bind('keydown', inputKeydownBind); + + $popup = $compile(popupEl)(scope); + // Prevent jQuery cache memory leak (template is now redundant after linking) + popupEl.remove(); + + if (appendToBody) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + + scope.$on('$destroy', function() { + if (scope.isOpen === true) { + if (!$rootScope.$$phase) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + } + + $popup.remove(); + element.unbind('keydown', inputKeydownBind); + $document.unbind('click', documentClickBind); + }); + }; + + scope.getText = function(key) { + return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; + }; + + scope.isDisabled = function(date) { + if (date === 'today') { + date = new Date(); + } + + return ((scope.watchData.minDate && scope.compare(date, cache.minDate) < 0) || + (scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0)); + }; + + scope.compare = function(date1, date2) { + return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())); + }; + + // Inner change + scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + scope.date = dt; + } + var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function + element.val(date); + ngModel.$setViewValue(date); + + if (closeOnDateSelection) { + scope.isOpen = false; + element[0].focus(); + } + }; + + scope.keydown = function(evt) { + if (evt.which === 27) { + scope.isOpen = false; + element[0].focus(); + } + }; + + scope.select = function(date) { + if (date === 'today') { + var today = new Date(); + if (angular.isDate(scope.date)) { + date = new Date(scope.date); + date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); + } else { + date = new Date(today.setHours(0, 0, 0, 0)); + } + } + scope.dateSelection(date); + }; + + scope.close = function() { + scope.isOpen = false; + element[0].focus(); + }; + + scope.$watch('isOpen', function(value) { + if (value) { + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); + + $timeout(function() { + if (onOpenFocus) { + scope.$broadcast('uib:datepicker.focus'); + } + $document.bind('click', documentClickBind); + }, 0, false); + } else { + $document.unbind('click', documentClickBind); + } + }); + + function cameltoDash(string) { + return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); + } + + function parseDate(viewValue) { + if (angular.isNumber(viewValue)) { + // presumably timestamp to date object + viewValue = new Date(viewValue); + } + + if (!viewValue) { + return null; + } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { + return viewValue; + } else if (angular.isString(viewValue)) { + var date = dateParser.parse(viewValue, dateFormat, scope.date); + if (isNaN(date)) { + return undefined; + } else { + return date; + } + } else { + return undefined; + } + } + + function validator(modelValue, viewValue) { + var value = modelValue || viewValue; + + if (!attrs.ngRequired && !value) { + return true; + } + + if (angular.isNumber(value)) { + value = new Date(value); + } + if (!value) { + return true; + } else if (angular.isDate(value) && !isNaN(value)) { + return true; + } else if (angular.isString(value)) { + var date = dateParser.parse(value, dateFormat); + return !isNaN(date); + } else { + return false; + } + } + + function documentClickBind(event) { + var popup = $popup[0]; + var dpContainsTarget = element[0].contains(event.target); + // The popup node may not be an element node + // In some browsers (IE) only element nodes have the 'contains' function + var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target); + if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + } + + function inputKeydownBind(evt) { + if (evt.which === 27 && scope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + scope.$apply(function() { + scope.isOpen = false; + }); + element[0].focus(); + } else if (evt.which === 40 && !scope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + scope.$apply(function() { + scope.isOpen = true; + }); + } + } +}]) + +.directive('uibDatepickerPopup', function() { return { - restrict: 'EA', - require: 'ngModel', + require: ['ngModel', 'uibDatepickerPopup'], + controller: 'UibDatepickerPopupController', scope: { isOpen: '=?', currentText: '@', clearText: '@', closeText: '@', - dateDisabled: '&' + dateDisabled: '&', + customClass: '&' }, - link: function(scope, element, attrs, ngModel) { - var dateFormat, - closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, - appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + link: function(scope, element, attrs, ctrls) { + var ngModel = ctrls[0], + ctrl = ctrls[1]; - scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; - - scope.getText = function( key ) { - return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; - }; - - attrs.$observe('datepickerPopup', function(value) { - dateFormat = value || datepickerPopupConfig.datepickerPopup; - ngModel.$render(); - }); - - // popup element used to display calendar - var popupEl = angular.element('
    '); - popupEl.attr({ - 'ng-model': 'date', - 'ng-change': 'dateSelection()' - }); - - function cameltoDash( string ){ - return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); - } - - // datepicker element - var datepickerEl = angular.element(popupEl.children()[0]); - if ( attrs.datepickerOptions ) { - angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) { - datepickerEl.attr( cameltoDash(option), value ); - }); - } - - scope.watchData = {}; - angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function( key ) { - if ( attrs[key] ) { - var getAttribute = $parse(attrs[key]); - scope.$parent.$watch(getAttribute, function(value){ - scope.watchData[key] = value; - }); - datepickerEl.attr(cameltoDash(key), 'watchData.' + key); - - // Propagate changes from datepicker to outside - if ( key === 'datepickerMode' ) { - var setAttribute = getAttribute.assign; - scope.$watch('watchData.' + key, function(value, oldvalue) { - if ( value !== oldvalue ) { - setAttribute(scope.$parent, value); - } - }); - } - } - }); - if (attrs.dateDisabled) { - datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); - } - - function parseDate(viewValue) { - if (!viewValue) { - ngModel.$setValidity('date', true); - return null; - } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { - ngModel.$setValidity('date', true); - return viewValue; - } else if (angular.isString(viewValue)) { - var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue); - if (isNaN(date)) { - ngModel.$setValidity('date', false); - return undefined; - } else { - ngModel.$setValidity('date', true); - return date; - } - } else { - ngModel.$setValidity('date', false); - return undefined; - } - } - ngModel.$parsers.unshift(parseDate); - - // Inner change - scope.dateSelection = function(dt) { - if (angular.isDefined(dt)) { - scope.date = dt; - } - ngModel.$setViewValue(scope.date); - ngModel.$render(); - - if ( closeOnDateSelection ) { - scope.isOpen = false; - element[0].focus(); - } - }; - - element.bind('input change keyup', function() { - scope.$apply(function() { - scope.date = ngModel.$modelValue; - }); - }); - - // Outter change - ngModel.$render = function() { - var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; - element.val(date); - scope.date = parseDate( ngModel.$modelValue ); - }; - - var documentClickBind = function(event) { - if (scope.isOpen && event.target !== element[0]) { - scope.$apply(function() { - scope.isOpen = false; - }); - } - }; - - var keydown = function(evt, noApply) { - scope.keydown(evt); - }; - element.bind('keydown', keydown); - - scope.keydown = function(evt) { - if (evt.which === 27) { - evt.preventDefault(); - evt.stopPropagation(); - scope.close(); - } else if (evt.which === 40 && !scope.isOpen) { - scope.isOpen = true; - } - }; - - scope.$watch('isOpen', function(value) { - if (value) { - scope.$broadcast('datepicker.focus'); - scope.position = appendToBody ? $position.offset(element) : $position.position(element); - scope.position.top = scope.position.top + element.prop('offsetHeight'); - - $document.bind('click', documentClickBind); - } else { - $document.unbind('click', documentClickBind); - } - }); - - scope.select = function( date ) { - if (date === 'today') { - var today = new Date(); - if (angular.isDate(ngModel.$modelValue)) { - date = new Date(ngModel.$modelValue); - date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); - } else { - date = new Date(today.setHours(0, 0, 0, 0)); - } - } - scope.dateSelection( date ); - }; - - scope.close = function() { - scope.isOpen = false; - element[0].focus(); - }; - - var $popup = $compile(popupEl)(scope); - // Prevent jQuery cache memory leak (template is now redundant after linking) - popupEl.remove(); - - if ( appendToBody ) { - $document.find('body').append($popup); - } else { - element.after($popup); - } - - scope.$on('$destroy', function() { - $popup.remove(); - element.unbind('keydown', keydown); - $document.unbind('click', documentClickBind); - }); + ctrl.init(ngModel); } }; -}]) +}) -.directive('datepickerPopupWrap', function() { +.directive('uibDatepickerPopupWrap', function() { return { - restrict:'EA', replace: true, transclude: true, - templateUrl: 'template/datepicker/popup.html', - link:function (scope, element, attrs) { - element.bind('click', function(event) { - event.preventDefault(); - event.stopPropagation(); - }); + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/popup.html'; } }; }); -angular.module('ui.bootstrap.dropdown', []) +/* Deprecated datepicker below */ -.constant('dropdownConfig', { +angular.module('ui.bootstrap.datepicker') + +.value('$datepickerSuppressWarning', false) + +.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', '$datepickerSuppressWarning', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, $datepickerSuppressWarning) { + if (!$datepickerSuppressWarning) { + $log.warn('DatepickerController is now deprecated. Use UibDatepickerController instead.'); + } + + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; + + this.modes = ['day', 'month', 'year']; + + angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', + 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) { + self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; + }); + + angular.forEach(['minDate', 'maxDate'], function(key) { + if ($attrs[key]) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = value ? new Date(value) : null; + self.refreshView(); + }); + } else { + self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; + } + }); + + angular.forEach(['minMode', 'maxMode'], function(key) { + if ($attrs[key]) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = angular.isDefined(value) ? value : $attrs[key]; + $scope[key] = self[key]; + if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) { + $scope.datepickerMode = self[key]; + } + }); + } else { + self[key] = datepickerConfig[key] || null; + $scope[key] = self[key]; + } + }); + + $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; + $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); + + if (angular.isDefined($attrs.initDate)) { + this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date(); + $scope.$parent.$watch($attrs.initDate, function(initDate) { + if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { + self.activeDate = initDate; + self.refreshView(); + } + }); + } else { + this.activeDate = new Date(); + } + + $scope.isActive = function(dateObject) { + if (self.compare(dateObject.date, self.activeDate) === 0) { + $scope.activeDateId = dateObject.uid; + return true; + } + return false; + }; + + this.init = function(ngModelCtrl_) { + ngModelCtrl = ngModelCtrl_; + + ngModelCtrl.$render = function() { + self.render(); + }; + }; + + this.render = function() { + if (ngModelCtrl.$viewValue) { + var date = new Date(ngModelCtrl.$viewValue), + isValid = !isNaN(date); + + if (isValid) { + this.activeDate = date; + } else if (!$datepickerSuppressError) { + $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } + } + this.refreshView(); + }; + + this.refreshView = function() { + if (this.element) { + this._refreshView(); + + var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date))); + } + }; + + this.createDateObject = function(date, format) { + var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + return { + date: date, + label: dateFilter(date, format), + selected: model && this.compare(date, model) === 0, + disabled: this.isDisabled(date), + current: this.compare(date, new Date()) === 0, + customClass: this.customClass(date) + }; + }; + + this.isDisabled = function(date) { + return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); + }; + + this.customClass = function(date) { + return $scope.customClass({date: date, mode: $scope.datepickerMode}); + }; + + // Split array into smaller arrays + this.split = function(arr, size) { + var arrays = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + }; + + this.fixTimeZone = function(date) { + var hours = date.getHours(); + date.setHours(hours === 23 ? hours + 2 : 0); + }; + + $scope.select = function(date) { + if ($scope.datepickerMode === self.minMode) { + var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + ngModelCtrl.$setViewValue(dt); + ngModelCtrl.$render(); + } else { + self.activeDate = date; + $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1]; + } + }; + + $scope.move = function(direction) { + var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), + month = self.activeDate.getMonth() + direction * (self.step.months || 0); + self.activeDate.setFullYear(year, month, 1); + self.refreshView(); + }; + + $scope.toggleMode = function(direction) { + direction = direction || 1; + + if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { + return; + } + + $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction]; + }; + + // Key event mapper + $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; + + var focusElement = function() { + self.element[0].focus(); + }; + + $scope.$on('uib:datepicker.focus', focusElement); + + $scope.keydown = function(evt) { + var key = $scope.keys[evt.which]; + + if (!key || evt.shiftKey || evt.altKey) { + return; + } + + evt.preventDefault(); + if (!self.shortcutPropagation) { + evt.stopPropagation(); + } + + if (key === 'enter' || key === 'space') { + if (self.isDisabled(self.activeDate)) { + return; // do nothing + } + $scope.select(self.activeDate); + } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { + $scope.toggleMode(key === 'up' ? 1 : -1); + } else { + self.handleKeyDown(key, evt); + self.refreshView(); + } + }; +}]) + +.directive('datepicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/datepicker.html'; + }, + scope: { + datepickerMode: '=?', + dateDisabled: '&', + customClass: '&', + shortcutPropagation: '&?' + }, + require: ['datepicker', '^ngModel'], + controller: 'DatepickerController', + controllerAs: 'datepicker', + link: function(scope, element, attrs, ctrls) { + if (!$datepickerSuppressWarning) { + $log.warn('datepicker is now deprecated. Use uib-datepicker instead.'); + } + + var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + datepickerCtrl.init(ngModelCtrl); + } + }; +}]) + +.directive('daypicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + replace: true, + templateUrl: 'template/datepicker/day.html', + require: ['^datepicker', 'daypicker'], + controller: 'UibDaypickerController', + link: function(scope, element, attrs, ctrls) { + if (!$datepickerSuppressWarning) { + $log.warn('daypicker is now deprecated. Use uib-daypicker instead.'); + } + + var datepickerCtrl = ctrls[0], + daypickerCtrl = ctrls[1]; + + daypickerCtrl.init(datepickerCtrl); + } + }; +}]) + +.directive('monthpicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + replace: true, + templateUrl: 'template/datepicker/month.html', + require: ['^datepicker', 'monthpicker'], + controller: 'UibMonthpickerController', + link: function(scope, element, attrs, ctrls) { + if (!$datepickerSuppressWarning) { + $log.warn('monthpicker is now deprecated. Use uib-monthpicker instead.'); + } + + var datepickerCtrl = ctrls[0], + monthpickerCtrl = ctrls[1]; + + monthpickerCtrl.init(datepickerCtrl); + } + }; +}]) + +.directive('yearpicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + replace: true, + templateUrl: 'template/datepicker/year.html', + require: ['^datepicker', 'yearpicker'], + controller: 'UibYearpickerController', + link: function(scope, element, attrs, ctrls) { + if (!$datepickerSuppressWarning) { + $log.warn('yearpicker is now deprecated. Use uib-yearpicker instead.'); + } + + var ctrl = ctrls[0]; + angular.extend(ctrl, ctrls[1]); + ctrl.yearpickerInit(); + + ctrl.refreshView(); + } + }; +}]) + +.directive('datepickerPopup', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + require: ['ngModel', 'datepickerPopup'], + controller: 'UibDatepickerPopupController', + scope: { + isOpen: '=?', + currentText: '@', + clearText: '@', + closeText: '@', + dateDisabled: '&', + customClass: '&' + }, + link: function(scope, element, attrs, ctrls) { + if (!$datepickerSuppressWarning) { + $log.warn('datepicker-popup is now deprecated. Use uib-datepicker-popup instead.'); + } + + var ngModel = ctrls[0], + ctrl = ctrls[1]; + + ctrl.init(ngModel); + } + }; +}]) + +.directive('datepickerPopupWrap', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + replace: true, + transclude: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/popup.html'; + }, + link: function() { + if (!$datepickerSuppressWarning) { + $log.warn('datepicker-popup-wrap is now deprecated. Use uib-datepicker-popup-wrap instead.'); + } + } + }; +}]); + +angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) + +.constant('uibDropdownConfig', { openClass: 'open' }) -.service('dropdownService', ['$document', function($document) { +.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) { var openScope = null; - this.open = function( dropdownScope ) { - if ( !openScope ) { + this.open = function(dropdownScope) { + if (!openScope) { $document.bind('click', closeDropdown); - $document.bind('keydown', escapeKeyBind); + $document.bind('keydown', keybindFilter); } - if ( openScope && openScope !== dropdownScope ) { - openScope.isOpen = false; + if (openScope && openScope !== dropdownScope) { + openScope.isOpen = false; } openScope = dropdownScope; }; - this.close = function( dropdownScope ) { - if ( openScope === dropdownScope ) { + this.close = function(dropdownScope) { + if (openScope === dropdownScope) { openScope = null; $document.unbind('click', closeDropdown); - $document.unbind('keydown', escapeKeyBind); + $document.unbind('keydown', keybindFilter); } }; - var closeDropdown = function( evt ) { + var closeDropdown = function(evt) { // This method may still be called during the same mouse event that // unbound this event handler. So check openScope before proceeding. if (!openScope) { return; } + if (evt && openScope.getAutoClose() === 'disabled') { return ; } + var toggleElement = openScope.getToggleElement(); - if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { - return; + if (evt && toggleElement && toggleElement[0].contains(evt.target)) { + return; } - openScope.$apply(function() { - openScope.isOpen = false; - }); + var dropdownElement = openScope.getDropdownElement(); + if (evt && openScope.getAutoClose() === 'outsideClick' && + dropdownElement && dropdownElement[0].contains(evt.target)) { + return; + } + + openScope.isOpen = false; + + if (!$rootScope.$$phase) { + openScope.$apply(); + } }; - var escapeKeyBind = function( evt ) { - if ( evt.which === 27 ) { + var keybindFilter = function(evt) { + if (evt.which === 27) { openScope.focusToggleElement(); closeDropdown(); + } else if (openScope.isKeynavEnabled() && /(38|40)/.test(evt.which) && openScope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + openScope.focusDropdownEntry(evt.which); } }; }]) -.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) { +.controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) { var self = this, - scope = $scope.$new(), // create a child scope so we are not polluting original one - openClass = dropdownConfig.openClass, - getIsOpen, - setIsOpen = angular.noop, - toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop; + scope = $scope.$new(), // create a child scope so we are not polluting original one + templateScope, + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, + appendToBody = false, + keynavEnabled =false, + selectedOption = null; - this.init = function( element ) { - self.$element = element; - if ( $attrs.isOpen ) { + $element.addClass('dropdown'); + + this.init = function() { + if ($attrs.isOpen) { getIsOpen = $parse($attrs.isOpen); setIsOpen = getIsOpen.assign; @@ -1700,9 +2860,19 @@ angular.module('ui.bootstrap.dropdown', []) scope.isOpen = !!value; }); } + + appendToBody = angular.isDefined($attrs.dropdownAppendToBody); + keynavEnabled = angular.isDefined($attrs.uibKeyboardNav); + + if (appendToBody && self.dropdownMenu) { + $document.find('body').append(self.dropdownMenu); + $element.on('$destroy', function handleDestroyEvent() { + self.dropdownMenu.remove(); + }); + } }; - this.toggle = function( open ) { + this.toggle = function(open) { return scope.isOpen = arguments.length ? !!open : !scope.isOpen; }; @@ -1715,60 +2885,215 @@ angular.module('ui.bootstrap.dropdown', []) return self.toggleElement; }; + scope.getAutoClose = function() { + return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' + }; + + scope.getElement = function() { + return $element; + }; + + scope.isKeynavEnabled = function() { + return keynavEnabled; + }; + + scope.focusDropdownEntry = function(keyCode) { + var elems = self.dropdownMenu ? //If append to body is used. + (angular.element(self.dropdownMenu).find('a')) : + (angular.element($element).find('ul').eq(0).find('a')); + + switch (keyCode) { + case (40): { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = 0; + } else { + self.selectedOption = (self.selectedOption === elems.length - 1 ? + self.selectedOption : + self.selectedOption + 1); + } + break; + } + case (38): { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = elems.length - 1; + } else { + self.selectedOption = self.selectedOption === 0 ? + 0 : self.selectedOption - 1; + } + break; + } + } + elems[self.selectedOption].focus(); + }; + + scope.getDropdownElement = function() { + return self.dropdownMenu; + }; + scope.focusToggleElement = function() { - if ( self.toggleElement ) { + if (self.toggleElement) { self.toggleElement[0].focus(); } }; - scope.$watch('isOpen', function( isOpen, wasOpen ) { - $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); + scope.$watch('isOpen', function(isOpen, wasOpen) { + if (appendToBody && self.dropdownMenu) { + var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true); + var css = { + top: pos.top + 'px', + display: isOpen ? 'block' : 'none' + }; - if ( isOpen ) { - scope.focusToggleElement(); - dropdownService.open( scope ); - } else { - dropdownService.close( scope ); + var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right'); + if (!rightalign) { + css.left = pos.left + 'px'; + css.right = 'auto'; + } else { + css.left = 'auto'; + css.right = (window.innerWidth - (pos.left + $element.prop('offsetWidth'))) + 'px'; + } + + self.dropdownMenu.css(css); } - setIsOpen($scope, isOpen); - if (angular.isDefined(isOpen) && isOpen !== wasOpen) { - toggleInvoker($scope, { open: !!isOpen }); + $animate[isOpen ? 'addClass' : 'removeClass']($element, openClass).then(function() { + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + + if (isOpen) { + if (self.dropdownMenuTemplateUrl) { + $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) { + templateScope = scope.$new(); + $compile(tplContent.trim())(templateScope, function(dropdownElement) { + var newEl = dropdownElement; + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + }); + }); + } + + scope.focusToggleElement(); + uibDropdownService.open(scope); + } else { + if (self.dropdownMenuTemplateUrl) { + if (templateScope) { + templateScope.$destroy(); + } + var newEl = angular.element(''); + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + } + + uibDropdownService.close(scope); + self.selectedOption = null; + } + + if (angular.isFunction(setIsOpen)) { + setIsOpen($scope, isOpen); } }); $scope.$on('$locationChangeSuccess', function() { - scope.isOpen = false; + if (scope.getAutoClose() !== 'disabled') { + scope.isOpen = false; + } }); - $scope.$on('$destroy', function() { + var offDestroy = $scope.$on('$destroy', function() { scope.$destroy(); }); + scope.$on('$destroy', offDestroy); }]) -.directive('dropdown', function() { +.directive('uibDropdown', function() { return { - controller: 'DropdownController', + controller: 'UibDropdownController', link: function(scope, element, attrs, dropdownCtrl) { - dropdownCtrl.init( element ); + dropdownCtrl.init(); } }; }) -.directive('dropdownToggle', function() { +.directive('uibDropdownMenu', function() { return { - require: '?^dropdown', + restrict: 'AC', + require: '?^uibDropdown', link: function(scope, element, attrs, dropdownCtrl) { - if ( !dropdownCtrl ) { + if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) { return; } + element.addClass('dropdown-menu'); + + var tplUrl = attrs.templateUrl; + if (tplUrl) { + dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; + } + + if (!dropdownCtrl.dropdownMenu) { + dropdownCtrl.dropdownMenu = element; + } + } + }; +}) + +.directive('uibKeyboardNav', function() { + return { + restrict: 'A', + require: '?^uibDropdown', + link: function(scope, element, attrs, dropdownCtrl) { + element.bind('keydown', function(e) { + if ([38, 40].indexOf(e.which) !== -1) { + e.preventDefault(); + e.stopPropagation(); + + var elems = dropdownCtrl.dropdownMenu.find('a'); + + switch (e.which) { + case (40): { // Down + if (!angular.isNumber(dropdownCtrl.selectedOption)) { + dropdownCtrl.selectedOption = 0; + } else { + dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ? + dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1; + } + break; + } + case (38): { // Up + if (!angular.isNumber(dropdownCtrl.selectedOption)) { + dropdownCtrl.selectedOption = elems.length - 1; + } else { + dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ? + 0 : dropdownCtrl.selectedOption - 1; + } + break; + } + } + elems[dropdownCtrl.selectedOption].focus(); + } + }); + } + }; +}) + +.directive('uibDropdownToggle', function() { + return { + require: '?^uibDropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!dropdownCtrl) { + return; + } + + element.addClass('dropdown-toggle'); + dropdownCtrl.toggleElement = element; var toggleDropdown = function(event) { event.preventDefault(); - if ( !element.hasClass('disabled') && !attrs.disabled ) { + if (!element.hasClass('disabled') && !attrs.disabled) { scope.$apply(function() { dropdownCtrl.toggle(); }); @@ -1779,7 +3104,7 @@ angular.module('ui.bootstrap.dropdown', []) // WAI-ARIA element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); - scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { + scope.$watch(dropdownCtrl.isOpen, function(isOpen) { element.attr('aria-expanded', !!isOpen); }); @@ -1790,25 +3115,337 @@ angular.module('ui.bootstrap.dropdown', []) }; }); -angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) +/* Deprecated dropdown below */ +angular.module('ui.bootstrap.dropdown') + +.value('$dropdownSuppressWarning', false) + +.service('dropdownService', ['$log', '$dropdownSuppressWarning', 'uibDropdownService', function($log, $dropdownSuppressWarning, uibDropdownService) { + if (!$dropdownSuppressWarning) { + $log.warn('dropdownService is now deprecated. Use uibDropdownService instead.'); + } + + angular.extend(this, uibDropdownService); +}]) + +.controller('DropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', '$log', '$dropdownSuppressWarning', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest, $log, $dropdownSuppressWarning) { + if (!$dropdownSuppressWarning) { + $log.warn('DropdownController is now deprecated. Use UibDropdownController instead.'); + } + + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + templateScope, + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, + appendToBody = false, + keynavEnabled =false, + selectedOption = null; + + + $element.addClass('dropdown'); + + this.init = function() { + if ($attrs.isOpen) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + + appendToBody = angular.isDefined($attrs.dropdownAppendToBody); + keynavEnabled = angular.isDefined($attrs.uibKeyboardNav); + + if (appendToBody && self.dropdownMenu) { + $document.find('body').append(self.dropdownMenu); + $element.on('$destroy', function handleDestroyEvent() { + self.dropdownMenu.remove(); + }); + } + }; + + this.toggle = function(open) { + return scope.isOpen = arguments.length ? !!open : !scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.getToggleElement = function() { + return self.toggleElement; + }; + + scope.getAutoClose = function() { + return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' + }; + + scope.getElement = function() { + return $element; + }; + + scope.isKeynavEnabled = function() { + return keynavEnabled; + }; + + scope.focusDropdownEntry = function(keyCode) { + var elems = self.dropdownMenu ? //If append to body is used. + (angular.element(self.dropdownMenu).find('a')) : + (angular.element($element).find('ul').eq(0).find('a')); + + switch (keyCode) { + case (40): { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = 0; + } else { + self.selectedOption = (self.selectedOption === elems.length -1 ? + self.selectedOption : + self.selectedOption + 1); + } + break; + } + case (38): { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = elems.length - 1; + } else { + self.selectedOption = self.selectedOption === 0 ? + 0 : self.selectedOption - 1; + } + break; + } + } + elems[self.selectedOption].focus(); + }; + + scope.getDropdownElement = function() { + return self.dropdownMenu; + }; + + scope.focusToggleElement = function() { + if (self.toggleElement) { + self.toggleElement[0].focus(); + } + }; + + scope.$watch('isOpen', function(isOpen, wasOpen) { + if (appendToBody && self.dropdownMenu) { + var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true); + var css = { + top: pos.top + 'px', + display: isOpen ? 'block' : 'none' + }; + + var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right'); + if (!rightalign) { + css.left = pos.left + 'px'; + css.right = 'auto'; + } else { + css.left = 'auto'; + css.right = (window.innerWidth - (pos.left + $element.prop('offsetWidth'))) + 'px'; + } + + self.dropdownMenu.css(css); + } + + $animate[isOpen ? 'addClass' : 'removeClass']($element, openClass).then(function() { + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + + if (isOpen) { + if (self.dropdownMenuTemplateUrl) { + $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) { + templateScope = scope.$new(); + $compile(tplContent.trim())(templateScope, function(dropdownElement) { + var newEl = dropdownElement; + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + }); + }); + } + + scope.focusToggleElement(); + uibDropdownService.open(scope); + } else { + if (self.dropdownMenuTemplateUrl) { + if (templateScope) { + templateScope.$destroy(); + } + var newEl = angular.element(''); + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + } + + uibDropdownService.close(scope); + self.selectedOption = null; + } + + if (angular.isFunction(setIsOpen)) { + setIsOpen($scope, isOpen); + } + }); + + $scope.$on('$locationChangeSuccess', function() { + if (scope.getAutoClose() !== 'disabled') { + scope.isOpen = false; + } + }); + + var offDestroy = $scope.$on('$destroy', function() { + scope.$destroy(); + }); + scope.$on('$destroy', offDestroy); +}]) + +.directive('dropdown', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) { + return { + controller: 'DropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + if (!$dropdownSuppressWarning) { + $log.warn('dropdown is now deprecated. Use uib-dropdown instead.'); + } + + dropdownCtrl.init(); + } + }; +}]) + +.directive('dropdownMenu', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) { + return { + restrict: 'AC', + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) { + return; + } + + if (!$dropdownSuppressWarning) { + $log.warn('dropdown-menu is now deprecated. Use uib-dropdown-menu instead.'); + } + + element.addClass('dropdown-menu'); + + var tplUrl = attrs.templateUrl; + if (tplUrl) { + dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; + } + + if (!dropdownCtrl.dropdownMenu) { + dropdownCtrl.dropdownMenu = element; + } + } + }; +}]) + +.directive('keyboardNav', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) { + return { + restrict: 'A', + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!$dropdownSuppressWarning) { + $log.warn('keyboard-nav is now deprecated. Use uib-keyboard-nav instead.'); + } + + element.bind('keydown', function(e) { + if ([38, 40].indexOf(e.which) !== -1) { + e.preventDefault(); + e.stopPropagation(); + + var elems = dropdownCtrl.dropdownMenu.find('a'); + + switch (e.which) { + case (40): { // Down + if (!angular.isNumber(dropdownCtrl.selectedOption)) { + dropdownCtrl.selectedOption = 0; + } else { + dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ? + dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1; + } + break; + } + case (38): { // Up + if (!angular.isNumber(dropdownCtrl.selectedOption)) { + dropdownCtrl.selectedOption = elems.length - 1; + } else { + dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ? + 0 : dropdownCtrl.selectedOption - 1; + } + break; + } + } + elems[dropdownCtrl.selectedOption].focus(); + } + }); + } + }; +}]) + +.directive('dropdownToggle', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) { + return { + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!$dropdownSuppressWarning) { + $log.warn('dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead.'); + } + + if (!dropdownCtrl) { + return; + } + + element.addClass('dropdown-toggle'); + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if (!element.hasClass('disabled') && !attrs.disabled) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function(isOpen) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } + }; +}]); + +angular.module('ui.bootstrap.stackedMap', []) /** * A helper, internal data structure that acts as a map but also allows getting / removing * elements in the LIFO order */ - .factory('$$stackedMap', function () { + .factory('$$stackedMap', function() { return { - createNew: function () { + createNew: function() { var stack = []; return { - add: function (key, value) { + add: function(key, value) { stack.push({ key: key, value: value }); }, - get: function (key) { + get: function(key) { for (var i = 0; i < stack.length; i++) { if (key == stack[i].key) { return stack[i]; @@ -1822,10 +3459,10 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) } return keys; }, - top: function () { + top: function() { return stack[stack.length - 1]; }, - remove: function (key) { + remove: function(key) { var idx = -1; for (var i = 0; i < stack.length; i++) { if (key == stack[i].key) { @@ -1835,84 +3472,233 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) } return stack.splice(idx, 1)[0]; }, - removeTop: function () { + removeTop: function() { return stack.splice(stack.length - 1, 1)[0]; }, - length: function () { + length: function() { return stack.length; } }; } }; + }); +angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) +/** + * A helper, internal data structure that stores all references attached to key + */ + .factory('$$multiMap', function() { + return { + createNew: function() { + var map = {}; + + return { + entries: function() { + return Object.keys(map).map(function(key) { + return { + key: key, + value: map[key] + }; + }); + }, + get: function(key) { + return map[key]; + }, + hasKey: function(key) { + return !!map[key]; + }, + keys: function() { + return Object.keys(map); + }, + put: function(key, value) { + if (!map[key]) { + map[key] = []; + } + + map[key].push(value); + }, + remove: function(key, value) { + var values = map[key]; + + if (!values) { + return; + } + + var idx = values.indexOf(value); + + if (idx !== -1) { + values.splice(idx, 1); + } + + if (!values.length) { + delete map[key]; + } + } + }; + } + }; }) /** * A helper directive for the $modal service. It creates a backdrop element. */ - .directive('modalBackdrop', ['$timeout', function ($timeout) { + .directive('uibModalBackdrop', [ + '$animate', '$injector', '$uibModalStack', + function($animate , $injector, $modalStack) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + return { - restrict: 'EA', replace: true, templateUrl: 'template/modal/backdrop.html', - link: function (scope, element, attrs) { - scope.backdropClass = attrs.backdropClass || ''; - - scope.animate = false; - - //trigger CSS transitions - $timeout(function () { - scope.animate = true; - }); + compile: function(tElement, tAttrs) { + tElement.addClass(tAttrs.backdropClass); + return linkFn; } }; + + function linkFn(scope, element, attrs) { + // Temporary fix for prefixing + element.addClass('modal-backdrop'); + + if (attrs.modalInClass) { + if ($animateCss) { + $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + } else { + $animate.addClass(element, attrs.modalInClass); + } + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if ($animateCss) { + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + } else { + $animate.removeClass(element, attrs.modalInClass).then(done); + } + }); + } + } }]) - .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + .directive('uibModalWindow', [ + '$uibModalStack', '$q', '$animate', '$injector', + function($modalStack , $q , $animate, $injector) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + return { - restrict: 'EA', scope: { - index: '@', - animate: '=' + index: '@' }, replace: true, transclude: true, templateUrl: function(tElement, tAttrs) { return tAttrs.templateUrl || 'template/modal/window.html'; }, - link: function (scope, element, attrs) { + link: function(scope, element, attrs) { element.addClass(attrs.windowClass || ''); + element.addClass(attrs.windowTopClass || ''); scope.size = attrs.size; - $timeout(function () { - // trigger CSS transitions - scope.animate = true; - - /** - * Auto-focusing of a freshly-opened modal element causes any child elements - * with the autofocus attribute to lose focus. This is an issue on touch - * based devices which will show and then hide the onscreen keyboard. - * Attempts to refocus the autofocus element via JavaScript will not reopen - * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus - * the modal element if the modal does not contain an autofocus element. - */ - if (!element[0].querySelectorAll('[autofocus]').length) { - element[0].focus(); - } - }); - - scope.close = function (evt) { + scope.close = function(evt) { var modal = $modalStack.getTop(); - if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) { evt.preventDefault(); evt.stopPropagation(); $modalStack.dismiss(modal.key, 'backdrop click'); } }; + + // moved from template to fix issue #2280 + element.on('click', scope.close); + + // This property is only added to the scope for the purpose of detecting when this directive is rendered. + // We can detect that by using this property in the template associated with this directive and then use + // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}. + scope.$isRendered = true; + + // Deferred object that will be resolved when this modal is render. + var modalRenderDeferObj = $q.defer(); + // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready. + // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template. + attrs.$observe('modalRender', function(value) { + if (value == 'true') { + modalRenderDeferObj.resolve(); + } + }); + + modalRenderDeferObj.promise.then(function() { + var animationPromise = null; + + if (attrs.modalInClass) { + if ($animateCss) { + animationPromise = $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + } else { + animationPromise = $animate.addClass(element, attrs.modalInClass); + } + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if ($animateCss) { + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + } else { + $animate.removeClass(element, attrs.modalInClass).then(done); + } + }); + } + + + $q.when(animationPromise).then(function() { + var inputWithAutofocus = element[0].querySelector('[autofocus]'); + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to lose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (inputWithAutofocus) { + inputWithAutofocus.focus(); + } else { + element[0].focus(); + } + }); + + // Notify {@link $modalStack} that modal is rendered. + var modal = $modalStack.getTop(); + if (modal) { + $modalStack.modalRendered(modal.key); + } + }); } }; }]) - .directive('modalTransclude', function () { + .directive('uibModalAnimationClass', function() { + return { + compile: function(tElement, tAttrs) { + if (tAttrs.modalAnimation) { + tElement.addClass(tAttrs.uibModalAnimationClass); + } + } + }; + }) + + .directive('uibModalTransclude', function() { return { link: function($scope, $element, $attrs, controller, $transclude) { $transclude($scope.$parent, function(clone) { @@ -1923,14 +3709,38 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) }; }) - .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', - function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + .factory('$uibModalStack', [ + '$animate', '$timeout', '$document', '$compile', '$rootScope', + '$q', + '$injector', + '$$multiMap', + '$$stackedMap', + function($animate , $timeout , $document , $compile , $rootScope , + $q, + $injector, + $$multiMap, + $$stackedMap) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } var OPENED_MODAL_CLASS = 'modal-open'; var backdropDomEl, backdropScope; var openedWindows = $$stackedMap.createNew(); - var $modalStack = {}; + var openedClasses = $$multiMap.createNew(); + var $modalStack = { + NOW_CLOSING_EVENT: 'modal.stack.now-closing' + }; + + //Modal focus behavior + var focusableElementList; + var focusIndex = 0; + var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' + + 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' + + 'iframe, object, embed, *[tabindex], *[contenteditable=true]'; function backdropIndex() { var topBackdropIndex = -1; @@ -1943,59 +3753,76 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) return topBackdropIndex; } - $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + $rootScope.$watch(backdropIndex, function(newBackdropIndex) { if (backdropScope) { backdropScope.index = newBackdropIndex; } }); - function removeModalWindow(modalInstance) { - + function removeModalWindow(modalInstance, elementToReceiveFocus) { var body = $document.find('body').eq(0); var modalWindow = openedWindows.get(modalInstance).value; //clean up the stack openedWindows.remove(modalInstance); - //remove window DOM element - removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() { - modalWindow.modalScope.$destroy(); - body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); - checkRemoveBackdrop(); + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { + var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; + openedClasses.remove(modalBodyClass, modalInstance); + body.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass)); + toggleTopWindowClass(true); }); + checkRemoveBackdrop(); + + //move focus to specified element if available, or else to body + if (elementToReceiveFocus && elementToReceiveFocus.focus) { + elementToReceiveFocus.focus(); + } else { + body.focus(); + } + } + + // Add or remove "windowTopClass" from the top window in the stack + function toggleTopWindowClass(toggleSwitch) { + var modalWindow; + + if (openedWindows.length() > 0) { + modalWindow = openedWindows.top().value; + modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch); + } } function checkRemoveBackdrop() { - //remove backdrop if no longer needed - if (backdropDomEl && backdropIndex() == -1) { - var backdropScopeRef = backdropScope; - removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { - backdropScopeRef.$destroy(); - backdropScopeRef = null; - }); - backdropDomEl = undefined; - backdropScope = undefined; - } + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, function() { + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } } - function removeAfterAnimate(domEl, scope, emulateTime, done) { - // Closing animation - scope.animate = false; + function removeAfterAnimate(domEl, scope, done) { + var asyncDeferred; + var asyncPromise = null; + var setIsAsync = function() { + if (!asyncDeferred) { + asyncDeferred = $q.defer(); + asyncPromise = asyncDeferred.promise; + } - var transitionEndEventName = $transition.transitionEndEventName; - if (transitionEndEventName) { - // transition out - var timeout = $timeout(afterAnimating, emulateTime); + return function asyncDone() { + asyncDeferred.resolve(); + }; + }; + scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync); - domEl.bind(transitionEndEventName, function () { - $timeout.cancel(timeout); - afterAnimating(); - scope.$apply(); - }); - } else { - // Ensure this call is async - $timeout(afterAnimating); - } + // Note that it's intentional that asyncPromise might be null. + // That's when setIsAsync has not been called during the + // NOW_CLOSING_EVENT broadcast. + return $q.when(asyncPromise).then(afterAnimating); function afterAnimating() { if (afterAnimating.done) { @@ -2003,138 +3830,257 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) } afterAnimating.done = true; - domEl.remove(); + if ($animateCss) { + $animateCss(domEl, { + event: 'leave' + }).start().then(function() { + domEl.remove(); + }); + } else { + $animate.leave(domEl); + } + scope.$destroy(); if (done) { done(); } } } - $document.bind('keydown', function (evt) { - var modal; + $document.bind('keydown', function(evt) { + if (evt.isDefaultPrevented()) { + return evt; + } - if (evt.which === 27) { - modal = openedWindows.top(); - if (modal && modal.value.keyboard) { - evt.preventDefault(); - $rootScope.$apply(function () { - $modalStack.dismiss(modal.key, 'escape key press'); - }); + var modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + switch (evt.which) { + case 27: { + evt.preventDefault(); + $rootScope.$apply(function() { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + break; + } + case 9: { + $modalStack.loadFocusElementList(modal); + var focusChanged = false; + if (evt.shiftKey) { + if ($modalStack.isFocusInFirstItem(evt)) { + focusChanged = $modalStack.focusLastFocusableElement(); + } + } else { + if ($modalStack.isFocusInLastItem(evt)) { + focusChanged = $modalStack.focusFirstFocusableElement(); + } + } + + if (focusChanged) { + evt.preventDefault(); + evt.stopPropagation(); + } + break; + } } } }); - $modalStack.open = function (modalInstance, modal) { + $modalStack.open = function(modalInstance, modal) { + var modalOpener = $document[0].activeElement, + modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; + + toggleTopWindowClass(false); openedWindows.add(modalInstance, { deferred: modal.deferred, + renderDeferred: modal.renderDeferred, modalScope: modal.scope, backdrop: modal.backdrop, - keyboard: modal.keyboard + keyboard: modal.keyboard, + openedClass: modal.openedClass, + windowTopClass: modal.windowTopClass }); + openedClasses.put(modalBodyClass, modalInstance); + var body = $document.find('body').eq(0), currBackdropIndex = backdropIndex(); if (currBackdropIndex >= 0 && !backdropDomEl) { backdropScope = $rootScope.$new(true); backdropScope.index = currBackdropIndex; - var angularBackgroundDomEl = angular.element('
    '); + var angularBackgroundDomEl = angular.element('
    '); angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); + if (modal.animation) { + angularBackgroundDomEl.attr('modal-animation', 'true'); + } backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope); body.append(backdropDomEl); } - var angularDomEl = angular.element('
    '); + var angularDomEl = angular.element('
    '); angularDomEl.attr({ 'template-url': modal.windowTemplateUrl, 'window-class': modal.windowClass, + 'window-top-class': modal.windowTopClass, 'size': modal.size, 'index': openedWindows.length() - 1, 'animate': 'animate' }).html(modal.content); + if (modal.animation) { + angularDomEl.attr('modal-animation', 'true'); + } var modalDomEl = $compile(angularDomEl)(modal.scope); openedWindows.top().value.modalDomEl = modalDomEl; + openedWindows.top().value.modalOpener = modalOpener; body.append(modalDomEl); - body.addClass(OPENED_MODAL_CLASS); + body.addClass(modalBodyClass); + + $modalStack.clearFocusListCache(); }; - $modalStack.close = function (modalInstance, result) { + function broadcastClosing(modalWindow, resultOrReason, closing) { + return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; + } + + $modalStack.close = function(modalInstance, result) { var modalWindow = openedWindows.get(modalInstance); - if (modalWindow) { + if (modalWindow && broadcastClosing(modalWindow, result, true)) { + modalWindow.value.modalScope.$$uibDestructionScheduled = true; modalWindow.value.deferred.resolve(result); - removeModalWindow(modalInstance); + removeModalWindow(modalInstance, modalWindow.value.modalOpener); + return true; } + return !modalWindow; }; - $modalStack.dismiss = function (modalInstance, reason) { + $modalStack.dismiss = function(modalInstance, reason) { var modalWindow = openedWindows.get(modalInstance); - if (modalWindow) { + if (modalWindow && broadcastClosing(modalWindow, reason, false)) { + modalWindow.value.modalScope.$$uibDestructionScheduled = true; modalWindow.value.deferred.reject(reason); - removeModalWindow(modalInstance); + removeModalWindow(modalInstance, modalWindow.value.modalOpener); + return true; } + return !modalWindow; }; - $modalStack.dismissAll = function (reason) { + $modalStack.dismissAll = function(reason) { var topModal = this.getTop(); - while (topModal) { - this.dismiss(topModal.key, reason); + while (topModal && this.dismiss(topModal.key, reason)) { topModal = this.getTop(); } }; - $modalStack.getTop = function () { + $modalStack.getTop = function() { return openedWindows.top(); }; + $modalStack.modalRendered = function(modalInstance) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.renderDeferred.resolve(); + } + }; + + $modalStack.focusFirstFocusableElement = function() { + if (focusableElementList.length > 0) { + focusableElementList[0].focus(); + return true; + } + return false; + }; + $modalStack.focusLastFocusableElement = function() { + if (focusableElementList.length > 0) { + focusableElementList[focusableElementList.length - 1].focus(); + return true; + } + return false; + }; + + $modalStack.isFocusInFirstItem = function(evt) { + if (focusableElementList.length > 0) { + return (evt.target || evt.srcElement) == focusableElementList[0]; + } + return false; + }; + + $modalStack.isFocusInLastItem = function(evt) { + if (focusableElementList.length > 0) { + return (evt.target || evt.srcElement) == focusableElementList[focusableElementList.length - 1]; + } + return false; + }; + + $modalStack.clearFocusListCache = function() { + focusableElementList = []; + focusIndex = 0; + }; + + $modalStack.loadFocusElementList = function(modalWindow) { + if (focusableElementList === undefined || !focusableElementList.length) { + if (modalWindow) { + var modalDomE1 = modalWindow.value.modalDomEl; + if (modalDomE1 && modalDomE1.length) { + focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector); + } + } + } + }; + return $modalStack; }]) - .provider('$modal', function () { - + .provider('$uibModal', function() { var $modalProvider = { options: { - backdrop: true, //can be also false or 'static' + animation: true, + backdrop: true, //can also be false or 'static' keyboard: true }, - $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', - function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { - + $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$uibModalStack', '$modalSuppressWarning', '$log', + function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack, $modalSuppressWarning, $log) { var $modal = {}; function getTemplatePromise(options) { return options.template ? $q.when(options.template) : - $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl, - {cache: $templateCache}).then(function (result) { - return result.data; - }); + $templateRequest(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl); } function getResolvePromises(resolves) { var promisesArr = []; - angular.forEach(resolves, function (value) { + angular.forEach(resolves, function(value) { if (angular.isFunction(value) || angular.isArray(value)) { promisesArr.push($q.when($injector.invoke(value))); + } else if (angular.isString(value)) { + promisesArr.push($q.when($injector.get(value))); + } else { + promisesArr.push($q.when(value)); } }); return promisesArr; } - $modal.open = function (modalOptions) { + var promiseChain = null; + $modal.getPromiseChain = function() { + return promiseChain; + }; + $modal.open = function(modalOptions) { var modalResultDeferred = $q.defer(); var modalOpenedDeferred = $q.defer(); + var modalRenderDeferred = $q.defer(); //prepare an instance of a modal to be injected into controllers and returned to a caller var modalInstance = { result: modalResultDeferred.promise, opened: modalOpenedDeferred.promise, + rendered: modalRenderDeferred.promise, close: function (result) { - $modalStack.close(modalInstance, result); + return $modalStack.close(modalInstance, result); }, dismiss: function (reason) { - $modalStack.dismiss(modalInstance, reason); + return $modalStack.dismiss(modalInstance, reason); } }; @@ -2150,65 +4096,328 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) var templateAndResolvePromise = $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + function resolveWithTemplate() { + return templateAndResolvePromise; + } - templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + // Wait for the resolution of the existing promise chain. + // Then switch to our own combined promise dependency (regardless of how the previous modal fared). + // Then add to $modalStack and resolve opened. + // Finally clean up the chain variable if no subsequent modal has overwritten it. + var samePromise; + samePromise = promiseChain = $q.all([promiseChain]) + .then(resolveWithTemplate, resolveWithTemplate) + .then(function resolveSuccess(tplAndVars) { - var modalScope = (modalOptions.scope || $rootScope).$new(); - modalScope.$close = modalInstance.close; - modalScope.$dismiss = modalInstance.dismiss; + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; - var ctrlInstance, ctrlLocals = {}; - var resolveIter = 1; - - //controllers - if (modalOptions.controller) { - ctrlLocals.$scope = modalScope; - ctrlLocals.$modalInstance = modalInstance; - angular.forEach(modalOptions.resolve, function (value, key) { - ctrlLocals[key] = tplAndVars[resolveIter++]; + modalScope.$on('$destroy', function() { + if (!modalScope.$$uibDestructionScheduled) { + modalScope.$dismiss('$uibUnscheduledDestruction'); + } }); - ctrlInstance = $controller(modalOptions.controller, ctrlLocals); - if (modalOptions.controllerAs) { - modalScope[modalOptions.controllerAs] = ctrlInstance; - } - } + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; - $modalStack.open(modalInstance, { - scope: modalScope, - deferred: modalResultDeferred, - content: tplAndVars[0], - backdrop: modalOptions.backdrop, - keyboard: modalOptions.keyboard, - backdropClass: modalOptions.backdropClass, - windowClass: modalOptions.windowClass, - windowTemplateUrl: modalOptions.windowTemplateUrl, - size: modalOptions.size - }); + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$uibModalInstance = modalInstance; + Object.defineProperty(ctrlLocals, '$modalInstance', { + get: function() { + if (!$modalSuppressWarning) { + $log.warn('$modalInstance is now deprecated. Use $uibModalInstance instead.'); + } + + return modalInstance; + } + }); + angular.forEach(modalOptions.resolve, function(value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + if (modalOptions.controllerAs) { + if (modalOptions.bindToController) { + angular.extend(ctrlInstance, modalScope); + } + + modalScope[modalOptions.controllerAs] = ctrlInstance; + } + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + renderDeferred: modalRenderDeferred, + content: tplAndVars[0], + animation: modalOptions.animation, + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + backdropClass: modalOptions.backdropClass, + windowTopClass: modalOptions.windowTopClass, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size, + openedClass: modalOptions.openedClass + }); + modalOpenedDeferred.resolve(true); }, function resolveError(reason) { + modalOpenedDeferred.reject(reason); modalResultDeferred.reject(reason); - }); - - templateAndResolvePromise.then(function () { - modalOpenedDeferred.resolve(true); - }, function () { - modalOpenedDeferred.reject(false); + }) + .finally(function() { + if (promiseChain === samePromise) { + promiseChain = null; + } }); return modalInstance; }; return $modal; - }] + } + ] }; return $modalProvider; }); -angular.module('ui.bootstrap.pagination', []) +/* deprecated modal below */ -.controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) { +angular.module('ui.bootstrap.modal') + + .value('$modalSuppressWarning', false) + + /** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', [ + '$animate', '$injector', '$modalStack', '$log', '$modalSuppressWarning', + function($animate , $injector, $modalStack, $log, $modalSuppressWarning) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + + return { + replace: true, + templateUrl: 'template/modal/backdrop.html', + compile: function(tElement, tAttrs) { + tElement.addClass(tAttrs.backdropClass); + return linkFn; + } + }; + + function linkFn(scope, element, attrs) { + if (!$modalSuppressWarning) { + $log.warn('modal-backdrop is now deprecated. Use uib-modal-backdrop instead.'); + } + element.addClass('modal-backdrop'); + + if (attrs.modalInClass) { + if ($animateCss) { + $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + } else { + $animate.addClass(element, attrs.modalInClass); + } + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if ($animateCss) { + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + } else { + $animate.removeClass(element, attrs.modalInClass).then(done); + } + }); + } + } + }]) + + .directive('modalWindow', [ + '$modalStack', '$q', '$animate', '$injector', '$log', '$modalSuppressWarning', + function($modalStack , $q , $animate, $injector, $log, $modalSuppressWarning) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + + return { + scope: { + index: '@' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'template/modal/window.html'; + }, + link: function(scope, element, attrs) { + if (!$modalSuppressWarning) { + $log.warn('modal-window is now deprecated. Use uib-modal-window instead.'); + } + element.addClass(attrs.windowClass || ''); + element.addClass(attrs.windowTopClass || ''); + scope.size = attrs.size; + + scope.close = function(evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + + // moved from template to fix issue #2280 + element.on('click', scope.close); + + // This property is only added to the scope for the purpose of detecting when this directive is rendered. + // We can detect that by using this property in the template associated with this directive and then use + // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}. + scope.$isRendered = true; + + // Deferred object that will be resolved when this modal is render. + var modalRenderDeferObj = $q.defer(); + // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready. + // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template. + attrs.$observe('modalRender', function(value) { + if (value == 'true') { + modalRenderDeferObj.resolve(); + } + }); + + modalRenderDeferObj.promise.then(function() { + var animationPromise = null; + + if (attrs.modalInClass) { + if ($animateCss) { + animationPromise = $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + } else { + animationPromise = $animate.addClass(element, attrs.modalInClass); + } + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if ($animateCss) { + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + } else { + $animate.removeClass(element, attrs.modalInClass).then(done); + } + }); + } + + + $q.when(animationPromise).then(function() { + var inputWithAutofocus = element[0].querySelector('[autofocus]'); + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to lose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (inputWithAutofocus) { + inputWithAutofocus.focus(); + } else { + element[0].focus(); + } + }); + + // Notify {@link $modalStack} that modal is rendered. + var modal = $modalStack.getTop(); + if (modal) { + $modalStack.modalRendered(modal.key); + } + }); + } + }; + }]) + + .directive('modalAnimationClass', [ + '$log', '$modalSuppressWarning', + function ($log, $modalSuppressWarning) { + return { + compile: function(tElement, tAttrs) { + if (!$modalSuppressWarning) { + $log.warn('modal-animation-class is now deprecated. Use uib-modal-animation-class instead.'); + } + if (tAttrs.modalAnimation) { + tElement.addClass(tAttrs.modalAnimationClass); + } + } + }; + }]) + + .directive('modalTransclude', [ + '$log', '$modalSuppressWarning', + function ($log, $modalSuppressWarning) { + return { + link: function($scope, $element, $attrs, controller, $transclude) { + if (!$modalSuppressWarning) { + $log.warn('modal-transclude is now deprecated. Use uib-modal-transclude instead.'); + } + $transclude($scope.$parent, function(clone) { + $element.empty(); + $element.append(clone); + }); + } + }; + }]) + + .service('$modalStack', [ + '$animate', '$timeout', '$document', '$compile', '$rootScope', + '$q', + '$injector', + '$$multiMap', + '$$stackedMap', + '$uibModalStack', + '$log', + '$modalSuppressWarning', + function($animate , $timeout , $document , $compile , $rootScope , + $q, + $injector, + $$multiMap, + $$stackedMap, + $uibModalStack, + $log, + $modalSuppressWarning) { + if (!$modalSuppressWarning) { + $log.warn('$modalStack is now deprecated. Use $uibModalStack instead.'); + } + + angular.extend(this, $uibModalStack); + }]) + + .provider('$modal', ['$uibModalProvider', function($uibModalProvider) { + angular.extend(this, $uibModalProvider); + + this.$get = ['$injector', '$log', '$modalSuppressWarning', + function ($injector, $log, $modalSuppressWarning) { + if (!$modalSuppressWarning) { + $log.warn('$modal is now deprecated. Use $uibModal instead.'); + } + + return $injector.invoke($uibModalProvider.$get); + }]; + }]); + +angular.module('ui.bootstrap.pagination', []) +.controller('UibPaginationController', ['$scope', '$attrs', '$parse', function($scope, $attrs, $parse) { var self = this, ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; @@ -2229,6 +4438,20 @@ angular.module('ui.bootstrap.pagination', []) } else { this.itemsPerPage = config.itemsPerPage; } + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( $scope.page > value ) { + $scope.selectPage(value); + } else { + ngModelCtrl.$render(); + } + }); }; this.calculateTotalPages = function() { @@ -2240,39 +4463,35 @@ angular.module('ui.bootstrap.pagination', []) $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; }; - $scope.selectPage = function(page) { - if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) { + $scope.selectPage = function(page, evt) { + if (evt) { + evt.preventDefault(); + } + + var clickAllowed = !$scope.ngDisabled || !evt; + if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) { + if (evt && evt.target) { + evt.target.blur(); + } ngModelCtrl.$setViewValue(page); ngModelCtrl.$render(); } }; - $scope.getText = function( key ) { + $scope.getText = function(key) { return $scope[key + 'Text'] || self.config[key + 'Text']; }; + $scope.noPrevious = function() { return $scope.page === 1; }; + $scope.noNext = function() { return $scope.page === $scope.totalPages; }; - - $scope.$watch('totalItems', function() { - $scope.totalPages = self.calculateTotalPages(); - }); - - $scope.$watch('totalPages', function(value) { - setNumPages($scope.$parent, value); // Readonly variable - - if ( $scope.page > value ) { - $scope.selectPage(value); - } else { - ngModelCtrl.$render(); - } - }); }]) -.constant('paginationConfig', { +.constant('uibPaginationConfig', { itemsPerPage: 10, boundaryLinks: false, directionLinks: true, @@ -2283,7 +4502,7 @@ angular.module('ui.bootstrap.pagination', []) rotate: true }) -.directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) { +.directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, paginationConfig) { return { restrict: 'EA', scope: { @@ -2291,11 +4510,15 @@ angular.module('ui.bootstrap.pagination', []) firstText: '@', previousText: '@', nextText: '@', - lastText: '@' + lastText: '@', + ngDisabled:'=' + }, + require: ['uibPagination', '?ngModel'], + controller: 'UibPaginationController', + controllerAs: 'pagination', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/pagination/pagination.html'; }, - require: ['pagination', '?ngModel'], - controller: 'PaginationController', - templateUrl: 'template/pagination/pagination.html', replace: true, link: function(scope, element, attrs, ctrls) { var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; @@ -2333,11 +4556,11 @@ angular.module('ui.bootstrap.pagination', []) // Default page limits var startPage = 1, endPage = totalPages; - var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); + var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages; // recompute if maxSize - if ( isMaxSized ) { - if ( rotate ) { + if (isMaxSized) { + if (rotate) { // Current page is displayed in the middle of the visible ones startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); endPage = startPage + maxSize - 1; @@ -2363,13 +4586,13 @@ angular.module('ui.bootstrap.pagination', []) } // Add links to move between page sets - if ( isMaxSized && ! rotate ) { - if ( startPage > 1 ) { + if (isMaxSized && ! rotate) { + if (startPage > 1) { var previousPageSet = makePage(startPage - 1, '...', false); pages.unshift(previousPageSet); } - if ( endPage < totalPages ) { + if (endPage < totalPages) { var nextPageSet = makePage(endPage + 1, '...', false); pages.push(nextPageSet); } @@ -2389,26 +4612,256 @@ angular.module('ui.bootstrap.pagination', []) }; }]) -.constant('pagerConfig', { +.constant('uibPagerConfig', { itemsPerPage: 10, previousText: '« Previous', nextText: 'Next »', align: true }) -.directive('pager', ['pagerConfig', function(pagerConfig) { +.directive('uibPager', ['uibPagerConfig', function(pagerConfig) { return { restrict: 'EA', scope: { totalItems: '=', previousText: '@', - nextText: '@' + nextText: '@', + ngDisabled: '=' + }, + require: ['uibPager', '?ngModel'], + controller: 'UibPaginationController', + controllerAs: 'pagination', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/pagination/pager.html'; + }, + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align; + paginationCtrl.init(ngModelCtrl, pagerConfig); + } + }; +}]); + +/* Deprecated Pagination Below */ + +angular.module('ui.bootstrap.pagination') +.value('$paginationSuppressWarning', false) +.controller('PaginationController', ['$scope', '$attrs', '$parse', '$log', '$paginationSuppressWarning', function($scope, $attrs, $parse, $log, $paginationSuppressWarning) { + if (!$paginationSuppressWarning) { + $log.warn('PaginationController is now deprecated. Use UibPaginationController instead.'); + } + + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; + + this.init = function(ngModelCtrl_, config) { + ngModelCtrl = ngModelCtrl_; + this.config = config; + + ngModelCtrl.$render = function() { + self.render(); + }; + + if ($attrs.itemsPerPage) { + $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { + self.itemsPerPage = parseInt(value, 10); + $scope.totalPages = self.calculateTotalPages(); + }); + } else { + this.itemsPerPage = config.itemsPerPage; + } + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( $scope.page > value ) { + $scope.selectPage(value); + } else { + ngModelCtrl.$render(); + } + }); + }; + + this.calculateTotalPages = function() { + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); + }; + + this.render = function() { + $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; + }; + + $scope.selectPage = function(page, evt) { + if (evt) { + evt.preventDefault(); + } + + var clickAllowed = !$scope.ngDisabled || !evt; + if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) { + if (evt && evt.target) { + evt.target.blur(); + } + ngModelCtrl.$setViewValue(page); + ngModelCtrl.$render(); + } + }; + + $scope.getText = function(key) { + return $scope[key + 'Text'] || self.config[key + 'Text']; + }; + + $scope.noPrevious = function() { + return $scope.page === 1; + }; + + $scope.noNext = function() { + return $scope.page === $scope.totalPages; + }; +}]) +.directive('pagination', ['$parse', 'uibPaginationConfig', '$log', '$paginationSuppressWarning', function($parse, paginationConfig, $log, $paginationSuppressWarning) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + firstText: '@', + previousText: '@', + nextText: '@', + lastText: '@', + ngDisabled:'=' + }, + require: ['pagination', '?ngModel'], + controller: 'PaginationController', + controllerAs: 'pagination', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/pagination/pagination.html'; + }, + replace: true, + link: function(scope, element, attrs, ctrls) { + if (!$paginationSuppressWarning) { + $log.warn('pagination is now deprecated. Use uib-pagination instead.'); + } + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + // Setup configuration parameters + var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize, + rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate; + scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; + scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks; + + paginationCtrl.init(ngModelCtrl, paginationConfig); + + if (attrs.maxSize) { + scope.$parent.$watch($parse(attrs.maxSize), function(value) { + maxSize = parseInt(value, 10); + paginationCtrl.render(); + }); + } + + // Create page object used in template + function makePage(number, text, isActive) { + return { + number: number, + text: text, + active: isActive + }; + } + + function getPages(currentPage, totalPages) { + var pages = []; + + // Default page limits + var startPage = 1, endPage = totalPages; + var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages; + + // recompute if maxSize + if (isMaxSized) { + if (rotate) { + // Current page is displayed in the middle of the visible ones + startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); + endPage = startPage + maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > totalPages) { + endPage = totalPages; + startPage = endPage - maxSize + 1; + } + } else { + // Visible pages are paginated with maxSize + startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; + + // Adjust last page if limit is exceeded + endPage = Math.min(startPage + maxSize - 1, totalPages); + } + } + + // Add page number links + for (var number = startPage; number <= endPage; number++) { + var page = makePage(number, number, number === currentPage); + pages.push(page); + } + + // Add links to move between page sets + if (isMaxSized && ! rotate) { + if (startPage > 1) { + var previousPageSet = makePage(startPage - 1, '...', false); + pages.unshift(previousPageSet); + } + + if (endPage < totalPages) { + var nextPageSet = makePage(endPage + 1, '...', false); + pages.push(nextPageSet); + } + } + + return pages; + } + + var originalRender = paginationCtrl.render; + paginationCtrl.render = function() { + originalRender(); + if (scope.page > 0 && scope.page <= scope.totalPages) { + scope.pages = getPages(scope.page, scope.totalPages); + } + }; + } + }; +}]) + +.directive('pager', ['uibPagerConfig', '$log', '$paginationSuppressWarning', function(pagerConfig, $log, $paginationSuppressWarning) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + previousText: '@', + nextText: '@', + ngDisabled: '=' }, require: ['pager', '?ngModel'], controller: 'PaginationController', - templateUrl: 'template/pagination/pager.html', + controllerAs: 'pagination', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/pagination/pager.html'; + }, replace: true, link: function(scope, element, attrs, ctrls) { + if (!$paginationSuppressWarning) { + $log.warn('pager is now deprecated. Use uib-pager instead.'); + } var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; if (!ngModelCtrl) { @@ -2426,25 +4879,28 @@ angular.module('ui.bootstrap.pagination', []) * function, placement as a function, inside, support for more triggers than * just mouse enter/leave, html tooltips, and selector delegation. */ -angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) +angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap']) /** * The $tooltip service creates tooltip- and popover-like directives as well as * houses global options for them. */ -.provider( '$tooltip', function () { +.provider('$uibTooltip', function() { // The default options tooltip and popover. var defaultOptions = { placement: 'top', animation: true, - popupDelay: 0 + popupDelay: 0, + popupCloseDelay: 0, + useContentExp: false }; // Default hide triggers for each show trigger var triggerMap = { 'mouseenter': 'mouseleave', 'click': 'click', - 'focus': 'blur' + 'focus': 'blur', + 'none': '' }; // The options specified to the provider globally. @@ -2459,8 +4915,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * $tooltipProvider.options( { placement: 'left' } ); * }); */ - this.options = function( value ) { - angular.extend( globalOptions, value ); + this.options = function(value) { + angular.extend(globalOptions, value); }; /** @@ -2468,14 +4924,14 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); */ - this.setTriggers = function setTriggers ( triggers ) { - angular.extend( triggerMap, triggers ); + this.setTriggers = function setTriggers(triggers) { + angular.extend(triggerMap, triggers); }; /** * This is a helper function for translating camel-case to snake-case. */ - function snake_case(name){ + function snake_case(name) { var regexp = /[A-Z]/g; var separator = '-'; return name.replace(regexp, function(letter, pos) { @@ -2487,9 +4943,21 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * Returns the actual instance of the $tooltip service. * TODO support multiple triggers */ - this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) { - return function $tooltip ( type, prefix, defaultTriggerShow ) { - var options = angular.extend( {}, defaultOptions, globalOptions ); + this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) { + var openedTooltips = $$stackedMap.createNew(); + $document.on('keypress', function(e) { + if (e.which === 27) { + var last = openedTooltips.top(); + if (last) { + last.value.close(); + openedTooltips.removeTop(); + last = null; + } + } + }); + + return function $tooltip(ttType, prefix, defaultTriggerShow, options) { + options = angular.extend({}, defaultOptions, globalOptions, options); /** * Returns an object of show and hide triggers. @@ -2505,60 +4973,89 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * undefined; otherwise, it uses the `triggerMap` value of the show * trigger; else it will just use the show trigger. */ - function getTriggers ( trigger ) { - var show = trigger || options.trigger || defaultTriggerShow; - var hide = triggerMap[show] || show; + function getTriggers(trigger) { + var show = (trigger || options.trigger || defaultTriggerShow).split(' '); + var hide = show.map(function(trigger) { + return triggerMap[trigger] || trigger; + }); return { show: show, hide: hide }; } - var directiveName = snake_case( type ); + var directiveName = snake_case(ttType); var startSym = $interpolate.startSymbol(); var endSym = $interpolate.endSymbol(); var template = - '
    '+ + '
    ' + '
    '; return { - restrict: 'EA', - compile: function (tElem, tAttrs) { - var tooltipLinker = $compile( template ); + compile: function(tElem, tAttrs) { + var tooltipLinker = $compile(template); - return function link ( scope, element, attrs ) { + return function link(scope, element, attrs, tooltipCtrl) { var tooltip; var tooltipLinkedScope; var transitionTimeout; - var popupTimeout; - var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; - var triggers = getTriggers( undefined ); - var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + var showTimeout; + var hideTimeout; + var positionTimeout; + var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false; + var triggers = getTriggers(undefined); + var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']); var ttScope = scope.$new(true); + var repositionScheduled = false; + var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false; + var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false; + var observers = []; - var positionTooltip = function () { + var positionTooltip = function() { + // check if tooltip exists and is not empty + if (!tooltip || !tooltip.html()) { return; } - var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); - ttPosition.top += 'px'; - ttPosition.left += 'px'; + if (!positionTimeout) { + positionTimeout = $timeout(function() { + // Reset the positioning. + tooltip.css({ top: 0, left: 0 }); - // Now set the calculated positioning. - tooltip.css( ttPosition ); + // Now set the calculated positioning. + var ttCss = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); + ttCss.top += 'px'; + ttCss.left += 'px'; + ttCss.visibility = 'visible'; + tooltip.css(ttCss); + + positionTimeout = null; + }, 0, false); + } }; + // Set up the correct scope to allow transclusion later + ttScope.origScope = scope; + // By default, the tooltip is not open. // TODO add ability to start tooltip opened ttScope.isOpen = false; + openedTooltips.add(ttScope, { + close: hide + }); - function toggleTooltipBind () { - if ( ! ttScope.isOpen ) { + function toggleTooltipBind() { + if (!ttScope.isOpen) { showTooltipBind(); } else { hideTooltipBind(); @@ -2567,101 +5064,127 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // Show the tooltip with delay if specified, otherwise show it immediately function showTooltipBind() { - if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) { return; } + cancelHide(); prepareTooltip(); - if ( ttScope.popupDelay ) { + if (ttScope.popupDelay) { // Do nothing if the tooltip was already scheduled to pop-up. // This happens if show is triggered multiple times before any hide is triggered. - if (!popupTimeout) { - popupTimeout = $timeout( show, ttScope.popupDelay, false ); - popupTimeout.then(function(reposition){reposition();}); + if (!showTimeout) { + showTimeout = $timeout(show, ttScope.popupDelay, false); } } else { - show()(); + show(); } } - function hideTooltipBind () { - scope.$apply(function () { + function hideTooltipBind() { + cancelShow(); + + if (ttScope.popupCloseDelay) { + if (!hideTimeout) { + hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false); + } + } else { hide(); - }); + } } // Show the tooltip popup element. function show() { - - popupTimeout = null; - - // If there is a pending remove transition, we must cancel it, lest the - // tooltip be mysteriously removed. - if ( transitionTimeout ) { - $timeout.cancel( transitionTimeout ); - transitionTimeout = null; - } + cancelShow(); + cancelHide(); // Don't show empty tooltips. - if ( ! ttScope.content ) { + if (!ttScope.content) { return angular.noop; } createTooltip(); - // Set the initial positioning. - tooltip.css({ top: 0, left: 0, display: 'block' }); - ttScope.$digest(); - - positionTooltip(); - // And show the tooltip. - ttScope.isOpen = true; - ttScope.$digest(); // digest required as $apply is not called + ttScope.$evalAsync(function() { + ttScope.isOpen = true; + assignIsOpen(true); + positionTooltip(); + }); + } - // Return positioning function as promise callback for correct - // positioning after draw. - return positionTooltip; + function cancelShow() { + if (showTimeout) { + $timeout.cancel(showTimeout); + showTimeout = null; + } + + if (positionTimeout) { + $timeout.cancel(positionTimeout); + positionTimeout = null; + } } // Hide the tooltip popup element. function hide() { + cancelShow(); + cancelHide(); + + if (!ttScope) { + return; + } + // First things first: we don't show it anymore. - ttScope.isOpen = false; - - //if tooltip is going to be shown after delay, we must cancel this - $timeout.cancel( popupTimeout ); - popupTimeout = null; - - // And now we remove it from the DOM. However, if we have animation, we - // need to wait for it to expire beforehand. - // FIXME: this is a placeholder for a port of the transitions library. - if ( ttScope.animation ) { - if (!transitionTimeout) { - transitionTimeout = $timeout(removeTooltip, 500); + ttScope.$evalAsync(function() { + ttScope.isOpen = false; + assignIsOpen(false); + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + // The fade transition in TWBS is 150ms. + if (ttScope.animation) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 150, false); + } + } else { + removeTooltip(); } - } else { - removeTooltip(); + }); + } + + function cancelHide() { + if (hideTimeout) { + $timeout.cancel(hideTimeout); + hideTimeout = null; + } + if (transitionTimeout) { + $timeout.cancel(transitionTimeout); + transitionTimeout = null; } } function createTooltip() { // There can only be one tooltip element per directive shown at once. if (tooltip) { - removeTooltip(); + return; } + tooltipLinkedScope = ttScope.$new(); - tooltip = tooltipLinker(tooltipLinkedScope, function (tooltip) { - if ( appendToBody ) { - $document.find( 'body' ).append( tooltip ); + tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) { + if (appendToBody) { + $document.find('body').append(tooltip); } else { - element.after( tooltip ); + element.after(tooltip); } }); + + prepObservers(); } function removeTooltip() { + unregisterObservers(); + transitionTimeout = null; if (tooltip) { tooltip.remove(); @@ -2673,55 +5196,166 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap } } + /** + * Set the inital scope values. Once + * the tooltip is created, the observers + * will be added to keep things in synch. + */ function prepareTooltip() { - prepPlacement(); - prepPopupDelay(); + ttScope.title = attrs[prefix + 'Title']; + if (contentParse) { + ttScope.content = contentParse(scope); + } else { + ttScope.content = attrs[ttType]; + } + + ttScope.popupClass = attrs[prefix + 'Class']; + ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement; + + var delay = parseInt(attrs[prefix + 'PopupDelay'], 10); + var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10); + ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay; + ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay; } + function assignIsOpen(isOpen) { + if (isOpenParse && angular.isFunction(isOpenParse.assign)) { + isOpenParse.assign(scope, isOpen); + } + } + + ttScope.contentExp = function() { + return ttScope.content; + }; + /** * Observe the relevant attributes. */ - attrs.$observe( type, function ( val ) { - ttScope.content = val; + attrs.$observe('disabled', function(val) { + if (val) { + cancelShow(); + } - if (!val && ttScope.isOpen ) { + if (val && ttScope.isOpen) { hide(); } }); - attrs.$observe( prefix+'Title', function ( val ) { - ttScope.title = val; - }); - - function prepPlacement() { - var val = attrs[ prefix + 'Placement' ]; - ttScope.placement = angular.isDefined( val ) ? val : options.placement; + if (isOpenParse) { + scope.$watch(isOpenParse, function(val) { + /*jshint -W018 */ + if (ttScope && !val === ttScope.isOpen) { + toggleTooltipBind(); + } + /*jshint +W018 */ + }); } - function prepPopupDelay() { - var val = attrs[ prefix + 'PopupDelay' ]; - var delay = parseInt( val, 10 ); - ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + function prepObservers() { + observers.length = 0; + + if (contentParse) { + observers.push( + scope.$watch(contentParse, function(val) { + ttScope.content = val; + if (!val && ttScope.isOpen) { + hide(); + } + }) + ); + + observers.push( + tooltipLinkedScope.$watch(function() { + if (!repositionScheduled) { + repositionScheduled = true; + tooltipLinkedScope.$$postDigest(function() { + repositionScheduled = false; + if (ttScope && ttScope.isOpen) { + positionTooltip(); + } + }); + } + }) + ); + } else { + observers.push( + attrs.$observe(ttType, function(val) { + ttScope.content = val; + if (!val && ttScope.isOpen) { + hide(); + } else { + positionTooltip(); + } + }) + ); + } + + observers.push( + attrs.$observe(prefix + 'Title', function(val) { + ttScope.title = val; + if (ttScope.isOpen) { + positionTooltip(); + } + }) + ); + + observers.push( + attrs.$observe(prefix + 'Placement', function(val) { + ttScope.placement = val ? val : options.placement; + if (ttScope.isOpen) { + positionTooltip(); + } + }) + ); } - var unregisterTriggers = function () { - element.unbind(triggers.show, showTooltipBind); - element.unbind(triggers.hide, hideTooltipBind); + function unregisterObservers() { + if (observers.length) { + angular.forEach(observers, function(observer) { + observer(); + }); + observers.length = 0; + } + } + + var unregisterTriggers = function() { + triggers.show.forEach(function(trigger) { + element.unbind(trigger, showTooltipBind); + }); + triggers.hide.forEach(function(trigger) { + trigger.split(' ').forEach(function(hideTrigger) { + element[0].removeEventListener(hideTrigger, hideTooltipBind); + }); + }); }; function prepTriggers() { - var val = attrs[ prefix + 'Trigger' ]; + var val = attrs[prefix + 'Trigger']; unregisterTriggers(); - triggers = getTriggers( val ); + triggers = getTriggers(val); - if ( triggers.show === triggers.hide ) { - element.bind( triggers.show, toggleTooltipBind ); - } else { - element.bind( triggers.show, showTooltipBind ); - element.bind( triggers.hide, hideTooltipBind ); + if (triggers.show !== 'none') { + triggers.show.forEach(function(trigger, idx) { + // Using raw addEventListener due to jqLite/jQuery bug - #4060 + if (trigger === triggers.hide[idx]) { + element[0].addEventListener(trigger, toggleTooltipBind); + } else if (trigger) { + element[0].addEventListener(trigger, showTooltipBind); + triggers.hide[idx].split(' ').forEach(function(trigger) { + element[0].addEventListener(trigger, hideTooltipBind); + }); + } + + element.on('keypress', function(e) { + if (e.which === 27) { + hideTooltipBind(); + } + }); + }); } } + prepTriggers(); var animation = scope.$eval(attrs[prefix + 'Animation']); @@ -2733,20 +5367,21 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // if a tooltip is attached to we need to remove it on // location change as its parent scope will probably not be destroyed // by the change. - if ( appendToBody ) { - scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { - if ( ttScope.isOpen ) { - hide(); - } - }); + if (appendToBody) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() { + if (ttScope.isOpen) { + hide(); + } + }); } // Make sure tooltip is destroyed and removed. scope.$on('$destroy', function onDestroyTooltip() { - $timeout.cancel( transitionTimeout ); - $timeout.cancel( popupTimeout ); + cancelShow(); + cancelHide(); unregisterTriggers(); removeTooltip(); + openedTooltips.remove(ttScope); ttScope = null; }); }; @@ -2756,172 +5391,733 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap }]; }) -.directive( 'tooltipPopup', function () { +// This is mostly ngInclude code but with a custom scope +.directive('uibTooltipTemplateTransclude', [ + '$animate', '$sce', '$compile', '$templateRequest', +function ($animate , $sce , $compile , $templateRequest) { return { - restrict: 'EA', - replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-popup.html' - }; -}) + link: function(scope, elem, attrs) { + var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope); -.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); + var changeCounter = 0, + currentScope, + previousElement, + currentElement; + + var cleanupLastIncludeContent = function() { + if (previousElement) { + previousElement.remove(); + previousElement = null; + } + + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + + if (currentElement) { + $animate.leave(currentElement).then(function() { + previousElement = null; + }); + previousElement = currentElement; + currentElement = null; + } + }; + + scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) { + var thisChangeId = ++changeCounter; + + if (src) { + //set the 2nd param to true to ignore the template request error so that the inner + //contents and scope can be cleaned up. + $templateRequest(src, true).then(function(response) { + if (thisChangeId !== changeCounter) { return; } + var newScope = origScope.$new(); + var template = response; + + var clone = $compile(template)(newScope, function(clone) { + cleanupLastIncludeContent(); + $animate.enter(clone, elem); + }); + + currentScope = newScope; + currentElement = clone; + + currentScope.$emit('$includeContentLoaded', src); + }, function() { + if (thisChangeId === changeCounter) { + cleanupLastIncludeContent(); + scope.$emit('$includeContentError', src); + } + }); + scope.$emit('$includeContentRequested', src); + } else { + cleanupLastIncludeContent(); + } + }); + + scope.$on('$destroy', cleanupLastIncludeContent); + } + }; }]) -.directive( 'tooltipHtmlUnsafePopup', function () { +/** + * Note that it's intentional that these classes are *not* applied through $animate. + * They must not be animated as they're expected to be present on the tooltip on + * initialization. + */ +.directive('uibTooltipClasses', function() { return { - restrict: 'EA', - replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + restrict: 'A', + link: function(scope, element, attrs) { + if (scope.placement) { + element.addClass(scope.placement); + } + + if (scope.popupClass) { + element.addClass(scope.popupClass); + } + + if (scope.animation()) { + element.addClass(attrs.tooltipAnimationClass); + } + } }; }) -.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +.directive('uibTooltipPopup', function() { + return { + replace: true, + scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html', + link: function(scope, element) { + element.addClass('tooltip'); + } + }; +}) + +.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter'); +}]) + +.directive('uibTooltipTemplatePopup', function() { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'template/tooltip/tooltip-template-popup.html', + link: function(scope, element) { + element.addClass('tooltip'); + } + }; +}) + +.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', { + useContentExp: true + }); +}]) + +.directive('uibTooltipHtmlPopup', function() { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-popup.html', + link: function(scope, element) { + element.addClass('tooltip'); + } + }; +}) + +.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', { + useContentExp: true + }); +}]); + +/* Deprecated tooltip below */ + +angular.module('ui.bootstrap.tooltip') + +.value('$tooltipSuppressWarning', false) + +.provider('$tooltip', ['$uibTooltipProvider', function($uibTooltipProvider) { + angular.extend(this, $uibTooltipProvider); + + this.$get = ['$log', '$tooltipSuppressWarning', '$injector', function($log, $tooltipSuppressWarning, $injector) { + if (!$tooltipSuppressWarning) { + $log.warn('$tooltip is now deprecated. Use $uibTooltip instead.'); + } + + return $injector.invoke($uibTooltipProvider.$get); + }]; +}]) + +// This is mostly ngInclude code but with a custom scope +.directive('tooltipTemplateTransclude', [ + '$animate', '$sce', '$compile', '$templateRequest', '$log', '$tooltipSuppressWarning', +function ($animate , $sce , $compile , $templateRequest, $log, $tooltipSuppressWarning) { + return { + link: function(scope, elem, attrs) { + if (!$tooltipSuppressWarning) { + $log.warn('tooltip-template-transclude is now deprecated. Use uib-tooltip-template-transclude instead.'); + } + + var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope); + + var changeCounter = 0, + currentScope, + previousElement, + currentElement; + + var cleanupLastIncludeContent = function() { + if (previousElement) { + previousElement.remove(); + previousElement = null; + } + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if (currentElement) { + $animate.leave(currentElement).then(function() { + previousElement = null; + }); + previousElement = currentElement; + currentElement = null; + } + }; + + scope.$watch($sce.parseAsResourceUrl(attrs.tooltipTemplateTransclude), function(src) { + var thisChangeId = ++changeCounter; + + if (src) { + //set the 2nd param to true to ignore the template request error so that the inner + //contents and scope can be cleaned up. + $templateRequest(src, true).then(function(response) { + if (thisChangeId !== changeCounter) { return; } + var newScope = origScope.$new(); + var template = response; + + var clone = $compile(template)(newScope, function(clone) { + cleanupLastIncludeContent(); + $animate.enter(clone, elem); + }); + + currentScope = newScope; + currentElement = clone; + + currentScope.$emit('$includeContentLoaded', src); + }, function() { + if (thisChangeId === changeCounter) { + cleanupLastIncludeContent(); + scope.$emit('$includeContentError', src); + } + }); + scope.$emit('$includeContentRequested', src); + } else { + cleanupLastIncludeContent(); + } + }); + + scope.$on('$destroy', cleanupLastIncludeContent); + } + }; +}]) + +.directive('tooltipClasses', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) { + return { + restrict: 'A', + link: function(scope, element, attrs) { + if (!$tooltipSuppressWarning) { + $log.warn('tooltip-classes is now deprecated. Use uib-tooltip-classes instead.'); + } + + if (scope.placement) { + element.addClass(scope.placement); + } + if (scope.popupClass) { + element.addClass(scope.popupClass); + } + if (scope.animation()) { + element.addClass(attrs.tooltipAnimationClass); + } + } + }; +}]) + +.directive('tooltipPopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) { + return { + replace: true, + scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html', + link: function(scope, element) { + if (!$tooltipSuppressWarning) { + $log.warn('tooltip-popup is now deprecated. Use uib-tooltip-popup instead.'); + } + + element.addClass('tooltip'); + } + }; +}]) + +.directive('tooltip', ['$tooltip', function($tooltip) { + return $tooltip('tooltip', 'tooltip', 'mouseenter'); +}]) + +.directive('tooltipTemplatePopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'template/tooltip/tooltip-template-popup.html', + link: function(scope, element) { + if (!$tooltipSuppressWarning) { + $log.warn('tooltip-template-popup is now deprecated. Use uib-tooltip-template-popup instead.'); + } + + element.addClass('tooltip'); + } + }; +}]) + +.directive('tooltipTemplate', ['$tooltip', function($tooltip) { + return $tooltip('tooltipTemplate', 'tooltip', 'mouseenter', { + useContentExp: true + }); +}]) + +.directive('tooltipHtmlPopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-popup.html', + link: function(scope, element) { + if (!$tooltipSuppressWarning) { + $log.warn('tooltip-html-popup is now deprecated. Use uib-tooltip-html-popup instead.'); + } + + element.addClass('tooltip'); + } + }; +}]) + +.directive('tooltipHtml', ['$tooltip', function($tooltip) { + return $tooltip('tooltipHtml', 'tooltip', 'mouseenter', { + useContentExp: true + }); }]); /** * The following features are still outstanding: popup delay, animation as a * function, placement as a function, inside, support for more triggers than - * just mouse enter/leave, html popovers, and selector delegatation. + * just mouse enter/leave, and selector delegatation. */ -angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) +angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip']) -.directive( 'popoverPopup', function () { +.directive('uibPopoverTemplatePopup', function() { return { - restrict: 'EA', replace: true, - scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/popover/popover.html' + scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'template/popover/popover-template.html', + link: function(scope, element) { + element.addClass('popover'); + } }; }) -.directive( 'popover', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'popover', 'popover', 'click' ); +.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopoverTemplate', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('uibPopoverHtmlPopup', function() { + return { + replace: true, + scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover-html.html', + link: function(scope, element) { + element.addClass('popover'); + } + }; +}) + +.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopoverHtml', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('uibPopoverPopup', function() { + return { + replace: true, + scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html', + link: function(scope, element) { + element.addClass('popover'); + } + }; +}) + +.directive('uibPopover', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopover', 'popover', 'click'); +}]); + +/* Deprecated popover below */ + +angular.module('ui.bootstrap.popover') + +.value('$popoverSuppressWarning', false) + +.directive('popoverTemplatePopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) { + return { + replace: true, + scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'template/popover/popover-template.html', + link: function(scope, element) { + if (!$popoverSuppressWarning) { + $log.warn('popover-template-popup is now deprecated. Use uib-popover-template-popup instead.'); + } + + element.addClass('popover'); + } + }; +}]) + +.directive('popoverTemplate', ['$tooltip', function($tooltip) { + return $tooltip('popoverTemplate', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('popoverHtmlPopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) { + return { + replace: true, + scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover-html.html', + link: function(scope, element) { + if (!$popoverSuppressWarning) { + $log.warn('popover-html-popup is now deprecated. Use uib-popover-html-popup instead.'); + } + + element.addClass('popover'); + } + }; +}]) + +.directive('popoverHtml', ['$tooltip', function($tooltip) { + return $tooltip('popoverHtml', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('popoverPopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) { + return { + replace: true, + scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html', + link: function(scope, element) { + if (!$popoverSuppressWarning) { + $log.warn('popover-popup is now deprecated. Use uib-popover-popup instead.'); + } + + element.addClass('popover'); + } + }; +}]) + +.directive('popover', ['$tooltip', function($tooltip) { + + return $tooltip('popover', 'popover', 'click'); }]); angular.module('ui.bootstrap.progressbar', []) -.constant('progressConfig', { +.constant('uibProgressConfig', { animate: true, max: 100 }) -.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { - var self = this, - animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; +.controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) { + var self = this, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; - this.bars = []; - $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max; + this.bars = []; + $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max; - this.addBar = function(bar, element) { - if ( !animate ) { - element.css({'transition': 'none'}); - } + this.addBar = function(bar, element, attrs) { + if (!animate) { + element.css({'transition': 'none'}); + } - this.bars.push(bar); + this.bars.push(bar); - bar.$watch('value', function( value ) { - bar.percent = +(100 * value / $scope.max).toFixed(2); - }); + bar.max = $scope.max; + bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar'; - bar.$on('$destroy', function() { - element = null; - self.removeBar(bar); - }); + bar.$watch('value', function(value) { + bar.recalculatePercentage(); + }); + + bar.recalculatePercentage = function() { + var totalPercentage = self.bars.reduce(function(total, bar) { + bar.percent = +(100 * bar.value / bar.max).toFixed(2); + return total + bar.percent; + }, 0); + + if (totalPercentage > 100) { + bar.percent -= totalPercentage - 100; + } }; - this.removeBar = function(bar) { - this.bars.splice(this.bars.indexOf(bar), 1); - }; + bar.$on('$destroy', function() { + element = null; + self.removeBar(bar); + }); + }; + + this.removeBar = function(bar) { + this.bars.splice(this.bars.indexOf(bar), 1); + this.bars.forEach(function (bar) { + bar.recalculatePercentage(); + }); + }; + + $scope.$watch('max', function(max) { + self.bars.forEach(function(bar) { + bar.max = $scope.max; + bar.recalculatePercentage(); + }); + }); }]) -.directive('progress', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - controller: 'ProgressController', - require: 'progress', - scope: {}, - templateUrl: 'template/progressbar/progress.html' - }; +.directive('uibProgress', function() { + return { + replace: true, + transclude: true, + controller: 'UibProgressController', + require: 'uibProgress', + scope: { + max: '=?' + }, + templateUrl: 'template/progressbar/progress.html' + }; }) -.directive('bar', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - require: '^progress', - scope: { - value: '=', - type: '@' - }, - templateUrl: 'template/progressbar/bar.html', - link: function(scope, element, attrs, progressCtrl) { - progressCtrl.addBar(scope, element); - } - }; +.directive('uibBar', function() { + return { + replace: true, + transclude: true, + require: '^uibProgress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, element, attrs); + } + }; }) -.directive('progressbar', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - controller: 'ProgressController', - scope: { - value: '=', - type: '@' - }, - templateUrl: 'template/progressbar/progressbar.html', - link: function(scope, element, attrs, progressCtrl) { - progressCtrl.addBar(scope, angular.element(element.children()[0])); - } - }; +.directive('uibProgressbar', function() { + return { + replace: true, + transclude: true, + controller: 'UibProgressController', + scope: { + value: '=', + max: '=?', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title}); + } + }; }); + +/* Deprecated progressbar below */ + +angular.module('ui.bootstrap.progressbar') + +.value('$progressSuppressWarning', false) + +.controller('ProgressController', ['$scope', '$attrs', 'uibProgressConfig', '$log', '$progressSuppressWarning', function($scope, $attrs, progressConfig, $log, $progressSuppressWarning) { + if (!$progressSuppressWarning) { + $log.warn('ProgressController is now deprecated. Use UibProgressController instead.'); + } + + var self = this, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; + + this.bars = []; + $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max; + + this.addBar = function(bar, element, attrs) { + if (!animate) { + element.css({'transition': 'none'}); + } + + this.bars.push(bar); + + bar.max = $scope.max; + bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar'; + + bar.$watch('value', function(value) { + bar.recalculatePercentage(); + }); + + bar.recalculatePercentage = function() { + bar.percent = +(100 * bar.value / bar.max).toFixed(2); + + var totalPercentage = self.bars.reduce(function(total, bar) { + return total + bar.percent; + }, 0); + + if (totalPercentage > 100) { + bar.percent -= totalPercentage - 100; + } + }; + + bar.$on('$destroy', function() { + element = null; + self.removeBar(bar); + }); + }; + + this.removeBar = function(bar) { + this.bars.splice(this.bars.indexOf(bar), 1); + }; + + $scope.$watch('max', function(max) { + self.bars.forEach(function(bar) { + bar.max = $scope.max; + bar.recalculatePercentage(); + }); + }); +}]) + +.directive('progress', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) { + return { + replace: true, + transclude: true, + controller: 'ProgressController', + require: 'progress', + scope: { + max: '=?', + title: '@?' + }, + templateUrl: 'template/progressbar/progress.html', + link: function() { + if (!$progressSuppressWarning) { + $log.warn('progress is now deprecated. Use uib-progress instead.'); + } + } + }; +}]) + +.directive('bar', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) { + return { + replace: true, + transclude: true, + require: '^progress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + if (!$progressSuppressWarning) { + $log.warn('bar is now deprecated. Use uib-bar instead.'); + } + progressCtrl.addBar(scope, element); + } + }; +}]) + +.directive('progressbar', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) { + return { + replace: true, + transclude: true, + controller: 'ProgressController', + scope: { + value: '=', + max: '=?', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + if (!$progressSuppressWarning) { + $log.warn('progressbar is now deprecated. Use uib-progressbar instead.'); + } + progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title}); + } + }; +}]); + angular.module('ui.bootstrap.rating', []) -.constant('ratingConfig', { +.constant('uibRatingConfig', { max: 5, stateOn: null, - stateOff: null + stateOff: null, + titles : ['one', 'two', 'three', 'four', 'five'] }) -.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) { +.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) { var ngModelCtrl = { $setViewValue: angular.noop }; this.init = function(ngModelCtrl_) { ngModelCtrl = ngModelCtrl_; ngModelCtrl.$render = this.render; + ngModelCtrl.$formatters.push(function(value) { + if (angular.isNumber(value) && value << 0 !== value) { + value = Math.round(value); + } + return value; + }); + this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; + var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ; + this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ? + tmpTitles : ratingConfig.titles; - var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : - new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max ); + var ratingStates = angular.isDefined($attrs.ratingStates) ? + $scope.$parent.$eval($attrs.ratingStates) : + new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max); $scope.range = this.buildTemplateObjects(ratingStates); }; this.buildTemplateObjects = function(states) { for (var i = 0, n = states.length; i < n; i++) { - states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]); + states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]); } return states; }; + this.getTitle = function(index) { + if (index >= this.titles.length) { + return index + 1; + } else { + return this.titles[index]; + } + }; + $scope.rate = function(value) { - if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) { - ngModelCtrl.$setViewValue(value); + if (!$scope.readonly && value >= 0 && value <= $scope.range.length) { + ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value); ngModelCtrl.$render(); } }; $scope.enter = function(value) { - if ( !$scope.readonly ) { + if (!$scope.readonly) { $scope.value = value; } $scope.onHover({value: value}); @@ -2936,7 +6132,7 @@ angular.module('ui.bootstrap.rating', []) if (/(37|38|39|40)/.test(evt.which)) { evt.preventDefault(); evt.stopPropagation(); - $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) ); + $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1)); } }; @@ -2945,9 +6141,43 @@ angular.module('ui.bootstrap.rating', []) }; }]) -.directive('rating', function() { +.directive('uibRating', function() { + return { + require: ['uibRating', 'ngModel'], + scope: { + readonly: '=?', + onHover: '&', + onLeave: '&' + }, + controller: 'UibRatingController', + templateUrl: 'template/rating/rating.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + ratingCtrl.init(ngModelCtrl); + } + }; +}); + +/* Deprecated rating below */ + +angular.module('ui.bootstrap.rating') + +.value('$ratingSuppressWarning', false) + +.controller('RatingController', ['$scope', '$attrs', '$controller', '$log', '$ratingSuppressWarning', function($scope, $attrs, $controller, $log, $ratingSuppressWarning) { + if (!$ratingSuppressWarning) { + $log.warn('RatingController is now deprecated. Use UibRatingController instead.'); + } + + angular.extend(this, $controller('UibRatingController', { + $scope: $scope, + $attrs: $attrs + })); +}]) + +.directive('rating', ['$log', '$ratingSuppressWarning', function($log, $ratingSuppressWarning) { return { - restrict: 'EA', require: ['rating', 'ngModel'], scope: { readonly: '=?', @@ -2958,14 +6188,15 @@ angular.module('ui.bootstrap.rating', []) templateUrl: 'template/rating/rating.html', replace: true, link: function(scope, element, attrs, ctrls) { - var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - if ( ngModelCtrl ) { - ratingCtrl.init( ngModelCtrl ); + if (!$ratingSuppressWarning) { + $log.warn('rating is now deprecated. Use uib-rating instead.'); } + var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + ratingCtrl.init(ngModelCtrl); } }; -}); +}]); + /** * @ngdoc overview @@ -2977,7 +6208,7 @@ angular.module('ui.bootstrap.rating', []) angular.module('ui.bootstrap.tabs', []) -.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { +.controller('UibTabsetController', ['$scope', function ($scope) { var ctrl = this, tabs = ctrl.tabs = $scope.tabs = []; @@ -2986,20 +6217,27 @@ angular.module('ui.bootstrap.tabs', []) if (tab.active && tab !== selectedTab) { tab.active = false; tab.onDeselect(); + selectedTab.selectCalled = false; } }); selectedTab.active = true; - selectedTab.onSelect(); + // only call select if it has not already been called + if (!selectedTab.selectCalled) { + selectedTab.onSelect(); + selectedTab.selectCalled = true; + } }; ctrl.addTab = function addTab(tab) { tabs.push(tab); // we can't run the select function on the first tab // since that would select it twice - if (tabs.length === 1) { + if (tabs.length === 1 && tab.active !== false) { tab.active = true; } else if (tab.active) { ctrl.select(tab); + } else { + tab.active = false; } }; @@ -3034,23 +6272,23 @@ angular.module('ui.bootstrap.tabs', []) * @example - - First Content! - Second Content! - + + First Content! + Second Content! +
    - - First Vertical Content! - Second Vertical Content! - - - First Justified Content! - Second Justified Content! - + + First Vertical Content! + Second Vertical Content! + + + First Justified Content! + Second Justified Content! +
    */ -.directive('tabset', function() { +.directive('uibTabset', function() { return { restrict: 'EA', transclude: true, @@ -3058,7 +6296,7 @@ angular.module('ui.bootstrap.tabs', []) scope: { type: '@' }, - controller: 'TabsetController', + controller: 'UibTabsetController', templateUrl: 'template/tabs/tabset.html', link: function(scope, element, attrs) { scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; @@ -3091,19 +6329,19 @@ angular.module('ui.bootstrap.tabs', []) Enable/disable item 2, using disabled binding
    - - First Tab - - Alert me! + + First Tab + + Alert me! Second Tab, with alert callback and html heading! - - + {{item.content}} - - + +
    @@ -3134,22 +6372,22 @@ angular.module('ui.bootstrap.tabs', []) * @example - - - HTML in my titles?! + + + HTML in my titles?! And some content, too! - - - Icon heading?!? + + + Icon heading?!? That's right. - - + + */ -.directive('tab', ['$parse', function($parse) { +.directive('uibTab', ['$parse', function($parse) { return { - require: '^tabset', + require: '^uibTabset', restrict: 'EA', replace: true, templateUrl: 'template/tabs/tab.html', @@ -3164,45 +6402,43 @@ angular.module('ui.bootstrap.tabs', []) controller: function() { //Empty controller so other directives can require being 'under' a tab }, - compile: function(elm, attrs, transclude) { - return function postLink(scope, elm, attrs, tabsetCtrl) { - scope.$watch('active', function(active) { - if (active) { - tabsetCtrl.select(scope); - } - }); - - scope.disabled = false; - if ( attrs.disabled ) { - scope.$parent.$watch($parse(attrs.disabled), function(value) { - scope.disabled = !! value; - }); + link: function(scope, elm, attrs, tabsetCtrl, transclude) { + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); } + }); - scope.select = function() { - if ( !scope.disabled ) { - scope.active = true; - } - }; - - tabsetCtrl.addTab(scope); - scope.$on('$destroy', function() { - tabsetCtrl.removeTab(scope); + scope.disabled = false; + if (attrs.disable) { + scope.$parent.$watch($parse(attrs.disable), function(value) { + scope.disabled = !! value; }); + } - //We need to transclude later, once the content container is ready. - //when this link happens, we're inside a tab heading. - scope.$transcludeFn = transclude; + scope.select = function() { + if (!scope.disabled) { + scope.active = true; + } }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; } }; }]) -.directive('tabHeadingTransclude', [function() { +.directive('uibTabHeadingTransclude', function() { return { restrict: 'A', - require: '^tab', - link: function(scope, elm, attrs, tabCtrl) { + require: ['?^uibTab', '?^tab'], // TODO: change to '^uibTab' after deprecation removal + link: function(scope, elm) { scope.$watch('headingElement', function updateHeadingElement(heading) { if (heading) { elm.html(''); @@ -3211,14 +6447,14 @@ angular.module('ui.bootstrap.tabs', []) }); } }; -}]) +}) -.directive('tabContentTransclude', function() { +.directive('uibTabContentTransclude', function() { return { restrict: 'A', - require: '^tabset', + require: ['?^uibTabset', '?^tabset'], // TODO: change to '^uibTabset' after deprecation removal link: function(scope, elm, attrs) { - var tab = scope.$eval(attrs.tabContentTransclude); + var tab = scope.$eval(attrs.uibTabContentTransclude); //Now our tab is ready to be transcluded: both the tab heading area //and the tab content area are loaded. Transclude 'em both. @@ -3234,48 +6470,217 @@ angular.module('ui.bootstrap.tabs', []) }); } }; + function isTabHeading(node) { - return node.tagName && ( - node.hasAttribute('tab-heading') || - node.hasAttribute('data-tab-heading') || - node.tagName.toLowerCase() === 'tab-heading' || - node.tagName.toLowerCase() === 'data-tab-heading' + return node.tagName && ( + node.hasAttribute('tab-heading') || // TODO: remove after deprecation removal + node.hasAttribute('data-tab-heading') || // TODO: remove after deprecation removal + node.hasAttribute('x-tab-heading') || // TODO: remove after deprecation removal + node.hasAttribute('uib-tab-heading') || + node.hasAttribute('data-uib-tab-heading') || + node.hasAttribute('x-uib-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || // TODO: remove after deprecation removal + node.tagName.toLowerCase() === 'data-tab-heading' || // TODO: remove after deprecation removal + node.tagName.toLowerCase() === 'x-tab-heading' || // TODO: remove after deprecation removal + node.tagName.toLowerCase() === 'uib-tab-heading' || + node.tagName.toLowerCase() === 'data-uib-tab-heading' || + node.tagName.toLowerCase() === 'x-uib-tab-heading' ); } -}) +}); -; +/* deprecated tabs below */ + +angular.module('ui.bootstrap.tabs') + + .value('$tabsSuppressWarning', false) + + .controller('TabsetController', ['$scope', '$controller', '$log', '$tabsSuppressWarning', function($scope, $controller, $log, $tabsSuppressWarning) { + if (!$tabsSuppressWarning) { + $log.warn('TabsetController is now deprecated. Use UibTabsetController instead.'); + } + + angular.extend(this, $controller('UibTabsetController', { + $scope: $scope + })); + }]) + + .directive('tabset', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: { + type: '@' + }, + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + + if (!$tabsSuppressWarning) { + $log.warn('tabset is now deprecated. Use uib-tabset instead.'); + } + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; + } + }; + }]) + + .directive('tab', ['$parse', '$log', '$tabsSuppressWarning', function($parse, $log, $tabsSuppressWarning) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + active: '=?', + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + link: function(scope, elm, attrs, tabsetCtrl, transclude) { + if (!$tabsSuppressWarning) { + $log.warn('tab is now deprecated. Use uib-tab instead.'); + } + + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); + } + }); + + scope.disabled = false; + if (attrs.disable) { + scope.$parent.$watch($parse(attrs.disable), function(value) { + scope.disabled = !!value; + }); + } + + scope.select = function() { + if (!scope.disabled) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + } + }; + }]) + + .directive('tabHeadingTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm) { + if (!$tabsSuppressWarning) { + $log.warn('tab-heading-transclude is now deprecated. Use uib-tab-heading-transclude instead.'); + } + + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; + }]) + + .directive('tabContentTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + if (!$tabsSuppressWarning) { + $log.warn('tab-content-transclude is now deprecated. Use uib-tab-content-transclude instead.'); + } + + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } + else { + elm.append(node); + } + }); + }); + } + }; + + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.hasAttribute('x-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' || + node.tagName.toLowerCase() === 'x-tab-heading' + ); + } + }]); angular.module('ui.bootstrap.timepicker', []) -.constant('timepickerConfig', { +.constant('uibTimepickerConfig', { hourStep: 1, minuteStep: 1, showMeridian: true, meridians: null, readonlyInput: false, - mousewheel: true + mousewheel: true, + arrowkeys: true, + showSpinners: true }) -.controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) { +.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) { var selected = new Date(), ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; - this.init = function( ngModelCtrl_, inputs ) { + $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0; + $element.removeAttr('tabindex'); + + this.init = function(ngModelCtrl_, inputs) { ngModelCtrl = ngModelCtrl_; ngModelCtrl.$render = this.render; + ngModelCtrl.$formatters.unshift(function(modelValue) { + return modelValue ? new Date(modelValue) : null; + }); + var hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1); var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; - if ( mousewheel ) { - this.setupMousewheelEvents( hoursInputEl, minutesInputEl ); + if (mousewheel) { + this.setupMousewheelEvents(hoursInputEl, minutesInputEl); + } + + var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys; + if (arrowkeys) { + this.setupArrowkeyEvents(hoursInputEl, minutesInputEl); } $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; - this.setupInputEvents( hoursInputEl, minutesInputEl ); + this.setupInputEvents(hoursInputEl, minutesInputEl); }; var hourStep = timepickerConfig.hourStep; @@ -3292,17 +6697,61 @@ angular.module('ui.bootstrap.timepicker', []) }); } + var min; + $scope.$parent.$watch($parse($attrs.min), function(value) { + var dt = new Date(value); + min = isNaN(dt) ? undefined : dt; + }); + + var max; + $scope.$parent.$watch($parse($attrs.max), function(value) { + var dt = new Date(value); + max = isNaN(dt) ? undefined : dt; + }); + + $scope.noIncrementHours = function() { + var incrementedSelected = addMinutes(selected, hourStep * 60); + return incrementedSelected > max || + (incrementedSelected < selected && incrementedSelected < min); + }; + + $scope.noDecrementHours = function() { + var decrementedSelected = addMinutes(selected, -hourStep * 60); + return decrementedSelected < min || + (decrementedSelected > selected && decrementedSelected > max); + }; + + $scope.noIncrementMinutes = function() { + var incrementedSelected = addMinutes(selected, minuteStep); + return incrementedSelected > max || + (incrementedSelected < selected && incrementedSelected < min); + }; + + $scope.noDecrementMinutes = function() { + var decrementedSelected = addMinutes(selected, -minuteStep); + return decrementedSelected < min || + (decrementedSelected > selected && decrementedSelected > max); + }; + + $scope.noToggleMeridian = function() { + if (selected.getHours() < 13) { + return addMinutes(selected, 12 * 60) > max; + } else { + return addMinutes(selected, -12 * 60) < min; + } + }; + // 12H / 24H mode $scope.showMeridian = timepickerConfig.showMeridian; if ($attrs.showMeridian) { $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { $scope.showMeridian = !!value; - if ( ngModelCtrl.$error.time ) { + if (ngModelCtrl.$error.time) { // Evaluate from template var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); - if (angular.isDefined( hours ) && angular.isDefined( minutes )) { - selected.setHours( hours ); + if (angular.isDefined(hours) && angular.isDefined(minutes)) { + selected.setHours(hours); refresh(); } } else { @@ -3312,18 +6761,18 @@ angular.module('ui.bootstrap.timepicker', []) } // Get $scope.hours in 24H mode if valid - function getHoursFromTemplate ( ) { - var hours = parseInt( $scope.hours, 10 ); - var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); - if ( !valid ) { + function getHoursFromTemplate() { + var hours = parseInt($scope.hours, 10); + var valid = $scope.showMeridian ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); + if (!valid) { return undefined; } - if ( $scope.showMeridian ) { - if ( hours === 12 ) { + if ($scope.showMeridian) { + if (hours === 12) { hours = 0; } - if ( $scope.meridian === meridians[1] ) { + if ($scope.meridian === meridians[1]) { hours = hours + 12; } } @@ -3332,15 +6781,15 @@ angular.module('ui.bootstrap.timepicker', []) function getMinutesFromTemplate() { var minutes = parseInt($scope.minutes, 10); - return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; + return (minutes >= 0 && minutes < 60) ? minutes : undefined; } - function pad( value ) { - return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; + function pad(value) { + return (angular.isDefined(value) && value.toString().length < 2) ? '0' + value : value.toString(); } // Respond on mousewheel spin - this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) { + this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl) { var isScrollingUp = function(e) { if (e.originalEvent) { e = e.originalEvent; @@ -3351,26 +6800,53 @@ angular.module('ui.bootstrap.timepicker', []) }; hoursInputEl.bind('mousewheel wheel', function(e) { - $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() ); + $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours()); e.preventDefault(); }); minutesInputEl.bind('mousewheel wheel', function(e) { - $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() ); + $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes()); e.preventDefault(); }); }; - this.setupInputEvents = function( hoursInputEl, minutesInputEl ) { - if ( $scope.readonlyInput ) { + // Respond on up/down arrowkeys + this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl) { + hoursInputEl.bind('keydown', function(e) { + if (e.which === 38) { // up + e.preventDefault(); + $scope.incrementHours(); + $scope.$apply(); + } else if (e.which === 40) { // down + e.preventDefault(); + $scope.decrementHours(); + $scope.$apply(); + } + }); + + minutesInputEl.bind('keydown', function(e) { + if (e.which === 38) { // up + e.preventDefault(); + $scope.incrementMinutes(); + $scope.$apply(); + } else if (e.which === 40) { // down + e.preventDefault(); + $scope.decrementMinutes(); + $scope.$apply(); + } + }); + }; + + this.setupInputEvents = function(hoursInputEl, minutesInputEl) { + if ($scope.readonlyInput) { $scope.updateHours = angular.noop; $scope.updateMinutes = angular.noop; return; } var invalidate = function(invalidHours, invalidMinutes) { - ngModelCtrl.$setViewValue( null ); + ngModelCtrl.$setViewValue(null); ngModelCtrl.$setValidity('time', false); if (angular.isDefined(invalidHours)) { $scope.invalidHours = invalidHours; @@ -3381,39 +6857,49 @@ angular.module('ui.bootstrap.timepicker', []) }; $scope.updateHours = function() { - var hours = getHoursFromTemplate(); + var hours = getHoursFromTemplate(), + minutes = getMinutesFromTemplate(); - if ( angular.isDefined(hours) ) { - selected.setHours( hours ); - refresh( 'h' ); + if (angular.isDefined(hours) && angular.isDefined(minutes)) { + selected.setHours(hours); + if (selected < min || selected > max) { + invalidate(true); + } else { + refresh('h'); + } } else { invalidate(true); } }; hoursInputEl.bind('blur', function(e) { - if ( !$scope.invalidHours && $scope.hours < 10) { - $scope.$apply( function() { - $scope.hours = pad( $scope.hours ); + if (!$scope.invalidHours && $scope.hours < 10) { + $scope.$apply(function() { + $scope.hours = pad($scope.hours); }); } }); $scope.updateMinutes = function() { - var minutes = getMinutesFromTemplate(); + var minutes = getMinutesFromTemplate(), + hours = getHoursFromTemplate(); - if ( angular.isDefined(minutes) ) { - selected.setMinutes( minutes ); - refresh( 'm' ); + if (angular.isDefined(minutes) && angular.isDefined(hours)) { + selected.setMinutes(minutes); + if (selected < min || selected > max) { + invalidate(undefined, true); + } else { + refresh('m'); + } } else { invalidate(undefined, true); } }; minutesInputEl.bind('blur', function(e) { - if ( !$scope.invalidMinutes && $scope.minutes < 10 ) { - $scope.$apply( function() { - $scope.minutes = pad( $scope.minutes ); + if (!$scope.invalidMinutes && $scope.minutes < 10) { + $scope.$apply(function() { + $scope.minutes = pad($scope.minutes); }); } }); @@ -3421,25 +6907,32 @@ angular.module('ui.bootstrap.timepicker', []) }; this.render = function() { - var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null; + var date = ngModelCtrl.$viewValue; - if ( isNaN(date) ) { + if (isNaN(date)) { ngModelCtrl.$setValidity('time', false); $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); } else { - if ( date ) { + if (date) { selected = date; } - makeValid(); + + if (selected < min || selected > max) { + ngModelCtrl.$setValidity('time', false); + $scope.invalidHours = true; + $scope.invalidMinutes = true; + } else { + makeValid(); + } updateTemplate(); } }; // Call internally when we know that model is valid. - function refresh( keyboardChange ) { + function refresh(keyboardChange) { makeValid(); - ngModelCtrl.$setViewValue( new Date(selected) ); - updateTemplate( keyboardChange ); + ngModelCtrl.$setViewValue(new Date(selected)); + updateTemplate(keyboardChange); } function makeValid() { @@ -3448,255 +6941,519 @@ angular.module('ui.bootstrap.timepicker', []) $scope.invalidMinutes = false; } - function updateTemplate( keyboardChange ) { + function updateTemplate(keyboardChange) { var hours = selected.getHours(), minutes = selected.getMinutes(); - if ( $scope.showMeridian ) { - hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system + if ($scope.showMeridian) { + hours = (hours === 0 || hours === 12) ? 12 : hours % 12; // Convert 24 to 12 hour system } $scope.hours = keyboardChange === 'h' ? hours : pad(hours); - $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); + if (keyboardChange !== 'm') { + $scope.minutes = pad(minutes); + } $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; } - function addMinutes( minutes ) { - var dt = new Date( selected.getTime() + minutes * 60000 ); - selected.setHours( dt.getHours(), dt.getMinutes() ); + function addMinutes(date, minutes) { + var dt = new Date(date.getTime() + minutes * 60000); + var newDate = new Date(date); + newDate.setHours(dt.getHours(), dt.getMinutes()); + return newDate; + } + + function addMinutesToSelected(minutes) { + selected = addMinutes(selected, minutes); refresh(); } + $scope.showSpinners = angular.isDefined($attrs.showSpinners) ? + $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners; + $scope.incrementHours = function() { - addMinutes( hourStep * 60 ); + if (!$scope.noIncrementHours()) { + addMinutesToSelected(hourStep * 60); + } }; + $scope.decrementHours = function() { - addMinutes( - hourStep * 60 ); + if (!$scope.noDecrementHours()) { + addMinutesToSelected(-hourStep * 60); + } }; + $scope.incrementMinutes = function() { - addMinutes( minuteStep ); + if (!$scope.noIncrementMinutes()) { + addMinutesToSelected(minuteStep); + } }; + $scope.decrementMinutes = function() { - addMinutes( - minuteStep ); + if (!$scope.noDecrementMinutes()) { + addMinutesToSelected(-minuteStep); + } }; + $scope.toggleMeridian = function() { - addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); + if (!$scope.noToggleMeridian()) { + addMinutesToSelected(12 * 60 * (selected.getHours() < 12 ? 1 : -1)); + } }; }]) -.directive('timepicker', function () { +.directive('uibTimepicker', function() { return { restrict: 'EA', - require: ['timepicker', '?^ngModel'], - controller:'TimepickerController', + require: ['uibTimepicker', '?^ngModel'], + controller: 'UibTimepickerController', + controllerAs: 'timepicker', replace: true, scope: {}, - templateUrl: 'template/timepicker/timepicker.html', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/timepicker/timepicker.html'; + }, link: function(scope, element, attrs, ctrls) { var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - if ( ngModelCtrl ) { - timepickerCtrl.init( ngModelCtrl, element.find('input') ); + if (ngModelCtrl) { + timepickerCtrl.init(ngModelCtrl, element.find('input')); } } }; }); -angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) +/* Deprecated timepicker below */ + +angular.module('ui.bootstrap.timepicker') + +.value('$timepickerSuppressWarning', false) + +.controller('TimepickerController', ['$scope', '$element', '$attrs', '$controller', '$log', '$timepickerSuppressWarning', function($scope, $element, $attrs, $controller, $log, $timepickerSuppressWarning) { + if (!$timepickerSuppressWarning) { + $log.warn('TimepickerController is now deprecated. Use UibTimepickerController instead.'); + } + + angular.extend(this, $controller('UibTimepickerController', { + $scope: $scope, + $element: $element, + $attrs: $attrs + })); +}]) + +.directive('timepicker', ['$log', '$timepickerSuppressWarning', function($log, $timepickerSuppressWarning) { + return { + restrict: 'EA', + require: ['timepicker', '?^ngModel'], + controller: 'TimepickerController', + controllerAs: 'timepicker', + replace: true, + scope: {}, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/timepicker/timepicker.html'; + }, + link: function(scope, element, attrs, ctrls) { + if (!$timepickerSuppressWarning) { + $log.warn('timepicker is now deprecated. Use uib-timepicker instead.'); + } + var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (ngModelCtrl) { + timepickerCtrl.init(ngModelCtrl, element.find('input')); + } + } + }; +}]); + +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) /** * A helper service that can parse typeahead's syntax (string provided by users) * Extracted to a separate service for ease of unit testing */ - .factory('typeaheadParser', ['$parse', function ($parse) { - - // 00000111000000000000022200000000000000003333333333333330000000000044000 - var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; - - return { - parse:function (input) { - - var match = input.match(TYPEAHEAD_REGEXP); - if (!match) { - throw new Error( - 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + - ' but got "' + input + '".'); - } - - return { - itemName:match[3], - source:$parse(match[4]), - viewMapper:$parse(match[2] || match[1]), - modelMapper:$parse(match[1]) - }; - } - }; -}]) - - .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', - function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { - - var HOT_KEYS = [9, 13, 27, 38, 40]; - - return { - require:'ngModel', - link:function (originalScope, element, attrs, modelCtrl) { - - //SUPPORTED ATTRIBUTES (OPTIONS) - - //minimal no of characters that needs to be entered before typeahead kicks-in - var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; - - //minimal wait time after last character typed before typehead kicks-in - var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; - - //should it restrict model values to the ones selected from the popup only? - var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; - - //binding to a variable that indicates if matches are being retrieved asynchronously - var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; - - //a callback executed when a match is selected - var onSelectCallback = $parse(attrs.typeaheadOnSelect); - - var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; - - var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; - - var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false; - - //INTERNAL VARIABLES - - //model setter executed upon match selection - var $setModelValue = $parse(attrs.ngModel).assign; - - //expressions used by typeahead - var parserResult = typeaheadParser.parse(attrs.typeahead); - - var hasFocus; - - //create a child scope for the typeahead directive so we are not polluting original scope - //with typeahead-specific data (matches, query etc.) - var scope = originalScope.$new(); - originalScope.$on('$destroy', function(){ - scope.$destroy(); - }); - - // WAI-ARIA - var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); - element.attr({ - 'aria-autocomplete': 'list', - 'aria-expanded': false, - 'aria-owns': popupId - }); - - //pop-up element used to display matches - var popUpEl = angular.element('
    '); - popUpEl.attr({ - id: popupId, - matches: 'matches', - active: 'activeIdx', - select: 'select(activeIdx)', - query: 'query', - position: 'position' - }); - //custom item template - if (angular.isDefined(attrs.typeaheadTemplateUrl)) { - popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); - } - - var resetMatches = function() { - scope.matches = []; - scope.activeIdx = -1; - element.attr('aria-expanded', false); - }; - - var getMatchId = function(index) { - return popupId + '-option-' + index; - }; - - // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. - // This attribute is added or removed automatically when the `activeIdx` changes. - scope.$watch('activeIdx', function(index) { - if (index < 0) { - element.removeAttr('aria-activedescendant'); - } else { - element.attr('aria-activedescendant', getMatchId(index)); + .factory('uibTypeaheadParser', ['$parse', function($parse) { + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; + return { + parse: function(input) { + var match = input.match(TYPEAHEAD_REGEXP); + if (!match) { + throw new Error( + 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + + ' but got "' + input + '".'); } - }); - var getMatchesAsync = function(inputValue) { + return { + itemName: match[3], + source: $parse(match[4]), + viewMapper: $parse(match[2] || match[1]), + modelMapper: $parse(match[1]) + }; + } + }; + }]) - var locals = {$viewValue: inputValue}; - isLoadingSetter(originalScope, true); - $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$uibPosition', 'uibTypeaheadParser', + function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) { + var HOT_KEYS = [9, 13, 27, 38, 40]; + var eventDebounceTime = 200; + var modelCtrl, ngModelOptions; + //SUPPORTED ATTRIBUTES (OPTIONS) - //it might happen that several async queries were in progress if a user were typing fast - //but we are interested only in responses that correspond to the current view value - var onCurrentRequest = (inputValue === modelCtrl.$viewValue); - if (onCurrentRequest && hasFocus) { - if (matches.length > 0) { + //minimal no of characters that needs to be entered before typeahead kicks-in + var minLength = originalScope.$eval(attrs.typeaheadMinLength); + if (!minLength && minLength !== 0) { + minLength = 1; + } - scope.activeIdx = focusFirst ? 0 : -1; - scope.matches.length = 0; + //minimal wait time after last character typed before typeahead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; - //transform labels - for(var i=0; i
    '); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + 'move-in-progress': 'moveInProgress', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) { + popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl); + } + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; + + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + + var inputIsExactMatch = function(inputValue, index) { + if (scope.matches.length > index && inputValue) { + return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase(); + } + + return false; + }; + + var getMatchesAsync = function(inputValue) { + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + isNoResultsSetter(originalScope, false); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches && matches.length > 0) { + scope.activeIdx = focusFirst ? 0 : -1; + isNoResultsSetter(originalScope, false); + scope.matches.length = 0; + + //transform labels + for (var i = 0; i < matches.length; i++) { + locals[parserResult.itemName] = matches[i]; + scope.matches.push({ + id: getMatchId(i), + label: parserResult.viewMapper(scope, locals), + model: matches[i] + }); } + + scope.query = inputValue; + //position pop-up with matches - we need to re-calculate its position each time we are opening a window + //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page + //due to other elements being rendered + recalculatePosition(); + + element.attr('aria-expanded', true); + + //Select the single remaining option if user input matches + if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { + scope.select(0); + } + } else { + resetMatches(); + isNoResultsSetter(originalScope, true); } - if (onCurrentRequest) { - isLoadingSetter(originalScope, false); - } - }, function(){ - resetMatches(); + } + if (onCurrentRequest) { isLoadingSetter(originalScope, false); - }); - }; + } + }, function() { + resetMatches(); + isLoadingSetter(originalScope, false); + isNoResultsSetter(originalScope, true); + }); + }; + + // bind events only if appendToBody params exist - performance feature + if (appendToBody) { + angular.element($window).bind('resize', fireRecalculating); + $document.find('body').bind('scroll', fireRecalculating); + } + + // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later + var timeoutEventPromise; + + // Default progress type + scope.moveInProgress = false; + + function fireRecalculating() { + if (!scope.moveInProgress) { + scope.moveInProgress = true; + scope.$digest(); + } + + // Cancel previous timeout + if (timeoutEventPromise) { + $timeout.cancel(timeoutEventPromise); + } + + // Debounced executing recalculate after events fired + timeoutEventPromise = $timeout(function() { + // if popup is visible + if (scope.matches.length) { + recalculatePosition(); + } + + scope.moveInProgress = false; + }, eventDebounceTime); + } + + // recalculate actual position and set new values to scope + // after digest loop is popup in right position + function recalculatePosition() { + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top += element.prop('offsetHeight'); + } + + //we need to propagate user's query so we can higlight matches + scope.query = undefined; + + //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later + var timeoutPromise; + + var scheduleSearchWithTimeout = function(inputValue) { + timeoutPromise = $timeout(function() { + getMatchesAsync(inputValue); + }, waitTime); + }; + + var cancelPreviousTimeout = function() { + if (timeoutPromise) { + $timeout.cancel(timeoutPromise); + } + }; + + resetMatches(); + + scope.select = function(activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + selected = true; + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + modelCtrl.$setValidity('parse', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); resetMatches(); - //we need to propagate user's query so we can higlight matches - scope.query = undefined; + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) { + $timeout(function() { element[0].focus(); }, 0, false); + } + }; - //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later - var timeoutPromise; + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function(evt) { + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } - var scheduleSearchWithTimeout = function(inputValue) { - timeoutPromise = $timeout(function () { - getMatchesAsync(inputValue); - }, waitTime); - }; + // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results + if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) { + resetMatches(); + scope.$digest(); + return; + } - var cancelPreviousTimeout = function() { - if (timeoutPromise) { - $timeout.cancel(timeoutPromise); + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function() { + if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) { + selected = true; + scope.$apply(function() { + scope.select(scope.activeIdx); + }); + } + hasFocus = false; + selected = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function(evt) { + // Issue #3973 + // Firefox treats right click as a click on document + if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) { + resetMatches(); + if (!$rootScope.$$phase) { + scope.$digest(); } - }; + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function() { + $document.unbind('click', dismissClickHandler); + if (appendToBody || appendToElementId) { + $popup.remove(); + } + + if (appendToBody) { + angular.element($window).unbind('resize', fireRecalculating); + $document.find('body').unbind('scroll', fireRecalculating); + } + // Prevent jQuery cache memory leak + popUpEl.remove(); + }); + + var $popup = $compile(popUpEl)(scope); + + if (appendToBody) { + $document.find('body').append($popup); + } else if (appendToElementId !== false) { + angular.element($document[0].getElementById(appendToElementId)).append($popup); + } else { + element.after($popup); + } + + this.init = function(_modelCtrl, _ngModelOptions) { + modelCtrl = _modelCtrl; + ngModelOptions = _ngModelOptions; //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue - modelCtrl.$parsers.unshift(function (inputValue) { - + modelCtrl.$parsers.unshift(function(inputValue) { hasFocus = true; - if (inputValue && inputValue.length >= minSearch) { + if (minLength === 0 || inputValue && inputValue.length >= minLength) { if (waitTime > 0) { cancelPreviousTimeout(); scheduleSearchWithTimeout(inputValue); @@ -3715,7 +7472,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap if (!inputValue) { // Reset in case user had typed something previously. modelCtrl.$setValidity('editable', true); - return inputValue; + return null; } else { modelCtrl.$setValidity('editable', false); return undefined; @@ -3723,18 +7480,21 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap } }); - modelCtrl.$formatters.push(function (modelValue) { - + modelCtrl.$formatters.push(function(modelValue) { var candidateViewValue, emptyViewValue; var locals = {}; - if (inputFormatter) { + // The validity may be set to false via $parsers (see above) if + // the model is restricted to selected values. If the model + // is set manually it is considered to be valid. + if (!isEditable) { + modelCtrl.$setValidity('editable', true); + } + if (inputFormatter) { locals.$model = modelValue; return inputFormatter(originalScope, locals); - } else { - //it might happen that we don't have enough info to properly render input value //we need to check for this situation and simply return model value if we can't apply custom formatting locals[parserResult.itemName] = modelValue; @@ -3742,173 +7502,638 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap locals[parserResult.itemName] = undefined; emptyViewValue = parserResult.viewMapper(originalScope, locals); - return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue; } }); + }; + }]) - scope.select = function (activeIdx) { - //called from within the $digest() cycle - var locals = {}; - var model, item; - - locals[parserResult.itemName] = item = scope.matches[activeIdx].model; - model = parserResult.modelMapper(originalScope, locals); - $setModelValue(originalScope, model); - modelCtrl.$setValidity('editable', true); - - onSelectCallback(originalScope, { - $item: item, - $model: model, - $label: parserResult.viewMapper(originalScope, locals) - }); - - resetMatches(); - - //return focus to the input element if a match was selected via a mouse click event - // use timeout to avoid $rootScope:inprog error - $timeout(function() { element[0].focus(); }, 0, false); - }; - - //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) - element.bind('keydown', function (evt) { - - //typeahead is open and an "interesting" key was pressed - if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { - return; - } - - // if there's nothing selected (i.e. focusFirst) and enter is hit, don't do anything - if (scope.activeIdx == -1 && (evt.which === 13 || evt.which === 9)) { - return; - } - - evt.preventDefault(); - - if (evt.which === 40) { - scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; - scope.$digest(); - - } else if (evt.which === 38) { - scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; - scope.$digest(); - - } else if (evt.which === 13 || evt.which === 9) { - scope.$apply(function () { - scope.select(scope.activeIdx); - }); - - } else if (evt.which === 27) { - evt.stopPropagation(); - - resetMatches(); - scope.$digest(); - } - }); - - element.bind('blur', function (evt) { - hasFocus = false; - }); - - // Keep reference to click handler to unbind it. - var dismissClickHandler = function (evt) { - if (element[0] !== evt.target) { - resetMatches(); - scope.$digest(); - } - }; - - $document.bind('click', dismissClickHandler); - - originalScope.$on('$destroy', function(){ - $document.unbind('click', dismissClickHandler); - if (appendToBody) { - $popup.remove(); - } - }); - - var $popup = $compile(popUpEl)(scope); - if (appendToBody) { - $document.find('body').append($popup); - } else { - element.after($popup); - } - } - }; - -}]) - - .directive('typeaheadPopup', function () { + .directive('uibTypeahead', function() { return { - restrict:'EA', - scope:{ - matches:'=', - query:'=', - active:'=', - position:'=', - select:'&' - }, - replace:true, - templateUrl:'template/typeahead/typeahead-popup.html', - link:function (scope, element, attrs) { + controller: 'UibTypeaheadController', + require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'], + link: function(originalScope, element, attrs, ctrls) { + ctrls[2].init(ctrls[0], ctrls[1]); + } + }; + }) + .directive('uibTypeaheadPopup', function() { + return { + scope: { + matches: '=', + query: '=', + active: '=', + position: '&', + moveInProgress: '=', + select: '&' + }, + replace: true, + templateUrl: function(element, attrs) { + return attrs.popupTemplateUrl || 'template/typeahead/typeahead-popup.html'; + }, + link: function(scope, element, attrs) { scope.templateUrl = attrs.templateUrl; - scope.isOpen = function () { + scope.isOpen = function() { return scope.matches.length > 0; }; - scope.isActive = function (matchIdx) { + scope.isActive = function(matchIdx) { return scope.active == matchIdx; }; - scope.selectActive = function (matchIdx) { + scope.selectActive = function(matchIdx) { scope.active = matchIdx; }; - scope.selectMatch = function (activeIdx) { + scope.selectMatch = function(activeIdx) { scope.select({activeIdx:activeIdx}); }; } }; }) - .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { + .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) { return { - restrict:'EA', - scope:{ - index:'=', - match:'=', - query:'=' + scope: { + index: '=', + match: '=', + query: '=' }, - link:function (scope, element, attrs) { + link:function(scope, element, attrs) { var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; - $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ - element.replaceWith($compile(tplContent.trim())(scope)); + $templateRequest(tplUrl).then(function(tplContent) { + $compile(tplContent.trim())(scope, function(clonedElement) { + element.replaceWith(clonedElement); + }); }); } }; }]) - .filter('typeaheadHighlight', function() { + .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) { + var isSanitizePresent; + isSanitizePresent = $injector.has('$sanitize'); function escapeRegexp(queryToEscape) { + // Regex: capture the whole query string and replace it with the string that will be used to match + // the results, for example if the capture is "a" the result will be \a return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); } + function containsHtml(matchItem) { + return /<.*>/g.test(matchItem); + } + return function(matchItem, query) { - return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + if (!isSanitizePresent && containsHtml(matchItem)) { + $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger + } + matchItem = query? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag + if (!isSanitizePresent) { + matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive + } + return matchItem; }; - }); + }]); + +/* Deprecated typeahead below */ + +angular.module('ui.bootstrap.typeahead') + .value('$typeaheadSuppressWarning', false) + .service('typeaheadParser', ['$parse', 'uibTypeaheadParser', '$log', '$typeaheadSuppressWarning', function($parse, uibTypeaheadParser, $log, $typeaheadSuppressWarning) { + if (!$typeaheadSuppressWarning) { + $log.warn('typeaheadParser is now deprecated. Use uibTypeaheadParser instead.'); + } + + return uibTypeaheadParser; + }]) + + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$uibPosition', 'typeaheadParser', '$log', '$typeaheadSuppressWarning', + function($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser, $log, $typeaheadSuppressWarning) { + var HOT_KEYS = [9, 13, 27, 38, 40]; + var eventDebounceTime = 200; + return { + require: ['ngModel', '^?ngModelOptions'], + link: function(originalScope, element, attrs, ctrls) { + if (!$typeaheadSuppressWarning) { + $log.warn('typeahead is now deprecated. Use uib-typeahead instead.'); + } + var modelCtrl = ctrls[0]; + var ngModelOptions = ctrls[1]; + //SUPPORTED ATTRIBUTES (OPTIONS) + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minLength = originalScope.$eval(attrs.typeaheadMinLength); + if (!minLength && minLength !== 0) { + minLength = 1; + } + + //minimal wait time after last character typed before typeahead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); + + //should it select highlighted popup value when losing focus? + var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false; + + //binding to a variable that indicates if there were no results after the query is completed + var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop; + + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + + var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; + + var appendToElementId = attrs.typeaheadAppendToElementId || false; + + var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false; + + //If input matches an item of the list exactly, select it automatically + var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false; + + //INTERNAL VARIABLES + + //model setter executed upon match selection + var parsedModel = $parse(attrs.ngModel); + var invokeModelSetter = $parse(attrs.ngModel + '($$$p)'); + var $setModelValue = function(scope, newValue) { + if (angular.isFunction(parsedModel(originalScope)) && + ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) { + return invokeModelSetter(scope, {$$$p: newValue}); + } else { + return parsedModel.assign(scope, newValue); + } + }; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + var hasFocus; + + //Used to avoid bug in iOS webview where iOS keyboard does not fire + //mousedown & mouseup events + //Issue #3699 + var selected; + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + var offDestroy = originalScope.$on('$destroy', function() { + scope.$destroy(); + }); + scope.$on('$destroy', offDestroy); + + // WAI-ARIA + var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + element.attr({ + 'aria-autocomplete': 'list', + 'aria-expanded': false, + 'aria-owns': popupId + }); + + //pop-up element used to display matches + var popUpEl = angular.element('
    '); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + 'move-in-progress': 'moveInProgress', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) { + popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl); + } + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; + + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + + var inputIsExactMatch = function(inputValue, index) { + if (scope.matches.length > index && inputValue) { + return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase(); + } + + return false; + }; + + var getMatchesAsync = function(inputValue) { + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + isNoResultsSetter(originalScope, false); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches && matches.length > 0) { + scope.activeIdx = focusFirst ? 0 : -1; + isNoResultsSetter(originalScope, false); + scope.matches.length = 0; + + //transform labels + for (var i = 0; i < matches.length; i++) { + locals[parserResult.itemName] = matches[i]; + scope.matches.push({ + id: getMatchId(i), + label: parserResult.viewMapper(scope, locals), + model: matches[i] + }); + } + + scope.query = inputValue; + //position pop-up with matches - we need to re-calculate its position each time we are opening a window + //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page + //due to other elements being rendered + recalculatePosition(); + + element.attr('aria-expanded', true); + + //Select the single remaining option if user input matches + if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { + scope.select(0); + } + } else { + resetMatches(); + isNoResultsSetter(originalScope, true); + } + } + if (onCurrentRequest) { + isLoadingSetter(originalScope, false); + } + }, function() { + resetMatches(); + isLoadingSetter(originalScope, false); + isNoResultsSetter(originalScope, true); + }); + }; + + // bind events only if appendToBody params exist - performance feature + if (appendToBody) { + angular.element($window).bind('resize', fireRecalculating); + $document.find('body').bind('scroll', fireRecalculating); + } + + // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later + var timeoutEventPromise; + + // Default progress type + scope.moveInProgress = false; + + function fireRecalculating() { + if (!scope.moveInProgress) { + scope.moveInProgress = true; + scope.$digest(); + } + + // Cancel previous timeout + if (timeoutEventPromise) { + $timeout.cancel(timeoutEventPromise); + } + + // Debounced executing recalculate after events fired + timeoutEventPromise = $timeout(function() { + // if popup is visible + if (scope.matches.length) { + recalculatePosition(); + } + + scope.moveInProgress = false; + }, eventDebounceTime); + } + + // recalculate actual position and set new values to scope + // after digest loop is popup in right position + function recalculatePosition() { + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top += element.prop('offsetHeight'); + } + + resetMatches(); + + //we need to propagate user's query so we can higlight matches + scope.query = undefined; + + //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later + var timeoutPromise; + + var scheduleSearchWithTimeout = function(inputValue) { + timeoutPromise = $timeout(function() { + getMatchesAsync(inputValue); + }, waitTime); + }; + + var cancelPreviousTimeout = function() { + if (timeoutPromise) { + $timeout.cancel(timeoutPromise); + } + }; + + //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM + //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue + modelCtrl.$parsers.unshift(function(inputValue) { + hasFocus = true; + + if (minLength === 0 || inputValue && inputValue.length >= minLength) { + if (waitTime > 0) { + cancelPreviousTimeout(); + scheduleSearchWithTimeout(inputValue); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); + cancelPreviousTimeout(); + resetMatches(); + } + + if (isEditable) { + return inputValue; + } else { + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return null; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } + } + }); + + modelCtrl.$formatters.push(function(modelValue) { + var candidateViewValue, emptyViewValue; + var locals = {}; + + // The validity may be set to false via $parsers (see above) if + // the model is restricted to selected values. If the model + // is set manually it is considered to be valid. + if (!isEditable) { + modelCtrl.$setValidity('editable', true); + } + + if (inputFormatter) { + locals.$model = modelValue; + return inputFormatter(originalScope, locals); + } else { + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue; + } + }); + + scope.select = function(activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + selected = true; + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + modelCtrl.$setValidity('parse', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); + + resetMatches(); + + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) { + $timeout(function() { element[0].focus(); }, 0, false); + } + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function(evt) { + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results + if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) { + resetMatches(); + scope.$digest(); + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function() { + if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) { + selected = true; + scope.$apply(function() { + scope.select(scope.activeIdx); + }); + } + hasFocus = false; + selected = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function(evt) { + // Issue #3973 + // Firefox treats right click as a click on document + if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) { + resetMatches(); + if (!$rootScope.$$phase) { + scope.$digest(); + } + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function() { + $document.unbind('click', dismissClickHandler); + if (appendToBody || appendToElementId) { + $popup.remove(); + } + + if (appendToBody) { + angular.element($window).unbind('resize', fireRecalculating); + $document.find('body').unbind('scroll', fireRecalculating); + } + // Prevent jQuery cache memory leak + popUpEl.remove(); + }); + + var $popup = $compile(popUpEl)(scope); + + if (appendToBody) { + $document.find('body').append($popup); + } else if (appendToElementId !== false) { + angular.element($document[0].getElementById(appendToElementId)).append($popup); + } else { + element.after($popup); + } + } + }; + }]) + + .directive('typeaheadPopup', ['$typeaheadSuppressWarning', '$log', function($typeaheadSuppressWarning, $log) { + return { + scope: { + matches: '=', + query: '=', + active: '=', + position: '&', + moveInProgress: '=', + select: '&' + }, + replace: true, + templateUrl: function(element, attrs) { + return attrs.popupTemplateUrl || 'template/typeahead/typeahead-popup.html'; + }, + link: function(scope, element, attrs) { + + if (!$typeaheadSuppressWarning) { + $log.warn('typeahead-popup is now deprecated. Use uib-typeahead-popup instead.'); + } + scope.templateUrl = attrs.templateUrl; + + scope.isOpen = function() { + return scope.matches.length > 0; + }; + + scope.isActive = function(matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function(matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function(activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }]) + + .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', '$typeaheadSuppressWarning', '$log', function($templateRequest, $compile, $parse, $typeaheadSuppressWarning, $log) { + return { + restrict: 'EA', + scope: { + index: '=', + match: '=', + query: '=' + }, + link:function(scope, element, attrs) { + if (!$typeaheadSuppressWarning) { + $log.warn('typeahead-match is now deprecated. Use uib-typeahead-match instead.'); + } + + var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; + $templateRequest(tplUrl).then(function(tplContent) { + $compile(tplContent.trim())(scope, function(clonedElement) { + element.replaceWith(clonedElement); + }); + }); + } + }; + }]) + + .filter('typeaheadHighlight', ['$sce', '$injector', '$log', '$typeaheadSuppressWarning', function($sce, $injector, $log, $typeaheadSuppressWarning) { + var isSanitizePresent; + isSanitizePresent = $injector.has('$sanitize'); + + function escapeRegexp(queryToEscape) { + // Regex: capture the whole query string and replace it with the string that will be used to match + // the results, for example if the capture is "a" the result will be \a + return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + } + + function containsHtml(matchItem) { + return /<.*>/g.test(matchItem); + } + + return function(matchItem, query) { + if (!$typeaheadSuppressWarning) { + $log.warn('typeaheadHighlight is now deprecated. Use uibTypeaheadHighlight instead.'); + } + + if (!isSanitizePresent && containsHtml(matchItem)) { + $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger + } + + matchItem = query? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag + if (!isSanitizePresent) { + matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive + } + + return matchItem; + }; + }]); angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/accordion/accordion-group.html", - "
    \n" + - "
    \n" + + "
    \n" + + "
    \n" + "

    \n" + - " {{heading}}\n" + + " {{heading}}\n" + "

    \n" + "
    \n" + - "
    \n" + + "
    \n" + "
    \n" + "
    \n" + "
    \n" + @@ -3922,8 +8147,8 @@ angular.module("template/accordion/accordion.html", []).run(["$templateCache", f angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/alert/alert.html", - "
    \n" + - " \n" + @@ -3935,24 +8160,27 @@ angular.module("template/alert/alert.html", []).run(["$templateCache", function( angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/carousel/carousel.html", "
    \n" + - "
      1\">\n" + - "
    1. \n" + - "
    \n" + - "
    \n" + - " 1\">\n" + - " 1\">\n" + - "
    \n" + - ""); + "
    \n" + + " 1\">\n" + + " \n" + + " previous\n" + + " \n" + + " 1\">\n" + + " \n" + + " next\n" + + " \n" + + "
      1\">\n" + + "
    1. \n" + + " slide {{ $index + 1 }} of {{ slides.length }}, currently active\n" + + "
    2. \n" + + "
    \n" + + "
    "); }]); angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/carousel/slide.html", "
    \n" + ""); }]); @@ -3960,31 +8188,31 @@ angular.module("template/carousel/slide.html", []).run(["$templateCache", functi angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/datepicker/datepicker.html", "
    \n" + - " \n" + - " \n" + - " \n" + + " \n" + + " \n" + + " \n" + "
    "); }]); angular.module("template/datepicker/day.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/datepicker/day.html", - "\n" + + "
    \n" + " \n" + " \n" + " \n" + - " \n" + + " \n" + " \n" + " \n" + " \n" + - " \n" + - " \n" + + " \n" + + " \n" + " \n" + " \n" + " \n" + " \n" + - " \n" + - " \n" + + " \n" + " \n" + " \n" + @@ -3994,18 +8222,18 @@ angular.module("template/datepicker/day.html", []).run(["$templateCache", functi angular.module("template/datepicker/month.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/datepicker/month.html", - "
    {{label.abbr}}{{::label.abbr}}
    {{ weekNumbers[$index] }}\n" + - " \n" + + " {{ weekNumbers[$index] }}\n" + + " \n" + "
    \n" + + "
    \n" + " \n" + " \n" + " \n" + - " \n" + + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + - " \n" + " \n" + " \n" + @@ -4015,11 +8243,11 @@ angular.module("template/datepicker/month.html", []).run(["$templateCache", func angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/datepicker/popup.html", - "
      \n" + + "
        \n" + "
      • \n" + "
      • \n" + " \n" + - " \n" + + " \n" + " \n" + " \n" + " \n" + @@ -4030,18 +8258,18 @@ angular.module("template/datepicker/popup.html", []).run(["$templateCache", func angular.module("template/datepicker/year.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/datepicker/year.html", - "
    \n" + - " \n" + + " \n" + + " \n" + "
    \n" + + "
    \n" + " \n" + " \n" + " \n" + - " \n" + + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + - " \n" + " \n" + " \n" + @@ -4051,8 +8279,8 @@ angular.module("template/datepicker/year.html", []).run(["$templateCache", funct angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/modal/backdrop.html", - "
    \n" + ""); @@ -4060,55 +8288,115 @@ angular.module("template/modal/backdrop.html", []).run(["$templateCache", functi angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/modal/window.html", - "
    \n" + - "
    \n" + - "
    "); + "
    \n" + + "
    \n" + + "
    \n" + + ""); }]); angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/pagination/pager.html", ""); + "
  • {{::getText('previous')}}
  • \n" + + "
  • {{::getText('next')}}
  • \n" + + "\n" + + ""); }]); angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/pagination/pagination.html", ""); + "
  • {{::getText('first')}}
  • \n" + + "
  • {{::getText('previous')}}
  • \n" + + "
  • {{page.text}}
  • \n" + + "
  • {{::getText('next')}}
  • \n" + + "
  • {{::getText('last')}}
  • \n" + + "\n" + + ""); }]); -angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", - "
    \n" + +angular.module("template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-html-popup.html", + "\n" + "
    \n" + - "
    \n" + + "
    \n" + "
    \n" + ""); }]); angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/tooltip/tooltip-popup.html", - "
    \n" + + "\n" + "
    \n" + "
    \n" + "
    \n" + ""); }]); -angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/popover/popover.html", - "
    \n" + +angular.module("template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-template-popup.html", + "\n" + + "
    \n" + + "
    \n" + + "
    \n" + + ""); +}]); + +angular.module("template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/popover/popover-html.html", + "
    \n" + "
    \n" + "\n" + "
    \n" + - "

    \n" + + "

    \n" + + "
    \n" + + "
    \n" + + "
    \n" + + ""); +}]); + +angular.module("template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/popover/popover-template.html", + "
    \n" + + "
    \n" + + "\n" + + "
    \n" + + "

    \n" + + "
    \n" + + "
    \n" + + "
    \n" + + ""); +}]); + +angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/popover/popover.html", + "
    \n" + + "
    \n" + + "\n" + + "
    \n" + + "

    \n" + "
    \n" + "
    \n" + "
    \n" + @@ -4117,34 +8405,36 @@ angular.module("template/popover/popover.html", []).run(["$templateCache", funct angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/progressbar/bar.html", - "
    "); + "
    \n" + + ""); }]); angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/progressbar/progress.html", - "
    "); + "
    "); }]); angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/progressbar/progressbar.html", "
    \n" + - "
    \n" + - "
    "); + "
    \n" + + "\n" + + ""); }]); angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/rating/rating.html", "\n" + - " \n" + - " ({{ $index < value ? '*' : ' ' }})\n" + - " \n" + - ""); + " ({{ $index < value ? '*' : ' ' }})\n" + + " \n" + + "\n" + + ""); }]); angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/tabs/tab.html", "
  • \n" + - " {{heading}}\n" + + " {{heading}}\n" + "
  • \n" + ""); }]); @@ -4157,7 +8447,7 @@ angular.module("template/tabs/tabset.html", []).run(["$templateCache", function( "
    \n" + + " uib-tab-content-transclude=\"tab\">\n" + "
    \n" + " \n" + "\n" + @@ -4167,45 +8457,47 @@ angular.module("template/tabs/tabset.html", []).run(["$templateCache", function( angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/timepicker/timepicker.html", "
    \n" + - " \n" + + " \n" + + " \n" + "
    \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + "
     
    \n" + - " \n" + - " :\n" + - " \n" + - "
     
     
    \n" + + " \n" + + " :\n" + + " \n" + + "
     
    \n" + ""); }]); angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/typeahead/typeahead-match.html", - ""); + "\n" + + ""); }]); angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/typeahead/typeahead-popup.html", - "
      \n" + - "
    • \n" + - "
      \n" + + "
        \n" + + "
      • \n" + + "
        \n" + "
      • \n" + "
      \n" + ""); }]); +!angular.$$csp() && angular.element(document).find('head').prepend(''); \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap/ui-bootstrap-tpls.min.js b/vendor/assets/components/angular-bootstrap/ui-bootstrap-tpls.min.js index 41fc2cbef..f729eda6e 100644 --- a/vendor/assets/components/angular-bootstrap/ui-bootstrap-tpls.min.js +++ b/vendor/assets/components/angular-bootstrap/ui-bootstrap-tpls.min.js @@ -2,9 +2,11 @@ * angular-ui-bootstrap * http://angular-ui.github.io/bootstrap/ - * Version: 0.12.1 - 2015-02-20 + * Version: 0.14.3 - 2015-10-23 * License: MIT */ -angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(a){return{link:function(b,c,d){function e(b){function d(){j===e&&(j=void 0)}var e=a(c,b);return j&&j.cancel(),j=e,e.then(d,d),e}function f(){k?(k=!1,g()):(c.removeClass("collapse").addClass("collapsing"),e({height:c[0].scrollHeight+"px"}).then(g))}function g(){c.removeClass("collapsing"),c.addClass("collapse in"),c.css({height:"auto"})}function h(){if(k)k=!1,i(),c.css({height:0});else{c.css({height:c[0].scrollHeight+"px"});{c[0].offsetWidth}c.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(i)}}function i(){c.removeClass("collapsing"),c.addClass("collapse")}var j,k=!0;b.$watch(d.collapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.$watch("isOpen",function(b){b&&d.closeOthers(a)}),a.toggleOpen=function(){a.isDisabled||(a.isOpen=!a.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b,this.close=a.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}).directive("dismissOnTimeout",["$timeout",function(a){return{require:"alert",link:function(b,c,d,e){a(function(){e.close()},parseInt(d.dismissOnTimeout,10))}}}]),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$interval","$transition",function(a,b,c,d){function e(){f();var b=+a.interval;!isNaN(b)&&b>0&&(h=c(g,b))}function f(){h&&(c.cancel(h),h=null)}function g(){var b=+a.interval;i&&!isNaN(b)&&b>0?a.next():a.pause()}var h,i,j=this,k=j.slides=a.slides=[],l=-1;j.currentSlide=null;var m=!1;j.select=a.select=function(c,f){function g(){if(!m){if(j.currentSlide&&angular.isString(f)&&!a.noTransition&&c.$element){c.$element.addClass(f);{c.$element[0].offsetWidth}angular.forEach(k,function(a){angular.extend(a,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(c,{direction:f,active:!0,entering:!0}),angular.extend(j.currentSlide||{},{direction:f,leaving:!0}),a.$currentTransition=d(c.$element,{}),function(b,c){a.$currentTransition.then(function(){h(b,c)},function(){h(b,c)})}(c,j.currentSlide)}else h(c,j.currentSlide);j.currentSlide=c,l=i,e()}}function h(b,c){angular.extend(b,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(c||{},{direction:"",active:!1,leaving:!1,entering:!1}),a.$currentTransition=null}var i=k.indexOf(c);void 0===f&&(f=i>l?"next":"prev"),c&&c!==j.currentSlide&&(a.$currentTransition?(a.$currentTransition.cancel(),b(g)):g())},a.$on("$destroy",function(){m=!0}),j.indexOfSlide=function(a){return k.indexOf(a)},a.next=function(){var b=(l+1)%k.length;return a.$currentTransition?void 0:j.select(k[b],"next")},a.prev=function(){var b=0>l-1?k.length-1:l-1;return a.$currentTransition?void 0:j.select(k[b],"prev")},a.isActive=function(a){return j.currentSlide===a},a.$watch("interval",e),a.$on("$destroy",f),a.play=function(){i||(i=!0,e())},a.pause=function(){a.noPause||(i=!1,f())},j.addSlide=function(b,c){b.$element=c,k.push(b),1===k.length||b.active?(j.select(k[k.length-1]),1==k.length&&a.play()):b.active=!1},j.removeSlide=function(a){var b=k.indexOf(a);k.splice(b,1),k.length>0&&a.active?j.select(b>=k.length?k[b-1]:k[b]):l>b&&l--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$locale","orderByFilter",function(a,b){function c(a){var c=[],d=a.split("");return angular.forEach(e,function(b,e){var f=a.indexOf(e);if(f>-1){a=a.split(""),d[f]="("+b.regex+")",a[f]="$";for(var g=f+1,h=f+e.length;h>g;g++)d[g]="",a[g]="$";a=a.join(""),c.push({index:f,apply:b.apply})}}),{regex:new RegExp("^"+d.join("")+"$"),map:b(c,"index")}}function d(a,b,c){return 1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}this.parsers={};var e={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:a.DATETIME_FORMATS.MONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.MONTH.indexOf(b)}},MMM:{regex:a.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.SHORTMONTH.indexOf(b)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:a.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:a.DATETIME_FORMATS.SHORTDAY.join("|")}};this.parse=function(b,e){if(!angular.isString(b)||!e)return b;e=a.DATETIME_FORMATS[e]||e,this.parsers[e]||(this.parsers[e]=c(e));var f=this.parsers[e],g=f.regex,h=f.map,i=b.match(g);if(i&&i.length){for(var j,k={year:1900,month:0,date:1,hours:0},l=1,m=i.length;m>l;l++){var n=h[l-1];n.apply&&n.apply.call(k,i[l])}return d(k.year,k.month,k.date)&&(j=new Date(k.year,k.month,k.date,k.hours)),j}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$timeout","$log","dateFilter","datepickerConfig",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","minMode","maxMode","showWeeks","startingDay","yearRange"],function(c,e){i[c]=angular.isDefined(b[c])?8>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):h[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=h[d]?new Date(h[d]):null}),a.datepickerMode=a.datepickerMode||h.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),this.activeDate=angular.isDefined(b.initDate)?a.$parent.$eval(b.initDate):new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$modelValue){var a=new Date(j.$modelValue),b=!isNaN(a);b?this.activeDate=a:f.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'),j.$setValidity("date",b)}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$modelValue?new Date(j.$modelValue):null;j.$setValidity("date-disabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$modelValue?new Date(j.$modelValue):null;return{date:a,label:g(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$modelValue?new Date(j.$modelValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){e(function(){i.element[0].focus()},0,!1)};a.$on("datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate),k()}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):(a.toggleMode("up"===c?1:-1),k())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{datepickerMode:"=?",dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}).directive("daypicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(b,c,d,e){function f(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?i[b]:29}function g(a,b){var c=new Array(b),d=new Date(a),e=0;for(d.setHours(12);b>e;)c[e++]=new Date(d),d.setDate(d.getDate()+1);return c}function h(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}b.showWeeks=e.showWeeks,e.step={months:1},e.element=c;var i=[31,28,31,30,31,30,31,31,30,31,30,31];e._refreshView=function(){var c=e.activeDate.getFullYear(),d=e.activeDate.getMonth(),f=new Date(c,d,1),i=e.startingDay-f.getDay(),j=i>0?7-i:-i,k=new Date(f);j>0&&k.setDate(-j+1);for(var l=g(k,42),m=0;42>m;m++)l[m]=angular.extend(e.createDateObject(l[m],e.formatDay),{secondary:l[m].getMonth()!==d,uid:b.uniqueId+"-"+m});b.labels=new Array(7);for(var n=0;7>n;n++)b.labels[n]={abbr:a(l[n].date,e.formatDayHeader),full:a(l[n].date,"EEEE")};if(b.title=a(e.activeDate,e.formatDayTitle),b.rows=e.split(l,7),b.showWeeks){b.weekNumbers=[];for(var o=h(b.rows[0][0].date),p=b.rows.length;b.weekNumbers.push(o++)f;f++)c[f]=angular.extend(e.createDateObject(new Date(d,f,1),e.formatMonth),{uid:b.uniqueId+"-"+f});b.title=a(e.activeDate,e.formatMonthTitle),b.rows=e.split(c,3)},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},e.handleKeyDown=function(a){var b=e.activeDate.getMonth();if("left"===a)b-=1;else if("up"===a)b-=3;else if("right"===a)b+=1;else if("down"===a)b+=3;else if("pageup"===a||"pagedown"===a){var c=e.activeDate.getFullYear()+("pageup"===a?-1:1);e.activeDate.setFullYear(c)}else"home"===a?b=0:"end"===a&&(b=11);e.activeDate.setMonth(b)},e.refreshView()}}}]).directive("yearpicker",["dateFilter",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(a,b,c,d){function e(a){return parseInt((a-1)/f,10)*f+1}var f=d.yearRange;d.step={years:f},d.element=b,d._refreshView=function(){for(var b=new Array(f),c=0,g=e(d.activeDate.getFullYear());f>c;c++)b[c]=angular.extend(d.createDateObject(new Date(g+c,0,1),d.formatYear),{uid:a.uniqueId+"-"+c});a.title=[b[0].label,b[f-1].label].join(" - "),a.rows=d.split(b,5)},d.compare=function(a,b){return a.getFullYear()-b.getFullYear()},d.handleKeyDown=function(a){var b=d.activeDate.getFullYear();"left"===a?b-=1:"up"===a?b-=5:"right"===a?b+=1:"down"===a?b+=5:"pageup"===a||"pagedown"===a?b+=("pageup"===a?-1:1)*d.step.years:"home"===a?b=e(d.activeDate.getFullYear()):"end"===a&&(b=e(d.activeDate.getFullYear())+f-1),d.activeDate.setFullYear(b)},d.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","dateParser","datepickerPopupConfig",function(a,b,c,d,e,f,g){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&"},link:function(h,i,j,k){function l(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function m(a){if(a){if(angular.isDate(a)&&!isNaN(a))return k.$setValidity("date",!0),a;if(angular.isString(a)){var b=f.parse(a,n)||new Date(a);return isNaN(b)?void k.$setValidity("date",!1):(k.$setValidity("date",!0),b)}return void k.$setValidity("date",!1)}return k.$setValidity("date",!0),null}var n,o=angular.isDefined(j.closeOnDateSelection)?h.$parent.$eval(j.closeOnDateSelection):g.closeOnDateSelection,p=angular.isDefined(j.datepickerAppendToBody)?h.$parent.$eval(j.datepickerAppendToBody):g.appendToBody;h.showButtonBar=angular.isDefined(j.showButtonBar)?h.$parent.$eval(j.showButtonBar):g.showButtonBar,h.getText=function(a){return h[a+"Text"]||g[a+"Text"]},j.$observe("datepickerPopup",function(a){n=a||g.datepickerPopup,k.$render()});var q=angular.element("
      ");q.attr({"ng-model":"date","ng-change":"dateSelection()"});var r=angular.element(q.children()[0]);j.datepickerOptions&&angular.forEach(h.$parent.$eval(j.datepickerOptions),function(a,b){r.attr(l(b),a)}),h.watchData={},angular.forEach(["minDate","maxDate","datepickerMode"],function(a){if(j[a]){var c=b(j[a]);if(h.$parent.$watch(c,function(b){h.watchData[a]=b}),r.attr(l(a),"watchData."+a),"datepickerMode"===a){var d=c.assign;h.$watch("watchData."+a,function(a,b){a!==b&&d(h.$parent,a)})}}}),j.dateDisabled&&r.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),k.$parsers.unshift(m),h.dateSelection=function(a){angular.isDefined(a)&&(h.date=a),k.$setViewValue(h.date),k.$render(),o&&(h.isOpen=!1,i[0].focus())},i.bind("input change keyup",function(){h.$apply(function(){h.date=k.$modelValue})}),k.$render=function(){var a=k.$viewValue?e(k.$viewValue,n):"";i.val(a),h.date=m(k.$modelValue)};var s=function(a){h.isOpen&&a.target!==i[0]&&h.$apply(function(){h.isOpen=!1})},t=function(a){h.keydown(a)};i.bind("keydown",t),h.keydown=function(a){27===a.which?(a.preventDefault(),a.stopPropagation(),h.close()):40!==a.which||h.isOpen||(h.isOpen=!0)},h.$watch("isOpen",function(a){a?(h.$broadcast("datepicker.focus"),h.position=p?d.offset(i):d.position(i),h.position.top=h.position.top+i.prop("offsetHeight"),c.bind("click",s)):c.unbind("click",s)}),h.select=function(a){if("today"===a){var b=new Date;angular.isDate(k.$modelValue)?(a=new Date(k.$modelValue),a.setFullYear(b.getFullYear(),b.getMonth(),b.getDate())):a=new Date(b.setHours(0,0,0,0))}h.dateSelection(a)},h.close=function(){h.isOpen=!1,i[0].focus()};var u=a(q)(h);q.remove(),p?c.find("body").append(u):i.after(u),h.$on("$destroy",function(){u.remove(),i.unbind("keydown",t),c.unbind("click",s)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(a,b){b.bind("click",function(a){a.preventDefault(),a.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function(a){var b=null;this.open=function(e){b||(a.bind("click",c),a.bind("keydown",d)),b&&b!==e&&(b.isOpen=!1),b=e},this.close=function(e){b===e&&(b=null,a.unbind("click",c),a.unbind("keydown",d))};var c=function(a){if(b){var c=b.getToggleElement();a&&c&&c[0].contains(a.target)||b.$apply(function(){b.isOpen=!1})}},d=function(a){27===a.which&&(b.focusToggleElement(),c())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function(a,b,c,d,e,f){var g,h=this,i=a.$new(),j=d.openClass,k=angular.noop,l=b.onToggle?c(b.onToggle):angular.noop;this.init=function(d){h.$element=d,b.isOpen&&(g=c(b.isOpen),k=g.assign,a.$watch(g,function(a){i.isOpen=!!a}))},this.toggle=function(a){return i.isOpen=arguments.length?!!a:!i.isOpen},this.isOpen=function(){return i.isOpen},i.getToggleElement=function(){return h.toggleElement},i.focusToggleElement=function(){h.toggleElement&&h.toggleElement[0].focus()},i.$watch("isOpen",function(b,c){f[b?"addClass":"removeClass"](h.$element,j),b?(i.focusToggleElement(),e.open(i)):e.close(i),k(a,b),angular.isDefined(b)&&b!==c&&l(a,{open:!!b})}),a.$on("$locationChangeSuccess",function(){i.isOpen=!1}),a.$on("$destroy",function(){i.$destroy()})}]).directive("dropdown",function(){return{controller:"DropdownController",link:function(a,b,c,d){d.init(b)}}}).directive("dropdownToggle",function(){return{require:"?^dropdown",link:function(a,b,c,d){if(d){d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0),i()})}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&(a.preventDefault(),e.$apply(function(){o.dismiss(b.key,"escape key press")})))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();if(h>=0&&!k){l=e.$new(!0),l.index=h;var i=angular.element("
      ");i.attr("backdrop-class",b.backdropClass),k=d(i)(l),f.append(k)}var j=angular.element("
      ");j.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,size:b.size,index:n.length()-1,animate:"animate"}).html(b.content);var o=d(j)(b.scope);n.top().value.modalDomEl=o,f.append(o),f.addClass(m)},o.close=function(a,b){var c=n.get(a);c&&(c.value.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a);c&&(c.value.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,backdropClass:b.backdropClass,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(f,g){e=f,this.config=g,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=g.itemsPerPage},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b){a.page!==b&&b>0&&b<=a.totalPages&&(e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages},a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render() -});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@"},require:["pager","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$document","$position","$interpolate",function(e,f,g,h,i,j){return function(e,k,l){function m(a){var b=a||n.trigger||l,d=c[b]||b;return{show:b,hide:d}}var n=angular.extend({},b,d),o=a(e),p=j.startSymbol(),q=j.endSymbol(),r="
      ';return{restrict:"EA",compile:function(){var a=f(r);return function(b,c,d){function f(){D.isOpen?l():j()}function j(){(!C||b.$eval(d[k+"Enable"]))&&(s(),D.popupDelay?z||(z=g(o,D.popupDelay,!1),z.then(function(a){a()})):o()())}function l(){b.$apply(function(){p()})}function o(){return z=null,y&&(g.cancel(y),y=null),D.content?(q(),w.css({top:0,left:0,display:"block"}),D.$digest(),E(),D.isOpen=!0,D.$digest(),E):angular.noop}function p(){D.isOpen=!1,g.cancel(z),z=null,D.animation?y||(y=g(r,500)):r()}function q(){w&&r(),x=D.$new(),w=a(x,function(a){A?h.find("body").append(a):c.after(a)})}function r(){y=null,w&&(w.remove(),w=null),x&&(x.$destroy(),x=null)}function s(){t(),u()}function t(){var a=d[k+"Placement"];D.placement=angular.isDefined(a)?a:n.placement}function u(){var a=d[k+"PopupDelay"],b=parseInt(a,10);D.popupDelay=isNaN(b)?n.popupDelay:b}function v(){var a=d[k+"Trigger"];F(),B=m(a),B.show===B.hide?c.bind(B.show,f):(c.bind(B.show,j),c.bind(B.hide,l))}var w,x,y,z,A=angular.isDefined(n.appendToBody)?n.appendToBody:!1,B=m(void 0),C=angular.isDefined(d[k+"Enable"]),D=b.$new(!0),E=function(){var a=i.positionElements(c,w,D.placement,A);a.top+="px",a.left+="px",w.css(a)};D.isOpen=!1,d.$observe(e,function(a){D.content=a,!a&&D.isOpen&&p()}),d.$observe(k+"Title",function(a){D.title=a});var F=function(){c.unbind(B.show,j),c.unbind(B.hide,l)};v();var G=b.$eval(d[k+"Animation"]);D.animation=angular.isDefined(G)?!!G:n.animation;var H=b.$eval(d[k+"AppendToBody"]);A=angular.isDefined(H)?H:A,A&&b.$on("$locationChangeSuccess",function(){D.isOpen&&p()}),b.$on("$destroy",function(){g.cancel(y),g.cancel(z),F(),r(),D=null})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,this.addBar=function(b,c){e||c.css({transition:"none"}),this.bars.push(b),b.$watch("value",function(c){b.percent=+(100*c/a.max).toFixed(2)}),b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(f)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff},a[b]);return a},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect())}),a.active=!0,a.onSelect()},b.addTab=function(a){c.push(a),1===c.length?a.active=!0:a.active&&b.select(a)},b.removeTab=function(a){var e=c.indexOf(a);if(a.active&&c.length>1&&!d){var f=e==c.length-1?e-1:e+1;b.select(c[f])}c.splice(e,1)};var d;a.$on("$destroy",function(){d=!0})}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){b.$watch("active",function(a){a&&f.select(b)}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function(a,b,c,d,e,f){function g(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===p[1]&&(b+=12)),b):void 0}function h(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function i(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a}function j(a){k(),o.$setViewValue(new Date(n)),l(a)}function k(){o.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function l(b){var c=n.getHours(),d=n.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:i(c),a.minutes="m"===b?d:i(d),a.meridian=n.getHours()<12?p[0]:p[1]}function m(a){var b=new Date(n.getTime()+6e4*a);n.setHours(b.getHours(),b.getMinutes()),j()}var n=new Date,o={$setViewValue:angular.noop},p=angular.isDefined(b.meridians)?a.$parent.$eval(b.meridians):f.meridians||e.DATETIME_FORMATS.AMPMS;this.init=function(c,d){o=c,o.$render=this.render;var e=d.eq(0),g=d.eq(1),h=angular.isDefined(b.mousewheel)?a.$parent.$eval(b.mousewheel):f.mousewheel;h&&this.setupMousewheelEvents(e,g),a.readonlyInput=angular.isDefined(b.readonlyInput)?a.$parent.$eval(b.readonlyInput):f.readonlyInput,this.setupInputEvents(e,g)};var q=f.hourStep;b.hourStep&&a.$parent.$watch(c(b.hourStep),function(a){q=parseInt(a,10)});var r=f.minuteStep;b.minuteStep&&a.$parent.$watch(c(b.minuteStep),function(a){r=parseInt(a,10)}),a.showMeridian=f.showMeridian,b.showMeridian&&a.$parent.$watch(c(b.showMeridian),function(b){if(a.showMeridian=!!b,o.$error.time){var c=g(),d=h();angular.isDefined(c)&&angular.isDefined(d)&&(n.setHours(c),j())}else l()}),this.setupMousewheelEvents=function(b,c){var d=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){o.$setViewValue(null),o.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=g();angular.isDefined(a)?(n.setHours(a),j("h")):d(!0)},b.bind("blur",function(){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=i(a.hours)})}),a.updateMinutes=function(){var a=h();angular.isDefined(a)?(n.setMinutes(a),j("m")):d(void 0,!0)},c.bind("blur",function(){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=i(a.minutes)})})},this.render=function(){var a=o.$modelValue?new Date(o.$modelValue):null;isNaN(a)?(o.$setValidity("time",!1),d.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(a&&(n=a),k(),l())},a.incrementHours=function(){m(60*q)},a.decrementHours=function(){m(60*-q)},a.incrementMinutes=function(){m(r)},a.decrementMinutes=function(){m(-r)},a.toggleMeridian=function(){m(720*(n.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",replace:!0,scope:{},templateUrl:"template/timepicker/timepicker.html",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?i.$eval(k.typeaheadAppendToBody):!1,u=i.$eval(k.typeaheadFocusFirst)!==!1,v=b(k.ngModel).assign,w=g.parse(k.typeahead),x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y="typeahead-"+x.$id+"-"+Math.floor(1e4*Math.random());j.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":y});var z=angular.element("
      ");z.attr({id:y,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&z.attr("template-url",k.typeaheadTemplateUrl);var A=function(){x.matches=[],x.activeIdx=-1,j.attr("aria-expanded",!1)},B=function(a){return y+"-option-"+a};x.$watch("activeIdx",function(a){0>a?j.removeAttr("aria-activedescendant"):j.attr("aria-activedescendant",B(a))});var C=function(a){var b={$viewValue:a};q(i,!0),c.when(w.source(i,b)).then(function(c){var d=a===l.$viewValue;if(d&&m)if(c.length>0){x.activeIdx=u?0:-1,x.matches.length=0;for(var e=0;e=n?o>0?(F(),E(a)):C(a):(q(i,!1),F(),A()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[w.itemName]=a,b=w.viewMapper(i,d),d[w.itemName]=void 0,c=w.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,e={};e[w.itemName]=c=x.matches[a].model,b=w.modelMapper(i,e),v(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:w.viewMapper(i,e)}),A(),d(function(){j[0].focus()},0,!1)},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(-1!=x.activeIdx||13!==a.which&&9!==a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx>0?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),A(),x.$digest()))}),j.bind("blur",function(){m=!1});var G=function(a){j[0]!==a.target&&(A(),x.$digest())};e.bind("click",G),i.$on("$destroy",function(){e.unbind("click",G),t&&H.remove()});var H=a(z)(x);t?e.find("body").append(H):j.after(H)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?(""+b).replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
      \n
      \n

      \n {{heading}}\n

      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
      ')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html",'\n')}]),angular.module("template/carousel/carousel.html",[]).run(["$templateCache",function(a){a.put("template/carousel/carousel.html",'\n')}]),angular.module("template/carousel/slide.html",[]).run(["$templateCache",function(a){a.put("template/carousel/slide.html","
      \n")}]),angular.module("template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/datepicker.html",'
      \n \n \n \n
      ')}]),angular.module("template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
      {{label.abbr}}
      {{ weekNumbers[$index] }}\n \n
      \n')}]),angular.module("template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
      \n \n
      \n')}]),angular.module("template/datepicker/popup.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/popup.html",'\n')}]),angular.module("template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
      \n \n
      \n')}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'
      \n
      \n
      \n
      \n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'
      \n
      \n
      \n
      \n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
      \n
      \n\n
      \n

      \n
      \n
      \n
      \n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'
      ')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
      ')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'
      \n
      \n
      ')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n ({{ $index < value ? \'*\' : \' \' }})\n \n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'
    • \n {{heading}}\n
    • \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'
      \n \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("template/timepicker/timepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
       
      \n \n :\n \n
       
      \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'') -}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html",'\n')}]); \ No newline at end of file +angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-popup.html","template/tooltip/tooltip-popup.html","template/tooltip/tooltip-template-popup.html","template/popover/popover-html.html","template/popover/popover-template.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.collapse",[]).directive("uibCollapse",["$animate","$injector",function(a,b){var c=b.has("$animateCss")?b.get("$animateCss"):null;return{link:function(b,d,e){function f(){d.removeClass("collapse").addClass("collapsing").attr("aria-expanded",!0).attr("aria-hidden",!1),c?c(d,{addClass:"in",easing:"ease",to:{height:d[0].scrollHeight+"px"}}).start()["finally"](g):a.addClass(d,"in",{to:{height:d[0].scrollHeight+"px"}}).then(g)}function g(){d.removeClass("collapsing").addClass("collapse").css({height:"auto"})}function h(){return d.hasClass("collapse")||d.hasClass("in")?(d.css({height:d[0].scrollHeight+"px"}).removeClass("collapse").addClass("collapsing").attr("aria-expanded",!1).attr("aria-hidden",!0),void(c?c(d,{removeClass:"in",to:{height:"0"}}).start()["finally"](i):a.removeClass(d,"in",{to:{height:"0"}}).then(i))):i()}function i(){d.css({height:"0"}),d.removeClass("collapsing").addClass("collapse")}b.$watch(e.uibCollapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.collapse").value("$collapseSuppressWarning",!1).directive("collapse",["$animate","$injector","$log","$collapseSuppressWarning",function(a,b,c,d){var e=b.has("$animateCss")?b.get("$animateCss"):null;return{link:function(b,f,g){function h(){f.removeClass("collapse").addClass("collapsing").attr("aria-expanded",!0).attr("aria-hidden",!1),e?e(f,{easing:"ease",to:{height:f[0].scrollHeight+"px"}}).start().done(i):a.animate(f,{},{height:f[0].scrollHeight+"px"}).then(i)}function i(){f.removeClass("collapsing").addClass("collapse in").css({height:"auto"})}function j(){return f.hasClass("collapse")||f.hasClass("in")?(f.css({height:f[0].scrollHeight+"px"}).removeClass("collapse in").addClass("collapsing").attr("aria-expanded",!1).attr("aria-hidden",!0),void(e?e(f,{to:{height:"0"}}).start().done(k):a.animate(f,{},{height:"0"}).then(k))):k()}function k(){f.css({height:"0"}),f.removeClass("collapsing").addClass("collapse")}d||c.warn("collapse is now deprecated. Use uib-collapse instead."),b.$watch(g.collapse,function(a){a?j():h()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("uibAccordionConfig",{closeOthers:!0}).controller("UibAccordionController",["$scope","$attrs","uibAccordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("uibAccordion",function(){return{controller:"UibAccordionController",controllerAs:"accordion",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"template/accordion/accordion.html"}}}).directive("uibAccordionGroup",function(){return{require:"^uibAccordion",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/accordion/accordion-group.html"},scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.openClass=c.openClass||"panel-open",a.panelClass=c.panelClass,a.$watch("isOpen",function(c){b.toggleClass(a.openClass,!!c),c&&d.closeOthers(a)}),a.toggleOpen=function(b){a.isDisabled||b&&32!==b.which||(a.isOpen=!a.isOpen)}}}}).directive("uibAccordionHeading",function(){return{transclude:!0,template:"",replace:!0,require:"^uibAccordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,angular.noop))}}}).directive("uibAccordionTransclude",function(){return{require:["?^uibAccordionGroup","?^accordionGroup"],link:function(a,b,c,d){d=d[0]?d[0]:d[1],a.$watch(function(){return d[c.uibAccordionTransclude]},function(a){a&&(b.find("span").html(""),b.find("span").append(a))})}}}),angular.module("ui.bootstrap.accordion").value("$accordionSuppressWarning",!1).controller("AccordionController",["$scope","$attrs","$controller","$log","$accordionSuppressWarning",function(a,b,c,d,e){e||d.warn("AccordionController is now deprecated. Use UibAccordionController instead."),angular.extend(this,c("UibAccordionController",{$scope:a,$attrs:b}))}]).directive("accordion",["$log","$accordionSuppressWarning",function(a,b){return{restrict:"EA",controller:"AccordionController",controllerAs:"accordion",transclude:!0,replace:!1,templateUrl:function(a,b){return b.templateUrl||"template/accordion/accordion.html"},link:function(){b||a.warn("accordion is now deprecated. Use uib-accordion instead.")}}}]).directive("accordionGroup",["$log","$accordionSuppressWarning",function(a,b){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/accordion/accordion-group.html"},scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(c,d,e,f){b||a.warn("accordion-group is now deprecated. Use uib-accordion-group instead."),f.addGroup(c),c.openClass=e.openClass||"panel-open",c.panelClass=e.panelClass,c.$watch("isOpen",function(a){d.toggleClass(c.openClass,!!a),a&&f.closeOthers(c)}),c.toggleOpen=function(a){c.isDisabled||a&&32!==a.which||(c.isOpen=!c.isOpen)}}}}]).directive("accordionHeading",["$log","$accordionSuppressWarning",function(a,b){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(c,d,e,f,g){b||a.warn("accordion-heading is now deprecated. Use uib-accordion-heading instead."),f.setHeading(g(c,angular.noop))}}}]).directive("accordionTransclude",["$log","$accordionSuppressWarning",function(a,b){return{require:"^accordionGroup",link:function(c,d,e,f){b||a.warn("accordion-transclude is now deprecated. Use uib-accordion-transclude instead."),c.$watch(function(){return f[e.accordionTransclude]},function(a){a&&(d.find("span").html(""),d.find("span").append(a))})}}}]),angular.module("ui.bootstrap.alert",[]).controller("UibAlertController",["$scope","$attrs","$interpolate","$timeout",function(a,b,c,d){a.closeable=!!b.close;var e=angular.isDefined(b.dismissOnTimeout)?c(b.dismissOnTimeout)(a.$parent):null;e&&d(function(){a.close()},parseInt(e,10))}]).directive("uibAlert",function(){return{controller:"UibAlertController",controllerAs:"alert",templateUrl:function(a,b){return b.templateUrl||"template/alert/alert.html"},transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}),angular.module("ui.bootstrap.alert").value("$alertSuppressWarning",!1).controller("AlertController",["$scope","$attrs","$controller","$log","$alertSuppressWarning",function(a,b,c,d,e){e||d.warn("AlertController is now deprecated. Use UibAlertController instead."),angular.extend(this,c("UibAlertController",{$scope:a,$attrs:b}))}]).directive("alert",["$log","$alertSuppressWarning",function(a,b){return{controller:"AlertController",controllerAs:"alert",templateUrl:function(a,b){return b.templateUrl||"template/alert/alert.html"},transclude:!0,replace:!0,scope:{type:"@",close:"&"},link:function(){b||a.warn("alert is now deprecated. Use uib-alert instead.")}}}]),angular.module("ui.bootstrap.buttons",[]).constant("uibButtonConfig",{activeClass:"active",toggleEvent:"click"}).controller("UibButtonsController",["uibButtonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("uibBtnRadio",function(){return{require:["uibBtnRadio","ngModel"],controller:"UibButtonsController",controllerAs:"buttons",link:function(a,b,c,d){var e=d[0],f=d[1];b.find("input").css({display:"none"}),f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.uibBtnRadio)))},b.on(e.toggleEvent,function(){if(!c.disabled){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.uibBtnRadio)),f.$render()})}})}}}).directive("uibBtnCheckbox",function(){return{require:["uibBtnCheckbox","ngModel"],controller:"UibButtonsController",controllerAs:"button",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){return angular.isDefined(b)?a.$eval(b):c}var h=d[0],i=d[1];b.find("input").css({display:"none"}),i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.on(h.toggleEvent,function(){c.disabled||a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.buttons").value("$buttonsSuppressWarning",!1).controller("ButtonsController",["$controller","$log","$buttonsSuppressWarning",function(a,b,c){c||b.warn("ButtonsController is now deprecated. Use UibButtonsController instead."),angular.extend(this,a("UibButtonsController"))}]).directive("btnRadio",["$log","$buttonsSuppressWarning",function(a,b){return{require:["btnRadio","ngModel"],controller:"ButtonsController",controllerAs:"buttons",link:function(c,d,e,f){b||a.warn("btn-radio is now deprecated. Use uib-btn-radio instead.");var g=f[0],h=f[1];d.find("input").css({display:"none"}),h.$render=function(){d.toggleClass(g.activeClass,angular.equals(h.$modelValue,c.$eval(e.btnRadio)))},d.bind(g.toggleEvent,function(){if(!e.disabled){var a=d.hasClass(g.activeClass);(!a||angular.isDefined(e.uncheckable))&&c.$apply(function(){h.$setViewValue(a?null:c.$eval(e.btnRadio)),h.$render()})}})}}}]).directive("btnCheckbox",["$document","$log","$buttonsSuppressWarning",function(a,b,c){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",controllerAs:"button",link:function(d,e,f,g){function h(){return j(f.btnCheckboxTrue,!0)}function i(){return j(f.btnCheckboxFalse,!1)}function j(a,b){var c=d.$eval(a);return angular.isDefined(c)?c:b}c||b.warn("btn-checkbox is now deprecated. Use uib-btn-checkbox instead.");var k=g[0],l=g[1];e.find("input").css({display:"none"}),l.$render=function(){e.toggleClass(k.activeClass,angular.equals(l.$modelValue,h()))},e.bind(k.toggleEvent,function(){f.disabled||d.$apply(function(){l.$setViewValue(e.hasClass(k.activeClass)?i():h()),l.$render()})}),e.on("keypress",function(b){f.disabled||32!==b.which||a[0].activeElement!==e[0]||d.$apply(function(){l.$setViewValue(e.hasClass(k.activeClass)?i():h()),l.$render()})})}}}]),angular.module("ui.bootstrap.carousel",[]).controller("UibCarouselController",["$scope","$element","$interval","$animate",function(a,b,c,d){function e(b,c,e){s||(angular.extend(b,{direction:e,active:!0}),angular.extend(m.currentSlide||{},{direction:e,active:!1}),d.enabled()&&!a.noTransition&&!a.$currentTransition&&b.$element&&m.slides.length>1&&(b.$element.data(q,b.direction),m.currentSlide&&m.currentSlide.$element&&m.currentSlide.$element.data(q,b.direction),a.$currentTransition=!0,o?d.on("addClass",b.$element,function(b,c){"close"===c&&(a.$currentTransition=null,d.off("addClass",b))}):b.$element.one("$animate:close",function(){a.$currentTransition=null})),m.currentSlide=b,r=c,g())}function f(a){if(angular.isUndefined(n[a].index))return n[a];var b;n.length;for(b=0;b0&&(k=c(i,b))}function h(){k&&(c.cancel(k),k=null)}function i(){var b=+a.interval;l&&!isNaN(b)&&b>0&&n.length?a.next():a.pause()}function j(b){b.length||(a.$currentTransition=null)}var k,l,m=this,n=m.slides=a.slides=[],o=angular.version.minor>=4,p="uib-noTransition",q="uib-slideDirection",r=-1;m.currentSlide=null;var s=!1;m.select=a.select=function(b,c){var d=a.indexOfSlide(b);void 0===c&&(c=d>m.getCurrentIndex()?"next":"prev"),b&&b!==m.currentSlide&&!a.$currentTransition&&e(b,d,c)},a.$on("$destroy",function(){s=!0}),m.getCurrentIndex=function(){return m.currentSlide&&angular.isDefined(m.currentSlide.index)?+m.currentSlide.index:r},a.indexOfSlide=function(a){return angular.isDefined(a.index)?+a.index:n.indexOf(a)},a.next=function(){var b=(m.getCurrentIndex()+1)%n.length;return 0===b&&a.noWrap()?void a.pause():m.select(f(b),"next")},a.prev=function(){var b=m.getCurrentIndex()-1<0?n.length-1:m.getCurrentIndex()-1;return a.noWrap()&&b===n.length-1?void a.pause():m.select(f(b),"prev")},a.isActive=function(a){return m.currentSlide===a},a.$watch("interval",g),a.$watchCollection("slides",j),a.$on("$destroy",h),a.play=function(){l||(l=!0,g())},a.pause=function(){a.noPause||(l=!1,h())},m.addSlide=function(b,c){b.$element=c,n.push(b),1===n.length||b.active?(m.select(n[n.length-1]),1===n.length&&a.play()):b.active=!1},m.removeSlide=function(a){angular.isDefined(a.index)&&n.sort(function(a,b){return+a.index>+b.index});var b=n.indexOf(a);n.splice(b,1),n.length>0&&a.active?b>=n.length?m.select(n[b-1]):m.select(n[b]):r>b&&r--,0===n.length&&(m.currentSlide=null)},a.$watch("noTransition",function(a){b.data(p,a)})}]).directive("uibCarousel",[function(){return{transclude:!0,replace:!0,controller:"UibCarouselController",controllerAs:"carousel",require:"carousel",templateUrl:function(a,b){return b.templateUrl||"template/carousel/carousel.html"},scope:{interval:"=",noTransition:"=",noPause:"=",noWrap:"&"}}}]).directive("uibSlide",function(){return{require:"^uibCarousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/carousel/slide.html"},scope:{active:"=?",actual:"=?",index:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}).animation(".item",["$injector","$animate",function(a,b){function c(a,b,c){a.removeClass(b),c&&c()}var d="uib-noTransition",e="uib-slideDirection",f=null;return a.has("$animateCss")&&(f=a.get("$animateCss")),{beforeAddClass:function(a,g,h){if("active"==g&&a.parent()&&a.parent().parent()&&!a.parent().parent().data(d)){var i=!1,j=a.data(e),k="next"==j?"left":"right",l=c.bind(this,a,k+" "+j,h);return a.addClass(j),f?f(a,{addClass:k}).start().done(l):b.addClass(a,k).then(function(){i||l(),h()}),function(){i=!0}}h()},beforeRemoveClass:function(a,g,h){if("active"===g&&a.parent()&&a.parent().parent()&&!a.parent().parent().data(d)){var i=!1,j=a.data(e),k="next"==j?"left":"right",l=c.bind(this,a,k,h);return f?f(a,{addClass:k}).start().done(l):b.addClass(a,k).then(function(){i||l(),h()}),function(){i=!0}}h()}}}]),angular.module("ui.bootstrap.carousel").value("$carouselSuppressWarning",!1).controller("CarouselController",["$scope","$element","$controller","$log","$carouselSuppressWarning",function(a,b,c,d,e){e||d.warn("CarouselController is now deprecated. Use UibCarouselController instead."),angular.extend(this,c("UibCarouselController",{$scope:a,$element:b}))}]).directive("carousel",["$log","$carouselSuppressWarning",function(a,b){return{transclude:!0,replace:!0,controller:"CarouselController",controllerAs:"carousel",require:"carousel",templateUrl:function(a,b){return b.templateUrl||"template/carousel/carousel.html"},scope:{interval:"=",noTransition:"=",noPause:"=",noWrap:"&"},link:function(){b||a.warn("carousel is now deprecated. Use uib-carousel instead.")}}}]).directive("slide",["$log","$carouselSuppressWarning",function(a,b){return{require:"^carousel",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/carousel/slide.html"},scope:{active:"=?",actual:"=?",index:"=?"},link:function(c,d,e,f){b||a.warn("slide is now deprecated. Use uib-slide instead."),f.addSlide(c,d),c.$on("$destroy",function(){f.removeSlide(c)}),c.$watch("active",function(a){a&&f.select(c)})}}}]),angular.module("ui.bootstrap.dateparser",[]).service("uibDateParser",["$log","$locale","orderByFilter",function(a,b,c){function d(a){var b=[],d=a.split("");return angular.forEach(g,function(c,e){var f=a.indexOf(e);if(f>-1){a=a.split(""),d[f]="("+c.regex+")",a[f]="$";for(var g=f+1,h=f+e.length;h>g;g++)d[g]="",a[g]="$";a=a.join(""),b.push({index:f,apply:c.apply})}}),{regex:new RegExp("^"+d.join("")+"$"),map:c(b,"index")}}function e(a,b,c){return 1>c?!1:1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}var f,g,h=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;this.init=function(){f=b.id,this.parsers={},g={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:b.DATETIME_FORMATS.MONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.MONTH.indexOf(a)}},MMM:{regex:b.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.SHORTMONTH.indexOf(a)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:b.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:b.DATETIME_FORMATS.SHORTDAY.join("|")},HH:{regex:"(?:0|1)[0-9]|2[0-3]",apply:function(a){this.hours=+a}},hh:{regex:"0[0-9]|1[0-2]",apply:function(a){this.hours=+a}},H:{regex:"1?[0-9]|2[0-3]",apply:function(a){this.hours=+a}},h:{regex:"[0-9]|1[0-2]",apply:function(a){this.hours=+a}},mm:{regex:"[0-5][0-9]",apply:function(a){this.minutes=+a}},m:{regex:"[0-9]|[1-5][0-9]",apply:function(a){this.minutes=+a}},sss:{regex:"[0-9][0-9][0-9]",apply:function(a){this.milliseconds=+a}},ss:{regex:"[0-5][0-9]",apply:function(a){this.seconds=+a}},s:{regex:"[0-9]|[1-5][0-9]",apply:function(a){this.seconds=+a}},a:{regex:b.DATETIME_FORMATS.AMPMS.join("|"),apply:function(a){12===this.hours&&(this.hours=0),"PM"===a&&(this.hours+=12)}}}},this.init(),this.parse=function(c,g,i){if(!angular.isString(c)||!g)return c;g=b.DATETIME_FORMATS[g]||g,g=g.replace(h,"\\$&"),b.id!==f&&this.init(),this.parsers[g]||(this.parsers[g]=d(g));var j=this.parsers[g],k=j.regex,l=j.map,m=c.match(k);if(m&&m.length){var n,o;angular.isDate(i)&&!isNaN(i.getTime())?n={year:i.getFullYear(),month:i.getMonth(),date:i.getDate(),hours:i.getHours(),minutes:i.getMinutes(),seconds:i.getSeconds(),milliseconds:i.getMilliseconds()}:(i&&a.warn("dateparser:","baseDate is not a valid date"),n={year:1900,month:0,date:1,hours:0,minutes:0,seconds:0,milliseconds:0});for(var p=1,q=m.length;q>p;p++){var r=l[p-1];r.apply&&r.apply.call(n,m[p])}return e(n.year,n.month,n.date)&&(angular.isDate(i)&&!isNaN(i.getTime())?(o=new Date(i),o.setFullYear(n.year,n.month,n.date,n.hours,n.minutes,n.seconds,n.milliseconds||0)):o=new Date(n.year,n.month,n.date,n.hours,n.minutes,n.seconds,n.milliseconds||0)),o}}}]),angular.module("ui.bootstrap.dateparser").value("$dateParserSuppressWarning",!1).service("dateParser",["$log","$dateParserSuppressWarning","uibDateParser",function(a,b,c){b||a.warn("dateParser is now deprecated. Use uibDateParser instead."),angular.extend(this,c)}]),angular.module("ui.bootstrap.position",[]).factory("$uibPosition",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.position").value("$positionSuppressWarning",!1).service("$position",["$log","$positionSuppressWarning","$uibPosition",function(a,b,c){b||a.warn("$position is now deprecated. Use $uibPosition instead."),angular.extend(this,c)}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).value("$datepickerSuppressError",!1).constant("uibDatepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null,shortcutPropagation:!1}).controller("UibDatepickerController",["$scope","$attrs","$parse","$interpolate","$log","dateFilter","uibDatepickerConfig","$datepickerSuppressError",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","showWeeks","startingDay","yearRange","shortcutPropagation"],function(c,e){i[c]=angular.isDefined(b[c])?6>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):g[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=g[d]?new Date(g[d]):null}),angular.forEach(["minMode","maxMode"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(c){i[d]=angular.isDefined(c)?c:b[d],a[d]=i[d],("minMode"==d&&i.modes.indexOf(a.datepickerMode)i.modes.indexOf(i[d]))&&(a.datepickerMode=i[d])}):(i[d]=g[d]||null,a[d]=i[d])}),a.datepickerMode=a.datepickerMode||g.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),angular.isDefined(b.initDate)?(this.activeDate=a.$parent.$eval(b.initDate)||new Date,a.$parent.$watch(b.initDate,function(a){a&&(j.$isEmpty(j.$modelValue)||j.$invalid)&&(i.activeDate=a,i.refreshView())})):this.activeDate=new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$viewValue){var a=new Date(j.$viewValue),b=!isNaN(a);b?this.activeDate=a:h||e.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$viewValue?new Date(j.$viewValue):null;j.$setValidity("dateDisabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$viewValue?new Date(j.$viewValue):null;return{date:a,label:f(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date),customClass:this.customClass(a)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.customClass=function(b){return a.customClass({date:b,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$viewValue?new Date(j.$viewValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){i.element[0].focus()};a.$on("uib:datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),i.shortcutPropagation||b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate)}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):a.toggleMode("up"===c?1:-1)}}]).controller("UibDaypickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?f[b]:29}function e(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}var f=[31,28,31,30,31,30,31,31,30,31,30,31];this.step={months:1},this.element=b,this.init=function(b){angular.extend(b,this),a.showWeeks=b.showWeeks,b.refreshView()},this.getDates=function(a,b){for(var c,d=new Array(b),e=new Date(a),f=0;b>f;)c=new Date(e),d[f++]=c,e.setDate(e.getDate()+1);return d},this._refreshView=function(){var b=this.activeDate.getFullYear(),d=this.activeDate.getMonth(),f=new Date(this.activeDate);f.setFullYear(b,d,1);var g=this.startingDay-f.getDay(),h=g>0?7-g:-g,i=new Date(f);h>0&&i.setDate(-h+1);for(var j=this.getDates(i,42),k=0;42>k;k++)j[k]=angular.extend(this.createDateObject(j[k],this.formatDay),{secondary:j[k].getMonth()!==d,uid:a.uniqueId+"-"+k});a.labels=new Array(7);for(var l=0;7>l;l++)a.labels[l]={abbr:c(j[l].date,this.formatDayHeader),full:c(j[l].date,"EEEE")};if(a.title=c(this.activeDate,this.formatDayTitle),a.rows=this.split(j,7),a.showWeeks){a.weekNumbers=[];for(var m=(11-this.startingDay)%7,n=a.rows.length,o=0;n>o;o++)a.weekNumbers.push(e(a.rows[o][m].date))}},this.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())},this.handleKeyDown=function(a,b){var c=this.activeDate.getDate();if("left"===a)c-=1;else if("up"===a)c-=7;else if("right"===a)c+=1;else if("down"===a)c+=7;else if("pageup"===a||"pagedown"===a){var e=this.activeDate.getMonth()+("pageup"===a?-1:1);this.activeDate.setMonth(e,1),c=Math.min(d(this.activeDate.getFullYear(),this.activeDate.getMonth()),c)}else"home"===a?c=1:"end"===a&&(c=d(this.activeDate.getFullYear(),this.activeDate.getMonth()));this.activeDate.setDate(c)}}]).controller("UibMonthpickerController",["$scope","$element","dateFilter",function(a,b,c){this.step={years:1},this.element=b,this.init=function(a){angular.extend(a,this),a.refreshView()},this._refreshView=function(){for(var b,d=new Array(12),e=this.activeDate.getFullYear(),f=0;12>f;f++)b=new Date(this.activeDate),b.setFullYear(e,f,1),d[f]=angular.extend(this.createDateObject(b,this.formatMonth),{uid:a.uniqueId+"-"+f});a.title=c(this.activeDate,this.formatMonthTitle),a.rows=this.split(d,3)},this.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},this.handleKeyDown=function(a,b){var c=this.activeDate.getMonth();if("left"===a)c-=1;else if("up"===a)c-=3;else if("right"===a)c+=1;else if("down"===a)c+=3;else if("pageup"===a||"pagedown"===a){var d=this.activeDate.getFullYear()+("pageup"===a?-1:1);this.activeDate.setFullYear(d)}else"home"===a?c=0:"end"===a&&(c=11);this.activeDate.setMonth(c)}}]).controller("UibYearpickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a){return parseInt((a-1)/e,10)*e+1}var e;this.element=b,this.yearpickerInit=function(){e=this.yearRange,this.step={years:e}},this._refreshView=function(){for(var b,c=new Array(e),f=0,g=d(this.activeDate.getFullYear());e>f;f++)b=new Date(this.activeDate),b.setFullYear(g+f,0,1),c[f]=angular.extend(this.createDateObject(b,this.formatYear),{uid:a.uniqueId+"-"+f});a.title=[c[0].label,c[e-1].label].join(" - "),a.rows=this.split(c,5)},this.compare=function(a,b){return a.getFullYear()-b.getFullYear()},this.handleKeyDown=function(a,b){var c=this.activeDate.getFullYear();"left"===a?c-=1:"up"===a?c-=5:"right"===a?c+=1:"down"===a?c+=5:"pageup"===a||"pagedown"===a?c+=("pageup"===a?-1:1)*this.step.years:"home"===a?c=d(this.activeDate.getFullYear()):"end"===a&&(c=d(this.activeDate.getFullYear())+e-1),this.activeDate.setFullYear(c)}}]).directive("uibDatepicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/datepicker.html"},scope:{datepickerMode:"=?",dateDisabled:"&",customClass:"&",shortcutPropagation:"&?"},require:["uibDatepicker","^ngModel"],controller:"UibDatepickerController",controllerAs:"datepicker",link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}).directive("uibDaypicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/day.html"},require:["^?uibDatepicker","uibDaypicker","^?datepicker"],controller:"UibDaypickerController",link:function(a,b,c,d){var e=d[0]||d[2],f=d[1];f.init(e)}}}).directive("uibMonthpicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/month.html"},require:["^?uibDatepicker","uibMonthpicker","^?datepicker"],controller:"UibMonthpickerController",link:function(a,b,c,d){var e=d[0]||d[2],f=d[1];f.init(e)}}}).directive("uibYearpicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/year.html"},require:["^?uibDatepicker","uibYearpicker","^?datepicker"],controller:"UibYearpickerController",link:function(a,b,c,d){var e=d[0]||d[2];angular.extend(e,d[1]),e.yearpickerInit(),e.refreshView()}}}).constant("uibDatepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",datepickerPopupTemplateUrl:"template/datepicker/popup.html",datepickerTemplateUrl:"template/datepicker/datepicker.html",html5Types:{date:"yyyy-MM-dd","datetime-local":"yyyy-MM-ddTHH:mm:ss.sss",month:"yyyy-MM"},currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0,onOpenFocus:!0}).controller("UibDatepickerPopupController",["$scope","$element","$attrs","$compile","$parse","$document","$rootScope","$uibPosition","dateFilter","uibDateParser","uibDatepickerPopupConfig","$timeout",function(a,b,c,d,e,f,g,h,i,j,k,l){ +function m(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function n(b){if(angular.isNumber(b)&&(b=new Date(b)),b){if(angular.isDate(b)&&!isNaN(b))return b;if(angular.isString(b)){var c=j.parse(b,r,a.date);return isNaN(c)?void 0:c}return void 0}return null}function o(a,b){var d=a||b;if(!c.ngRequired&&!d)return!0;if(angular.isNumber(d)&&(d=new Date(d)),d){if(angular.isDate(d)&&!isNaN(d))return!0;if(angular.isString(d)){var e=j.parse(d,r);return!isNaN(e)}return!1}return!0}function p(c){var d=A[0],e=b[0].contains(c.target),f=void 0!==d.contains&&d.contains(c.target);!a.isOpen||e||f||a.$apply(function(){a.isOpen=!1})}function q(c){27===c.which&&a.isOpen?(c.preventDefault(),c.stopPropagation(),a.$apply(function(){a.isOpen=!1}),b[0].focus()):40!==c.which||a.isOpen||(c.preventDefault(),c.stopPropagation(),a.$apply(function(){a.isOpen=!0}))}var r,s,t,u,v,w,x,y,z,A,B={},C=!1;a.watchData={},this.init=function(h){if(z=h,s=angular.isDefined(c.closeOnDateSelection)?a.$parent.$eval(c.closeOnDateSelection):k.closeOnDateSelection,t=angular.isDefined(c.datepickerAppendToBody)?a.$parent.$eval(c.datepickerAppendToBody):k.appendToBody,u=angular.isDefined(c.onOpenFocus)?a.$parent.$eval(c.onOpenFocus):k.onOpenFocus,v=angular.isDefined(c.datepickerPopupTemplateUrl)?c.datepickerPopupTemplateUrl:k.datepickerPopupTemplateUrl,w=angular.isDefined(c.datepickerTemplateUrl)?c.datepickerTemplateUrl:k.datepickerTemplateUrl,a.showButtonBar=angular.isDefined(c.showButtonBar)?a.$parent.$eval(c.showButtonBar):k.showButtonBar,k.html5Types[c.type]?(r=k.html5Types[c.type],C=!0):(r=c.datepickerPopup||c.uibDatepickerPopup||k.datepickerPopup,c.$observe("uibDatepickerPopup",function(a,b){var c=a||k.datepickerPopup;if(c!==r&&(r=c,z.$modelValue=null,!r))throw new Error("uibDatepickerPopup must have a date format specified.")})),!r)throw new Error("uibDatepickerPopup must have a date format specified.");if(C&&c.datepickerPopup)throw new Error("HTML5 date input types do not support custom formats.");if(x=angular.element("
      "),x.attr({"ng-model":"date","ng-change":"dateSelection(date)","template-url":v}),y=angular.element(x.children()[0]),y.attr("template-url",w),C&&"month"===c.type&&(y.attr("datepicker-mode",'"month"'),y.attr("min-mode","month")),c.datepickerOptions){var l=a.$parent.$eval(c.datepickerOptions);l&&l.initDate&&(a.initDate=l.initDate,y.attr("init-date","initDate"),delete l.initDate),angular.forEach(l,function(a,b){y.attr(m(b),a)})}angular.forEach(["minMode","maxMode","minDate","maxDate","datepickerMode","initDate","shortcutPropagation"],function(b){if(c[b]){var d=e(c[b]);if(a.$parent.$watch(d,function(c){a.watchData[b]=c,("minDate"===b||"maxDate"===b)&&(B[b]=new Date(c))}),y.attr(m(b),"watchData."+b),"datepickerMode"===b){var f=d.assign;a.$watch("watchData."+b,function(b,c){angular.isFunction(f)&&b!==c&&f(a.$parent,b)})}}}),c.dateDisabled&&y.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),c.showWeeks&&y.attr("show-weeks",c.showWeeks),c.customClass&&y.attr("custom-class","customClass({ date: date, mode: mode })"),C?z.$formatters.push(function(b){return a.date=b,b}):(z.$$parserName="date",z.$validators.date=o,z.$parsers.unshift(n),z.$formatters.push(function(b){return a.date=b,z.$isEmpty(b)?b:i(b,r)})),z.$viewChangeListeners.push(function(){a.date=j.parse(z.$viewValue,r,a.date)}),b.bind("keydown",q),A=d(x)(a),x.remove(),t?f.find("body").append(A):b.after(A),a.$on("$destroy",function(){a.isOpen===!0&&(g.$$phase||a.$apply(function(){a.isOpen=!1})),A.remove(),b.unbind("keydown",q),f.unbind("click",p)})},a.getText=function(b){return a[b+"Text"]||k[b+"Text"]},a.isDisabled=function(b){return"today"===b&&(b=new Date),a.watchData.minDate&&a.compare(b,B.minDate)<0||a.watchData.maxDate&&a.compare(b,B.maxDate)>0},a.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())},a.dateSelection=function(c){angular.isDefined(c)&&(a.date=c);var d=a.date?i(a.date,r):null;b.val(d),z.$setViewValue(d),s&&(a.isOpen=!1,b[0].focus())},a.keydown=function(c){27===c.which&&(a.isOpen=!1,b[0].focus())},a.select=function(b){if("today"===b){var c=new Date;angular.isDate(a.date)?(b=new Date(a.date),b.setFullYear(c.getFullYear(),c.getMonth(),c.getDate())):b=new Date(c.setHours(0,0,0,0))}a.dateSelection(b)},a.close=function(){a.isOpen=!1,b[0].focus()},a.$watch("isOpen",function(c){c?(a.position=t?h.offset(b):h.position(b),a.position.top=a.position.top+b.prop("offsetHeight"),l(function(){u&&a.$broadcast("uib:datepicker.focus"),f.bind("click",p)},0,!1)):f.unbind("click",p)})}]).directive("uibDatepickerPopup",function(){return{require:["ngModel","uibDatepickerPopup"],controller:"UibDatepickerPopupController",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&",customClass:"&"},link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibDatepickerPopupWrap",function(){return{replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/popup.html"}}}),angular.module("ui.bootstrap.datepicker").value("$datepickerSuppressWarning",!1).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$log","dateFilter","uibDatepickerConfig","$datepickerSuppressError","$datepickerSuppressWarning",function(a,b,c,d,e,f,g,h,i){i||e.warn("DatepickerController is now deprecated. Use UibDatepickerController instead.");var j=this,k={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","showWeeks","startingDay","yearRange","shortcutPropagation"],function(c,e){j[c]=angular.isDefined(b[c])?6>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):g[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){j[d]=a?new Date(a):null,j.refreshView()}):j[d]=g[d]?new Date(g[d]):null}),angular.forEach(["minMode","maxMode"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(c){j[d]=angular.isDefined(c)?c:b[d],a[d]=j[d],("minMode"==d&&j.modes.indexOf(a.datepickerMode)j.modes.indexOf(j[d]))&&(a.datepickerMode=j[d])}):(j[d]=g[d]||null,a[d]=j[d])}),a.datepickerMode=a.datepickerMode||g.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),angular.isDefined(b.initDate)?(this.activeDate=a.$parent.$eval(b.initDate)||new Date,a.$parent.$watch(b.initDate,function(a){a&&(k.$isEmpty(k.$modelValue)||k.$invalid)&&(j.activeDate=a,j.refreshView())})):this.activeDate=new Date,a.isActive=function(b){return 0===j.compare(b.date,j.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){k=a,k.$render=function(){j.render()}},this.render=function(){if(k.$viewValue){var a=new Date(k.$viewValue),b=!isNaN(a);b?this.activeDate=a:h||e.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=k.$viewValue?new Date(k.$viewValue):null;k.$setValidity("dateDisabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=k.$viewValue?new Date(k.$viewValue):null;return{date:a,label:f(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date),customClass:this.customClass(a)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.customClass=function(b){return a.customClass({date:b,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},this.fixTimeZone=function(a){var b=a.getHours();a.setHours(23===b?b+2:0)},a.select=function(b){if(a.datepickerMode===j.minMode){var c=k.$viewValue?new Date(k.$viewValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),k.$setViewValue(c),k.$render()}else j.activeDate=b,a.datepickerMode=j.modes[j.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=j.activeDate.getFullYear()+a*(j.step.years||0),c=j.activeDate.getMonth()+a*(j.step.months||0);j.activeDate.setFullYear(b,c,1),j.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===j.maxMode&&1===b||a.datepickerMode===j.minMode&&-1===b||(a.datepickerMode=j.modes[j.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var l=function(){j.element[0].focus()};a.$on("uib:datepicker.focus",l),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),j.shortcutPropagation||b.stopPropagation(),"enter"===c||"space"===c){if(j.isDisabled(j.activeDate))return;a.select(j.activeDate)}else!b.ctrlKey||"up"!==c&&"down"!==c?(j.handleKeyDown(c,b),j.refreshView()):a.toggleMode("up"===c?1:-1)}}]).directive("datepicker",["$log","$datepickerSuppressWarning",function(a,b){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/datepicker.html"},scope:{datepickerMode:"=?",dateDisabled:"&",customClass:"&",shortcutPropagation:"&?"},require:["datepicker","^ngModel"],controller:"DatepickerController",controllerAs:"datepicker",link:function(c,d,e,f){b||a.warn("datepicker is now deprecated. Use uib-datepicker instead.");var g=f[0],h=f[1];g.init(h)}}}]).directive("daypicker",["$log","$datepickerSuppressWarning",function(a,b){return{replace:!0,templateUrl:"template/datepicker/day.html",require:["^datepicker","daypicker"],controller:"UibDaypickerController",link:function(c,d,e,f){b||a.warn("daypicker is now deprecated. Use uib-daypicker instead.");var g=f[0],h=f[1];h.init(g)}}}]).directive("monthpicker",["$log","$datepickerSuppressWarning",function(a,b){return{replace:!0,templateUrl:"template/datepicker/month.html",require:["^datepicker","monthpicker"],controller:"UibMonthpickerController",link:function(c,d,e,f){b||a.warn("monthpicker is now deprecated. Use uib-monthpicker instead.");var g=f[0],h=f[1];h.init(g)}}}]).directive("yearpicker",["$log","$datepickerSuppressWarning",function(a,b){return{replace:!0,templateUrl:"template/datepicker/year.html",require:["^datepicker","yearpicker"],controller:"UibYearpickerController",link:function(c,d,e,f){b||a.warn("yearpicker is now deprecated. Use uib-yearpicker instead.");var g=f[0];angular.extend(g,f[1]),g.yearpickerInit(),g.refreshView()}}}]).directive("datepickerPopup",["$log","$datepickerSuppressWarning",function(a,b){return{require:["ngModel","datepickerPopup"],controller:"UibDatepickerPopupController",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&",customClass:"&"},link:function(c,d,e,f){b||a.warn("datepicker-popup is now deprecated. Use uib-datepicker-popup instead.");var g=f[0],h=f[1];h.init(g)}}}]).directive("datepickerPopupWrap",["$log","$datepickerSuppressWarning",function(a,b){return{replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/popup.html"},link:function(){b||a.warn("datepicker-popup-wrap is now deprecated. Use uib-datepicker-popup-wrap instead.")}}}]),angular.module("ui.bootstrap.dropdown",["ui.bootstrap.position"]).constant("uibDropdownConfig",{openClass:"open"}).service("uibDropdownService",["$document","$rootScope",function(a,b){var c=null;this.open=function(b){c||(a.bind("click",d),a.bind("keydown",e)),c&&c!==b&&(c.isOpen=!1),c=b},this.close=function(b){c===b&&(c=null,a.unbind("click",d),a.unbind("keydown",e))};var d=function(a){if(c&&(!a||"disabled"!==c.getAutoClose())){var d=c.getToggleElement();if(!(a&&d&&d[0].contains(a.target))){var e=c.getDropdownElement();a&&"outsideClick"===c.getAutoClose()&&e&&e[0].contains(a.target)||(c.isOpen=!1,b.$$phase||c.$apply())}}},e=function(a){27===a.which?(c.focusToggleElement(),d()):c.isKeynavEnabled()&&/(38|40)/.test(a.which)&&c.isOpen&&(a.preventDefault(),a.stopPropagation(),c.focusDropdownEntry(a.which))}}]).controller("UibDropdownController",["$scope","$element","$attrs","$parse","uibDropdownConfig","uibDropdownService","$animate","$uibPosition","$document","$compile","$templateRequest",function(a,b,c,d,e,f,g,h,i,j,k){var l,m,n=this,o=a.$new(),p=e.openClass,q=angular.noop,r=c.onToggle?d(c.onToggle):angular.noop,s=!1,t=!1;b.addClass("dropdown"),this.init=function(){c.isOpen&&(m=d(c.isOpen),q=m.assign,a.$watch(m,function(a){o.isOpen=!!a})),s=angular.isDefined(c.dropdownAppendToBody),t=angular.isDefined(c.uibKeyboardNav),s&&n.dropdownMenu&&(i.find("body").append(n.dropdownMenu),b.on("$destroy",function(){n.dropdownMenu.remove()}))},this.toggle=function(a){return o.isOpen=arguments.length?!!a:!o.isOpen},this.isOpen=function(){return o.isOpen},o.getToggleElement=function(){return n.toggleElement},o.getAutoClose=function(){return c.autoClose||"always"},o.getElement=function(){return b},o.isKeynavEnabled=function(){return t},o.focusDropdownEntry=function(a){var c=n.dropdownMenu?angular.element(n.dropdownMenu).find("a"):angular.element(b).find("ul").eq(0).find("a");switch(a){case 40:angular.isNumber(n.selectedOption)?n.selectedOption=n.selectedOption===c.length-1?n.selectedOption:n.selectedOption+1:n.selectedOption=0;break;case 38:angular.isNumber(n.selectedOption)?n.selectedOption=0===n.selectedOption?0:n.selectedOption-1:n.selectedOption=c.length-1}c[n.selectedOption].focus()},o.getDropdownElement=function(){return n.dropdownMenu},o.focusToggleElement=function(){n.toggleElement&&n.toggleElement[0].focus()},o.$watch("isOpen",function(c,d){if(s&&n.dropdownMenu){var e=h.positionElements(b,n.dropdownMenu,"bottom-left",!0),i={top:e.top+"px",display:c?"block":"none"},m=n.dropdownMenu.hasClass("dropdown-menu-right");m?(i.left="auto",i.right=window.innerWidth-(e.left+b.prop("offsetWidth"))+"px"):(i.left=e.left+"px",i.right="auto"),n.dropdownMenu.css(i)}if(g[c?"addClass":"removeClass"](b,p).then(function(){angular.isDefined(c)&&c!==d&&r(a,{open:!!c})}),c)n.dropdownMenuTemplateUrl&&k(n.dropdownMenuTemplateUrl).then(function(a){l=o.$new(),j(a.trim())(l,function(a){var b=a;n.dropdownMenu.replaceWith(b),n.dropdownMenu=b})}),o.focusToggleElement(),f.open(o);else{if(n.dropdownMenuTemplateUrl){l&&l.$destroy();var t=angular.element('');n.dropdownMenu.replaceWith(t),n.dropdownMenu=t}f.close(o),n.selectedOption=null}angular.isFunction(q)&&q(a,c)}),a.$on("$locationChangeSuccess",function(){"disabled"!==o.getAutoClose()&&(o.isOpen=!1)});var u=a.$on("$destroy",function(){o.$destroy()});o.$on("$destroy",u)}]).directive("uibDropdown",function(){return{controller:"UibDropdownController",link:function(a,b,c,d){d.init()}}}).directive("uibDropdownMenu",function(){return{restrict:"AC",require:"?^uibDropdown",link:function(a,b,c,d){if(d&&!angular.isDefined(c.dropdownNested)){b.addClass("dropdown-menu");var e=c.templateUrl;e&&(d.dropdownMenuTemplateUrl=e),d.dropdownMenu||(d.dropdownMenu=b)}}}}).directive("uibKeyboardNav",function(){return{restrict:"A",require:"?^uibDropdown",link:function(a,b,c,d){b.bind("keydown",function(a){if(-1!==[38,40].indexOf(a.which)){a.preventDefault(),a.stopPropagation();var b=d.dropdownMenu.find("a");switch(a.which){case 40:angular.isNumber(d.selectedOption)?d.selectedOption=d.selectedOption===b.length-1?d.selectedOption:d.selectedOption+1:d.selectedOption=0;break;case 38:angular.isNumber(d.selectedOption)?d.selectedOption=0===d.selectedOption?0:d.selectedOption-1:d.selectedOption=b.length-1}b[d.selectedOption].focus()}})}}}).directive("uibDropdownToggle",function(){return{require:"?^uibDropdown",link:function(a,b,c,d){if(d){b.addClass("dropdown-toggle"),d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.dropdown").value("$dropdownSuppressWarning",!1).service("dropdownService",["$log","$dropdownSuppressWarning","uibDropdownService",function(a,b,c){b||a.warn("dropdownService is now deprecated. Use uibDropdownService instead."),angular.extend(this,c)}]).controller("DropdownController",["$scope","$element","$attrs","$parse","uibDropdownConfig","uibDropdownService","$animate","$uibPosition","$document","$compile","$templateRequest","$log","$dropdownSuppressWarning",function(a,b,c,d,e,f,g,h,i,j,k,l,m){m||l.warn("DropdownController is now deprecated. Use UibDropdownController instead.");var n,o,p=this,q=a.$new(),r=e.openClass,s=angular.noop,t=c.onToggle?d(c.onToggle):angular.noop,u=!1,v=!1;b.addClass("dropdown"),this.init=function(){c.isOpen&&(o=d(c.isOpen),s=o.assign,a.$watch(o,function(a){q.isOpen=!!a})),u=angular.isDefined(c.dropdownAppendToBody),v=angular.isDefined(c.uibKeyboardNav),u&&p.dropdownMenu&&(i.find("body").append(p.dropdownMenu),b.on("$destroy",function(){p.dropdownMenu.remove()}))},this.toggle=function(a){return q.isOpen=arguments.length?!!a:!q.isOpen},this.isOpen=function(){return q.isOpen},q.getToggleElement=function(){return p.toggleElement},q.getAutoClose=function(){return c.autoClose||"always"},q.getElement=function(){return b},q.isKeynavEnabled=function(){return v},q.focusDropdownEntry=function(a){var c=p.dropdownMenu?angular.element(p.dropdownMenu).find("a"):angular.element(b).find("ul").eq(0).find("a");switch(a){case 40:angular.isNumber(p.selectedOption)?p.selectedOption=p.selectedOption===c.length-1?p.selectedOption:p.selectedOption+1:p.selectedOption=0;break;case 38:angular.isNumber(p.selectedOption)?p.selectedOption=0===p.selectedOption?0:p.selectedOption-1:p.selectedOption=c.length-1}c[p.selectedOption].focus()},q.getDropdownElement=function(){return p.dropdownMenu},q.focusToggleElement=function(){p.toggleElement&&p.toggleElement[0].focus()},q.$watch("isOpen",function(c,d){if(u&&p.dropdownMenu){var e=h.positionElements(b,p.dropdownMenu,"bottom-left",!0),i={top:e.top+"px",display:c?"block":"none"},l=p.dropdownMenu.hasClass("dropdown-menu-right");l?(i.left="auto",i.right=window.innerWidth-(e.left+b.prop("offsetWidth"))+"px"):(i.left=e.left+"px",i.right="auto"),p.dropdownMenu.css(i)}if(g[c?"addClass":"removeClass"](b,r).then(function(){angular.isDefined(c)&&c!==d&&t(a,{open:!!c})}),c)p.dropdownMenuTemplateUrl&&k(p.dropdownMenuTemplateUrl).then(function(a){n=q.$new(),j(a.trim())(n,function(a){var b=a;p.dropdownMenu.replaceWith(b),p.dropdownMenu=b})}),q.focusToggleElement(),f.open(q);else{if(p.dropdownMenuTemplateUrl){n&&n.$destroy();var m=angular.element('');p.dropdownMenu.replaceWith(m),p.dropdownMenu=m}f.close(q),p.selectedOption=null}angular.isFunction(s)&&s(a,c)}),a.$on("$locationChangeSuccess",function(){"disabled"!==q.getAutoClose()&&(q.isOpen=!1)});var w=a.$on("$destroy",function(){q.$destroy()});q.$on("$destroy",w)}]).directive("dropdown",["$log","$dropdownSuppressWarning",function(a,b){return{controller:"DropdownController",link:function(c,d,e,f){b||a.warn("dropdown is now deprecated. Use uib-dropdown instead."),f.init()}}}]).directive("dropdownMenu",["$log","$dropdownSuppressWarning",function(a,b){return{restrict:"AC",require:"?^dropdown",link:function(c,d,e,f){if(f&&!angular.isDefined(e.dropdownNested)){b||a.warn("dropdown-menu is now deprecated. Use uib-dropdown-menu instead."),d.addClass("dropdown-menu");var g=e.templateUrl;g&&(f.dropdownMenuTemplateUrl=g),f.dropdownMenu||(f.dropdownMenu=d)}}}}]).directive("keyboardNav",["$log","$dropdownSuppressWarning",function(a,b){return{restrict:"A",require:"?^dropdown",link:function(c,d,e,f){b||a.warn("keyboard-nav is now deprecated. Use uib-keyboard-nav instead."),d.bind("keydown",function(a){if(-1!==[38,40].indexOf(a.which)){a.preventDefault(),a.stopPropagation();var b=f.dropdownMenu.find("a");switch(a.which){case 40:angular.isNumber(f.selectedOption)?f.selectedOption=f.selectedOption===b.length-1?f.selectedOption:f.selectedOption+1:f.selectedOption=0;break;case 38:angular.isNumber(f.selectedOption)?f.selectedOption=0===f.selectedOption?0:f.selectedOption-1:f.selectedOption=b.length-1}b[f.selectedOption].focus()}})}}}]).directive("dropdownToggle",["$log","$dropdownSuppressWarning",function(a,b){return{require:"?^dropdown",link:function(c,d,e,f){if(b||a.warn("dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead."),f){d.addClass("dropdown-toggle"),f.toggleElement=d;var g=function(a){a.preventDefault(),d.hasClass("disabled")||e.disabled||c.$apply(function(){f.toggle()})};d.bind("click",g),d.attr({"aria-haspopup":!0,"aria-expanded":!1}),c.$watch(f.isOpen,function(a){d.attr("aria-expanded",!!a)}),c.$on("$destroy",function(){d.unbind("click",g)})}}}}]),angular.module("ui.bootstrap.stackedMap",[]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0&&(b=u.top().value,b.modalDomEl.toggleClass(b.windowTopClass||"",a))}function m(){if(q&&-1==j()){var a=r;n(q,r,function(){a=null}),q=void 0,r=void 0}}function n(b,c,d){function e(){e.done||(e.done=!0,p?p(b,{event:"leave"}).start().then(function(){b.remove()}):a.leave(b),c.$destroy(),d&&d())}var g,h=null,i=function(){return g||(g=f.defer(),h=g.promise),function(){g.resolve()}};return c.$broadcast(w.NOW_CLOSING_EVENT,i),f.when(h).then(e)}function o(a,b,c){return!a.value.modalScope.$broadcast("modal.closing",b,c).defaultPrevented}var p=null;g.has("$animateCss")&&(p=g.get("$animateCss"));var q,r,s,t="modal-open",u=i.createNew(),v=h.createNew(),w={NOW_CLOSING_EVENT:"modal.stack.now-closing"},x=0,y="a[href], area[href], input:not([disabled]), button:not([disabled]),select:not([disabled]), textarea:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable=true]";return e.$watch(j,function(a){r&&(r.index=a)}),c.bind("keydown",function(a){if(a.isDefaultPrevented())return a;var b=u.top();if(b&&b.value.keyboard)switch(a.which){case 27:a.preventDefault(),e.$apply(function(){w.dismiss(b.key,"escape key press")});break;case 9:w.loadFocusElementList(b);var c=!1;a.shiftKey?w.isFocusInFirstItem(a)&&(c=w.focusLastFocusableElement()):w.isFocusInLastItem(a)&&(c=w.focusFirstFocusableElement()),c&&(a.preventDefault(),a.stopPropagation())}}),w.open=function(a,b){var f=c[0].activeElement,g=b.openedClass||t;l(!1),u.add(a,{deferred:b.deferred,renderDeferred:b.renderDeferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard,openedClass:b.openedClass,windowTopClass:b.windowTopClass}),v.put(g,a);var h=c.find("body").eq(0),i=j();if(i>=0&&!q){r=e.$new(!0),r.index=i;var k=angular.element('
      ');k.attr("backdrop-class",b.backdropClass),b.animation&&k.attr("modal-animation","true"),q=d(k)(r),h.append(q)}var m=angular.element('
      ');m.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,"window-top-class":b.windowTopClass,size:b.size,index:u.length()-1,animate:"animate"}).html(b.content),b.animation&&m.attr("modal-animation","true");var n=d(m)(b.scope);u.top().value.modalDomEl=n,u.top().value.modalOpener=f,h.append(n),h.addClass(g),w.clearFocusListCache()},w.close=function(a,b){var c=u.get(a);return c&&o(c,b,!0)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.resolve(b),k(a,c.value.modalOpener),!0):!c},w.dismiss=function(a,b){var c=u.get(a);return c&&o(c,b,!1)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.reject(b),k(a,c.value.modalOpener),!0):!c},w.dismissAll=function(a){for(var b=this.getTop();b&&this.dismiss(b.key,a);)b=this.getTop()},w.getTop=function(){return u.top()},w.modalRendered=function(a){var b=u.get(a);b&&b.value.renderDeferred.resolve()},w.focusFirstFocusableElement=function(){return s.length>0?(s[0].focus(),!0):!1},w.focusLastFocusableElement=function(){return s.length>0?(s[s.length-1].focus(),!0):!1},w.isFocusInFirstItem=function(a){return s.length>0?(a.target||a.srcElement)==s[0]:!1},w.isFocusInLastItem=function(a){return s.length>0?(a.target||a.srcElement)==s[s.length-1]:!1},w.clearFocusListCache=function(){s=[],x=0},w.loadFocusElementList=function(a){if((void 0===s||!s.length)&&a){var b=a.value.modalDomEl;b&&b.length&&(s=b[0].querySelectorAll(y))}},w}]).provider("$uibModal",function(){var a={options:{animation:!0,backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$templateRequest","$controller","$uibModalStack","$modalSuppressWarning","$log",function(b,c,d,e,f,g,h,i){function j(a){return a.template?d.when(a.template):e(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl)}function k(a){var c=[];return angular.forEach(a,function(a){angular.isFunction(a)||angular.isArray(a)?c.push(d.when(b.invoke(a))):angular.isString(a)?c.push(d.when(b.get(a))):c.push(d.when(a))}),c}var l={},m=null;return l.getPromiseChain=function(){return m},l.open=function(b){function e(){return r}var l=d.defer(),n=d.defer(),o=d.defer(),p={result:l.promise,opened:n.promise,rendered:o.promise,close:function(a){return g.close(p,a)},dismiss:function(a){return g.dismiss(p,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var q,r=d.all([j(b)].concat(k(b.resolve)));return q=m=d.all([m]).then(e,e).then(function(a){var d=(b.scope||c).$new();d.$close=p.close,d.$dismiss=p.dismiss,d.$on("$destroy",function(){d.$$uibDestructionScheduled||d.$dismiss("$uibUnscheduledDestruction")});var e,j={},k=1;b.controller&&(j.$scope=d,j.$uibModalInstance=p,Object.defineProperty(j,"$modalInstance",{get:function(){return h||i.warn("$modalInstance is now deprecated. Use $uibModalInstance instead."),p}}),angular.forEach(b.resolve,function(b,c){j[c]=a[k++]}),e=f(b.controller,j),b.controllerAs&&(b.bindToController&&angular.extend(e,d),d[b.controllerAs]=e)),g.open(p,{scope:d,deferred:l,renderDeferred:o,content:a[0],animation:b.animation,backdrop:b.backdrop,keyboard:b.keyboard,backdropClass:b.backdropClass,windowTopClass:b.windowTopClass,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size,openedClass:b.openedClass}),n.resolve(!0)},function(a){n.reject(a),l.reject(a)})["finally"](function(){m===q&&(m=null)}),p},l}]};return a}),angular.module("ui.bootstrap.modal").value("$modalSuppressWarning",!1).directive("modalBackdrop",["$animate","$injector","$modalStack","$log","$modalSuppressWarning",function(a,b,c,d,e){function f(b,f,h){e||d.warn("modal-backdrop is now deprecated. Use uib-modal-backdrop instead."),f.addClass("modal-backdrop"),h.modalInClass&&(g?g(f,{addClass:h.modalInClass}).start():a.addClass(f,h.modalInClass),b.$on(c.NOW_CLOSING_EVENT,function(b,c){var d=c();g?g(f,{removeClass:h.modalInClass}).start().then(d):a.removeClass(f,h.modalInClass).then(d)}))}var g=null;return b.has("$animateCss")&&(g=b.get("$animateCss")),{replace:!0,templateUrl:"template/modal/backdrop.html",compile:function(a,b){return a.addClass(b.backdropClass),f}}}]).directive("modalWindow",["$modalStack","$q","$animate","$injector","$log","$modalSuppressWarning",function(a,b,c,d,e,f){var g=null;return d.has("$animateCss")&&(g=d.get("$animateCss")),{scope:{index:"@"},replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"template/modal/window.html"},link:function(d,h,i){f||e.warn("modal-window is now deprecated. Use uib-modal-window instead."),h.addClass(i.windowClass||""),h.addClass(i.windowTopClass||""),d.size=i.size,d.close=function(b){var c=a.getTop();c&&c.value.backdrop&&"static"!==c.value.backdrop&&b.target===b.currentTarget&&(b.preventDefault(),b.stopPropagation(),a.dismiss(c.key,"backdrop click"))},h.on("click",d.close),d.$isRendered=!0;var j=b.defer();i.$observe("modalRender",function(a){"true"==a&&j.resolve()}),j.promise.then(function(){var e=null;i.modalInClass&&(e=g?g(h,{addClass:i.modalInClass}).start():c.addClass(h,i.modalInClass),d.$on(a.NOW_CLOSING_EVENT,function(a,b){var d=b();g?g(h,{removeClass:i.modalInClass}).start().then(d):c.removeClass(h,i.modalInClass).then(d)})),b.when(e).then(function(){var a=h[0].querySelector("[autofocus]");a?a.focus():h[0].focus()});var f=a.getTop();f&&a.modalRendered(f.key)})}}}]).directive("modalAnimationClass",["$log","$modalSuppressWarning",function(a,b){return{compile:function(c,d){b||a.warn("modal-animation-class is now deprecated. Use uib-modal-animation-class instead."),d.modalAnimation&&c.addClass(d.modalAnimationClass)}}}]).directive("modalTransclude",["$log","$modalSuppressWarning",function(a,b){return{link:function(c,d,e,f,g){ +b||a.warn("modal-transclude is now deprecated. Use uib-modal-transclude instead."),g(c.$parent,function(a){d.empty(),d.append(a)})}}}]).service("$modalStack",["$animate","$timeout","$document","$compile","$rootScope","$q","$injector","$$multiMap","$$stackedMap","$uibModalStack","$log","$modalSuppressWarning",function(a,b,c,d,e,f,g,h,i,j,k,l){l||k.warn("$modalStack is now deprecated. Use $uibModalStack instead."),angular.extend(this,j)}]).provider("$modal",["$uibModalProvider",function(a){angular.extend(this,a),this.$get=["$injector","$log","$modalSuppressWarning",function(b,c,d){return d||c.warn("$modal is now deprecated. Use $uibModal instead."),b.invoke(a.$get)}]}]),angular.module("ui.bootstrap.pagination",[]).controller("UibPaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(g,h){e=g,this.config=h,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=h.itemsPerPage,a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b,c){c&&c.preventDefault();var d=!a.ngDisabled||!c;d&&a.page!==b&&b>0&&b<=a.totalPages&&(c&&c.target&&c.target.blur(),e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages}}]).constant("uibPaginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("uibPagination",["$parse","uibPaginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@",ngDisabled:"="},require:["uibPagination","?ngModel"],controller:"UibPaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"template/pagination/pagination.html"},replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render()});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("uibPagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("uibPager",["uibPagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@",ngDisabled:"="},require:["uibPager","?ngModel"],controller:"UibPaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"template/pagination/pager.html"},replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.pagination").value("$paginationSuppressWarning",!1).controller("PaginationController",["$scope","$attrs","$parse","$log","$paginationSuppressWarning",function(a,b,c,d,e){e||d.warn("PaginationController is now deprecated. Use UibPaginationController instead.");var f=this,g={$setViewValue:angular.noop},h=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d,e){g=d,this.config=e,g.$render=function(){f.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){f.itemsPerPage=parseInt(b,10),a.totalPages=f.calculateTotalPages()}):this.itemsPerPage=e.itemsPerPage,a.$watch("totalItems",function(){a.totalPages=f.calculateTotalPages()}),a.$watch("totalPages",function(b){h(a.$parent,b),a.page>b?a.selectPage(b):g.$render()})},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(g.$viewValue,10)||1},a.selectPage=function(b,c){c&&c.preventDefault();var d=!a.ngDisabled||!c;d&&a.page!==b&&b>0&&b<=a.totalPages&&(c&&c.target&&c.target.blur(),g.$setViewValue(b),g.$render())},a.getText=function(b){return a[b+"Text"]||f.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages}}]).directive("pagination",["$parse","uibPaginationConfig","$log","$paginationSuppressWarning",function(a,b,c,d){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@",ngDisabled:"="},require:["pagination","?ngModel"],controller:"PaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"template/pagination/pagination.html"},replace:!0,link:function(e,f,g,h){function i(a,b,c){return{number:a,text:b,active:c}}function j(a,b){var c=[],d=1,e=b,f=angular.isDefined(m)&&b>m;f&&(n?(d=Math.max(a-Math.floor(m/2),1),e=d+m-1,e>b&&(e=b,d=e-m+1)):(d=(Math.ceil(a/m)-1)*m+1,e=Math.min(d+m-1,b)));for(var g=d;e>=g;g++){var h=i(g,g,g===a);c.push(h)}if(f&&!n){if(d>1){var j=i(d-1,"...",!1);c.unshift(j)}if(b>e){var k=i(e+1,"...",!1);c.push(k)}}return c}d||c.warn("pagination is now deprecated. Use uib-pagination instead.");var k=h[0],l=h[1];if(l){var m=angular.isDefined(g.maxSize)?e.$parent.$eval(g.maxSize):b.maxSize,n=angular.isDefined(g.rotate)?e.$parent.$eval(g.rotate):b.rotate;e.boundaryLinks=angular.isDefined(g.boundaryLinks)?e.$parent.$eval(g.boundaryLinks):b.boundaryLinks,e.directionLinks=angular.isDefined(g.directionLinks)?e.$parent.$eval(g.directionLinks):b.directionLinks,k.init(l,b),g.maxSize&&e.$parent.$watch(a(g.maxSize),function(a){m=parseInt(a,10),k.render()});var o=k.render;k.render=function(){o(),e.page>0&&e.page<=e.totalPages&&(e.pages=j(e.page,e.totalPages))}}}}}]).directive("pager",["uibPagerConfig","$log","$paginationSuppressWarning",function(a,b,c){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@",ngDisabled:"="},require:["pager","?ngModel"],controller:"PaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"template/pagination/pager.html"},replace:!0,link:function(d,e,f,g){c||b.warn("pager is now deprecated. Use uib-pager instead.");var h=g[0],i=g[1];i&&(d.align=angular.isDefined(f.align)?d.$parent.$eval(f.align):a.align,h.init(i,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.stackedMap"]).provider("$uibTooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0,popupCloseDelay:0,useContentExp:!1},c={mouseenter:"mouseleave",click:"click",focus:"blur",none:""},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$document","$uibPosition","$interpolate","$rootScope","$parse","$$stackedMap",function(e,f,g,h,i,j,k,l,m){var n=m.createNew();return h.on("keypress",function(a){if(27===a.which){var b=n.top();b&&(b.value.close(),n.removeTop(),b=null)}}),function(e,k,m,o){function p(a){var b=(a||o.trigger||m).split(" "),d=b.map(function(a){return c[a]||a});return{show:b,hide:d}}o=angular.extend({},b,d,o);var q=a(e),r=j.startSymbol(),s=j.endSymbol(),t="
      ';return{compile:function(a,b){var c=f(t);return function(a,b,d,f){function j(){L.isOpen?q():m()}function m(){(!K||a.$eval(d[k+"Enable"]))&&(u(),x(),L.popupDelay?F||(F=g(r,L.popupDelay,!1)):r())}function q(){s(),L.popupCloseDelay?G||(G=g(t,L.popupCloseDelay,!1)):t()}function r(){return s(),u(),L.content?(v(),void L.$evalAsync(function(){L.isOpen=!0,y(!0),Q()})):angular.noop}function s(){F&&(g.cancel(F),F=null),H&&(g.cancel(H),H=null)}function t(){s(),u(),L&&L.$evalAsync(function(){L.isOpen=!1,y(!1),L.animation?E||(E=g(w,150,!1)):w()})}function u(){G&&(g.cancel(G),G=null),E&&(g.cancel(E),E=null)}function v(){C||(D=L.$new(),C=c(D,function(a){I?h.find("body").append(a):b.after(a)}),z())}function w(){A(),E=null,C&&(C.remove(),C=null),D&&(D.$destroy(),D=null)}function x(){L.title=d[k+"Title"],O?L.content=O(a):L.content=d[e],L.popupClass=d[k+"Class"],L.placement=angular.isDefined(d[k+"Placement"])?d[k+"Placement"]:o.placement;var b=parseInt(d[k+"PopupDelay"],10),c=parseInt(d[k+"PopupCloseDelay"],10);L.popupDelay=isNaN(b)?o.popupDelay:b,L.popupCloseDelay=isNaN(c)?o.popupCloseDelay:c}function y(b){N&&angular.isFunction(N.assign)&&N.assign(a,b)}function z(){P.length=0,O?(P.push(a.$watch(O,function(a){L.content=a,!a&&L.isOpen&&t()})),P.push(D.$watch(function(){M||(M=!0,D.$$postDigest(function(){M=!1,L&&L.isOpen&&Q()}))}))):P.push(d.$observe(e,function(a){L.content=a,!a&&L.isOpen?t():Q()})),P.push(d.$observe(k+"Title",function(a){L.title=a,L.isOpen&&Q()})),P.push(d.$observe(k+"Placement",function(a){L.placement=a?a:o.placement,L.isOpen&&Q()}))}function A(){P.length&&(angular.forEach(P,function(a){a()}),P.length=0)}function B(){var a=d[k+"Trigger"];R(),J=p(a),"none"!==J.show&&J.show.forEach(function(a,c){a===J.hide[c]?b[0].addEventListener(a,j):a&&(b[0].addEventListener(a,m),J.hide[c].split(" ").forEach(function(a){b[0].addEventListener(a,q)})),b.on("keypress",function(a){27===a.which&&q()})})}var C,D,E,F,G,H,I=angular.isDefined(o.appendToBody)?o.appendToBody:!1,J=p(void 0),K=angular.isDefined(d[k+"Enable"]),L=a.$new(!0),M=!1,N=angular.isDefined(d[k+"IsOpen"])?l(d[k+"IsOpen"]):!1,O=o.useContentExp?l(d[e]):!1,P=[],Q=function(){C&&C.html()&&(H||(H=g(function(){C.css({top:0,left:0});var a=i.positionElements(b,C,L.placement,I);a.top+="px",a.left+="px",a.visibility="visible",C.css(a),H=null},0,!1)))};L.origScope=a,L.isOpen=!1,n.add(L,{close:t}),L.contentExp=function(){return L.content},d.$observe("disabled",function(a){a&&s(),a&&L.isOpen&&t()}),N&&a.$watch(N,function(a){L&&!a===L.isOpen&&j()});var R=function(){J.show.forEach(function(a){b.unbind(a,m)}),J.hide.forEach(function(a){a.split(" ").forEach(function(a){b[0].removeEventListener(a,q)})})};B();var S=a.$eval(d[k+"Animation"]);L.animation=angular.isDefined(S)?!!S:o.animation;var T=a.$eval(d[k+"AppendToBody"]);I=angular.isDefined(T)?T:I,I&&a.$on("$locationChangeSuccess",function(){L.isOpen&&t()}),a.$on("$destroy",function(){s(),u(),R(),w(),n.remove(L),L=null})}}}}}]}).directive("uibTooltipTemplateTransclude",["$animate","$sce","$compile","$templateRequest",function(a,b,c,d){return{link:function(e,f,g){var h,i,j,k=e.$eval(g.tooltipTemplateTranscludeScope),l=0,m=function(){i&&(i.remove(),i=null),h&&(h.$destroy(),h=null),j&&(a.leave(j).then(function(){i=null}),i=j,j=null)};e.$watch(b.parseAsResourceUrl(g.uibTooltipTemplateTransclude),function(b){var g=++l;b?(d(b,!0).then(function(d){if(g===l){var e=k.$new(),i=d,n=c(i)(e,function(b){m(),a.enter(b,f)});h=e,j=n,h.$emit("$includeContentLoaded",b)}},function(){g===l&&(m(),e.$emit("$includeContentError",b))}),e.$emit("$includeContentRequested",b)):m()}),e.$on("$destroy",m)}}}]).directive("uibTooltipClasses",function(){return{restrict:"A",link:function(a,b,c){a.placement&&b.addClass(a.placement),a.popupClass&&b.addClass(a.popupClass),a.animation()&&b.addClass(c.tooltipAnimationClass)}}}).directive("uibTooltipPopup",function(){return{replace:!0,scope:{content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html",link:function(a,b){b.addClass("tooltip")}}}).directive("uibTooltip",["$uibTooltip",function(a){return a("uibTooltip","tooltip","mouseenter")}]).directive("uibTooltipTemplatePopup",function(){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"template/tooltip/tooltip-template-popup.html",link:function(a,b){b.addClass("tooltip")}}}).directive("uibTooltipTemplate",["$uibTooltip",function(a){return a("uibTooltipTemplate","tooltip","mouseenter",{useContentExp:!0})}]).directive("uibTooltipHtmlPopup",function(){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-popup.html",link:function(a,b){b.addClass("tooltip")}}}).directive("uibTooltipHtml",["$uibTooltip",function(a){return a("uibTooltipHtml","tooltip","mouseenter",{useContentExp:!0})}]),angular.module("ui.bootstrap.tooltip").value("$tooltipSuppressWarning",!1).provider("$tooltip",["$uibTooltipProvider",function(a){angular.extend(this,a),this.$get=["$log","$tooltipSuppressWarning","$injector",function(b,c,d){return c||b.warn("$tooltip is now deprecated. Use $uibTooltip instead."),d.invoke(a.$get)}]}]).directive("tooltipTemplateTransclude",["$animate","$sce","$compile","$templateRequest","$log","$tooltipSuppressWarning",function(a,b,c,d,e,f){return{link:function(g,h,i){f||e.warn("tooltip-template-transclude is now deprecated. Use uib-tooltip-template-transclude instead.");var j,k,l,m=g.$eval(i.tooltipTemplateTranscludeScope),n=0,o=function(){k&&(k.remove(),k=null),j&&(j.$destroy(),j=null),l&&(a.leave(l).then(function(){k=null}),k=l,l=null)};g.$watch(b.parseAsResourceUrl(i.tooltipTemplateTransclude),function(b){var e=++n;b?(d(b,!0).then(function(d){if(e===n){var f=m.$new(),g=d,i=c(g)(f,function(b){o(),a.enter(b,h)});j=f,l=i,j.$emit("$includeContentLoaded",b)}},function(){e===n&&(o(),g.$emit("$includeContentError",b))}),g.$emit("$includeContentRequested",b)):o()}),g.$on("$destroy",o)}}}]).directive("tooltipClasses",["$log","$tooltipSuppressWarning",function(a,b){return{restrict:"A",link:function(c,d,e){b||a.warn("tooltip-classes is now deprecated. Use uib-tooltip-classes instead."),c.placement&&d.addClass(c.placement),c.popupClass&&d.addClass(c.popupClass),c.animation()&&d.addClass(e.tooltipAnimationClass)}}}]).directive("tooltipPopup",["$log","$tooltipSuppressWarning",function(a,b){return{replace:!0,scope:{content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html",link:function(c,d){b||a.warn("tooltip-popup is now deprecated. Use uib-tooltip-popup instead."),d.addClass("tooltip")}}}]).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipTemplatePopup",["$log","$tooltipSuppressWarning",function(a,b){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"template/tooltip/tooltip-template-popup.html",link:function(c,d){b||a.warn("tooltip-template-popup is now deprecated. Use uib-tooltip-template-popup instead."),d.addClass("tooltip")}}}]).directive("tooltipTemplate",["$tooltip",function(a){return a("tooltipTemplate","tooltip","mouseenter",{useContentExp:!0})}]).directive("tooltipHtmlPopup",["$log","$tooltipSuppressWarning",function(a,b){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-popup.html",link:function(c,d){b||a.warn("tooltip-html-popup is now deprecated. Use uib-tooltip-html-popup instead."),d.addClass("tooltip")}}}]).directive("tooltipHtml",["$tooltip",function(a){return a("tooltipHtml","tooltip","mouseenter",{useContentExp:!0})}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("uibPopoverTemplatePopup",function(){return{replace:!0,scope:{title:"@",contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"template/popover/popover-template.html",link:function(a,b){b.addClass("popover")}}}).directive("uibPopoverTemplate",["$uibTooltip",function(a){return a("uibPopoverTemplate","popover","click",{useContentExp:!0})}]).directive("uibPopoverHtmlPopup",function(){return{replace:!0,scope:{contentExp:"&",title:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover-html.html",link:function(a,b){b.addClass("popover")}}}).directive("uibPopoverHtml",["$uibTooltip",function(a){return a("uibPopoverHtml","popover","click",{useContentExp:!0})}]).directive("uibPopoverPopup",function(){return{replace:!0,scope:{title:"@",content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html",link:function(a,b){b.addClass("popover")}}}).directive("uibPopover",["$uibTooltip",function(a){return a("uibPopover","popover","click")}]),angular.module("ui.bootstrap.popover").value("$popoverSuppressWarning",!1).directive("popoverTemplatePopup",["$log","$popoverSuppressWarning",function(a,b){return{replace:!0,scope:{title:"@",contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"template/popover/popover-template.html",link:function(c,d){b||a.warn("popover-template-popup is now deprecated. Use uib-popover-template-popup instead."),d.addClass("popover")}}}]).directive("popoverTemplate",["$tooltip",function(a){return a("popoverTemplate","popover","click",{useContentExp:!0})}]).directive("popoverHtmlPopup",["$log","$popoverSuppressWarning",function(a,b){return{replace:!0,scope:{contentExp:"&",title:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover-html.html",link:function(c,d){b||a.warn("popover-html-popup is now deprecated. Use uib-popover-html-popup instead."),d.addClass("popover")}}}]).directive("popoverHtml",["$tooltip",function(a){return a("popoverHtml","popover","click",{useContentExp:!0})}]).directive("popoverPopup",["$log","$popoverSuppressWarning",function(a,b){return{replace:!0,scope:{title:"@",content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html",link:function(c,d){b||a.warn("popover-popup is now deprecated. Use uib-popover-popup instead."),d.addClass("popover")}}}]).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("uibProgressConfig",{animate:!0,max:100}).controller("UibProgressController",["$scope","$attrs","uibProgressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(a.max)?a.max:c.max,this.addBar=function(b,c,f){e||c.css({transition:"none"}),this.bars.push(b),b.max=a.max,b.title=f&&angular.isDefined(f.title)?f.title:"progressbar",b.$watch("value",function(a){b.recalculatePercentage()}),b.recalculatePercentage=function(){var a=d.bars.reduce(function(a,b){return b.percent=+(100*b.value/b.max).toFixed(2),a+b.percent},0);a>100&&(b.percent-=a-100)},b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1),this.bars.forEach(function(a){a.recalculatePercentage()})},a.$watch("max",function(b){d.bars.forEach(function(b){b.max=a.max,b.recalculatePercentage()})})}]).directive("uibProgress",function(){return{replace:!0,transclude:!0,controller:"UibProgressController",require:"uibProgress",scope:{max:"=?"},templateUrl:"template/progressbar/progress.html"}}).directive("uibBar",function(){return{replace:!0,transclude:!0,require:"^uibProgress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b,c)}}}).directive("uibProgressbar",function(){return{replace:!0,transclude:!0,controller:"UibProgressController",scope:{value:"=",max:"=?",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]),{title:c.title})}}}),angular.module("ui.bootstrap.progressbar").value("$progressSuppressWarning",!1).controller("ProgressController",["$scope","$attrs","uibProgressConfig","$log","$progressSuppressWarning",function(a,b,c,d,e){e||d.warn("ProgressController is now deprecated. Use UibProgressController instead.");var f=this,g=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(a.max)?a.max:c.max,this.addBar=function(b,c,d){g||c.css({transition:"none"}),this.bars.push(b),b.max=a.max,b.title=d&&angular.isDefined(d.title)?d.title:"progressbar",b.$watch("value",function(a){b.recalculatePercentage()}),b.recalculatePercentage=function(){b.percent=+(100*b.value/b.max).toFixed(2);var a=f.bars.reduce(function(a,b){return a+b.percent},0);a>100&&(b.percent-=a-100)},b.$on("$destroy",function(){c=null,f.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)},a.$watch("max",function(b){f.bars.forEach(function(b){b.max=a.max,b.recalculatePercentage()})})}]).directive("progress",["$log","$progressSuppressWarning",function(a,b){return{replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{max:"=?",title:"@?"},templateUrl:"template/progressbar/progress.html",link:function(){b||a.warn("progress is now deprecated. Use uib-progress instead.")}}}]).directive("bar",["$log","$progressSuppressWarning",function(a,b){return{replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(c,d,e,f){b||a.warn("bar is now deprecated. Use uib-bar instead."),f.addBar(c,d)}}}]).directive("progressbar",["$log","$progressSuppressWarning",function(a,b){return{replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",max:"=?",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(c,d,e,f){b||a.warn("progressbar is now deprecated. Use uib-progressbar instead."),f.addBar(c,angular.element(d.children()[0]),{title:e.title})}}}]),angular.module("ui.bootstrap.rating",[]).constant("uibRatingConfig",{max:5,stateOn:null,stateOff:null,titles:["one","two","three","four","five"]}).controller("UibRatingController",["$scope","$attrs","uibRatingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,d.$formatters.push(function(a){return angular.isNumber(a)&&a<<0!==a&&(a=Math.round(a)),a}),this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.titles)?a.$parent.$eval(b.titles):c.titles;this.titles=angular.isArray(f)&&f.length>0?f:c.titles;var g=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(g)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff,title:this.getTitle(b)},a[b]);return a},this.getTitle=function(a){return a>=this.titles.length?a+1:this.titles[a]},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(d.$viewValue===b?0:b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("uibRating",function(){return{require:["uibRating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"UibRatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}),angular.module("ui.bootstrap.rating").value("$ratingSuppressWarning",!1).controller("RatingController",["$scope","$attrs","$controller","$log","$ratingSuppressWarning",function(a,b,c,d,e){e||d.warn("RatingController is now deprecated. Use UibRatingController instead."),angular.extend(this,c("UibRatingController",{$scope:a,$attrs:b}))}]).directive("rating",["$log","$ratingSuppressWarning",function(a,b){return{require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(c,d,e,f){b||a.warn("rating is now deprecated. Use uib-rating instead.");var g=f[0],h=f[1];g.init(h)}}}]),angular.module("ui.bootstrap.tabs",[]).controller("UibTabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect(),a.selectCalled=!1)}),a.active=!0,a.selectCalled||(a.onSelect(),a.selectCalled=!0)},b.addTab=function(a){c.push(a),1===c.length&&a.active!==!1?a.active=!0:a.active?b.select(a):a.active=!1},b.removeTab=function(a){var e=c.indexOf(a);if(a.active&&c.length>1&&!d){var f=e==c.length-1?e-1:e+1;b.select(c[f])}c.splice(e,1)};var d;a.$on("$destroy",function(){d=!0})}]).directive("uibTabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"UibTabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("uibTab",["$parse",function(a){return{require:"^uibTabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},link:function(b,c,d,e,f){b.$watch("active",function(a){a&&e.select(b)}),b.disabled=!1,d.disable&&b.$parent.$watch(a(d.disable),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},e.addTab(b),b.$on("$destroy",function(){e.removeTab(b)}),b.$transcludeFn=f}}}]).directive("uibTabHeadingTransclude",function(){return{restrict:"A",require:["?^uibTab","?^tab"],link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}).directive("uibTabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||a.hasAttribute("x-tab-heading")||a.hasAttribute("uib-tab-heading")||a.hasAttribute("data-uib-tab-heading")||a.hasAttribute("x-uib-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase()||"x-tab-heading"===a.tagName.toLowerCase()||"uib-tab-heading"===a.tagName.toLowerCase()||"data-uib-tab-heading"===a.tagName.toLowerCase()||"x-uib-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:["?^uibTabset","?^tabset"],link:function(b,c,d){var e=b.$eval(d.uibTabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.tabs").value("$tabsSuppressWarning",!1).controller("TabsetController",["$scope","$controller","$log","$tabsSuppressWarning",function(a,b,c,d){d||c.warn("TabsetController is now deprecated. Use UibTabsetController instead."),angular.extend(this,b("UibTabsetController",{$scope:a}))}]).directive("tabset",["$log","$tabsSuppressWarning",function(a,b){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(c,d,e){b||a.warn("tabset is now deprecated. Use uib-tabset instead."),c.vertical=angular.isDefined(e.vertical)?c.$parent.$eval(e.vertical):!1,c.justified=angular.isDefined(e.justified)?c.$parent.$eval(e.justified):!1}}}]).directive("tab",["$parse","$log","$tabsSuppressWarning",function(a,b,c){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},link:function(d,e,f,g,h){c||b.warn("tab is now deprecated. Use uib-tab instead."),d.$watch("active",function(a){a&&g.select(d)}),d.disabled=!1,f.disable&&d.$parent.$watch(a(f.disable),function(a){d.disabled=!!a}),d.select=function(){d.disabled||(d.active=!0)},g.addTab(d),d.$on("$destroy",function(){g.removeTab(d)}),d.$transcludeFn=h}}}]).directive("tabHeadingTransclude",["$log","$tabsSuppressWarning",function(a,b){return{restrict:"A",require:"^tab",link:function(c,d){b||a.warn("tab-heading-transclude is now deprecated. Use uib-tab-heading-transclude instead."),c.$watch("headingElement",function(a){a&&(d.html(""),d.append(a))})}}}]).directive("tabContentTransclude",["$log","$tabsSuppressWarning",function(a,b){function c(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||a.hasAttribute("x-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase()||"x-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(d,e,f){b||a.warn("tab-content-transclude is now deprecated. Use uib-tab-content-transclude instead.");var g=d.$eval(f.tabContentTransclude);g.$transcludeFn(g.$parent,function(a){angular.forEach(a,function(a){c(a)?g.headingElement=a:e.append(a)})})}}}]),angular.module("ui.bootstrap.timepicker",[]).constant("uibTimepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0,arrowkeys:!0,showSpinners:!0}).controller("UibTimepickerController",["$scope","$element","$attrs","$parse","$log","$locale","uibTimepickerConfig",function(a,b,c,d,e,f,g){function h(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===r[1]&&(b+=12)),b):void 0}function i(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function j(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a.toString()}function k(a){l(),q.$setViewValue(new Date(p)),m(a)}function l(){q.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function m(b){var c=p.getHours(),d=p.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:j(c),"m"!==b&&(a.minutes=j(d)),a.meridian=p.getHours()<12?r[0]:r[1]}function n(a,b){var c=new Date(a.getTime()+6e4*b),d=new Date(a);return d.setHours(c.getHours(),c.getMinutes()),d}function o(a){p=n(p,a),k()}var p=new Date,q={$setViewValue:angular.noop},r=angular.isDefined(c.meridians)?a.$parent.$eval(c.meridians):g.meridians||f.DATETIME_FORMATS.AMPMS;a.tabindex=angular.isDefined(c.tabindex)?c.tabindex:0,b.removeAttr("tabindex"),this.init=function(b,d){q=b,q.$render=this.render,q.$formatters.unshift(function(a){return a?new Date(a):null});var e=d.eq(0),f=d.eq(1),h=angular.isDefined(c.mousewheel)?a.$parent.$eval(c.mousewheel):g.mousewheel;h&&this.setupMousewheelEvents(e,f);var i=angular.isDefined(c.arrowkeys)?a.$parent.$eval(c.arrowkeys):g.arrowkeys;i&&this.setupArrowkeyEvents(e,f),a.readonlyInput=angular.isDefined(c.readonlyInput)?a.$parent.$eval(c.readonlyInput):g.readonlyInput,this.setupInputEvents(e,f)};var s=g.hourStep;c.hourStep&&a.$parent.$watch(d(c.hourStep),function(a){s=parseInt(a,10)});var t=g.minuteStep;c.minuteStep&&a.$parent.$watch(d(c.minuteStep),function(a){t=parseInt(a,10)});var u;a.$parent.$watch(d(c.min),function(a){var b=new Date(a);u=isNaN(b)?void 0:b});var v;a.$parent.$watch(d(c.max),function(a){var b=new Date(a);v=isNaN(b)?void 0:b}),a.noIncrementHours=function(){var a=n(p,60*s); +return a>v||p>a&&u>a},a.noDecrementHours=function(){var a=n(p,60*-s);return u>a||a>p&&a>v},a.noIncrementMinutes=function(){var a=n(p,t);return a>v||p>a&&u>a},a.noDecrementMinutes=function(){var a=n(p,-t);return u>a||a>p&&a>v},a.noToggleMeridian=function(){return p.getHours()<13?n(p,720)>v:n(p,-720)0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupArrowkeyEvents=function(b,c){b.bind("keydown",function(b){38===b.which?(b.preventDefault(),a.incrementHours(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementHours(),a.$apply())}),c.bind("keydown",function(b){38===b.which?(b.preventDefault(),a.incrementMinutes(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementMinutes(),a.$apply())})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){q.$setViewValue(null),q.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=h(),b=i();angular.isDefined(a)&&angular.isDefined(b)?(p.setHours(a),u>p||p>v?d(!0):k("h")):d(!0)},b.bind("blur",function(b){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=j(a.hours)})}),a.updateMinutes=function(){var a=i(),b=h();angular.isDefined(a)&&angular.isDefined(b)?(p.setMinutes(a),u>p||p>v?d(void 0,!0):k("m")):d(void 0,!0)},c.bind("blur",function(b){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=j(a.minutes)})})},this.render=function(){var b=q.$viewValue;isNaN(b)?(q.$setValidity("time",!1),e.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(b&&(p=b),u>p||p>v?(q.$setValidity("time",!1),a.invalidHours=!0,a.invalidMinutes=!0):l(),m())},a.showSpinners=angular.isDefined(c.showSpinners)?a.$parent.$eval(c.showSpinners):g.showSpinners,a.incrementHours=function(){a.noIncrementHours()||o(60*s)},a.decrementHours=function(){a.noDecrementHours()||o(60*-s)},a.incrementMinutes=function(){a.noIncrementMinutes()||o(t)},a.decrementMinutes=function(){a.noDecrementMinutes()||o(-t)},a.toggleMeridian=function(){a.noToggleMeridian()||o(720*(p.getHours()<12?1:-1))}}]).directive("uibTimepicker",function(){return{restrict:"EA",require:["uibTimepicker","?^ngModel"],controller:"UibTimepickerController",controllerAs:"timepicker",replace:!0,scope:{},templateUrl:function(a,b){return b.templateUrl||"template/timepicker/timepicker.html"},link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.timepicker").value("$timepickerSuppressWarning",!1).controller("TimepickerController",["$scope","$element","$attrs","$controller","$log","$timepickerSuppressWarning",function(a,b,c,d,e,f){f||e.warn("TimepickerController is now deprecated. Use UibTimepickerController instead."),angular.extend(this,d("UibTimepickerController",{$scope:a,$element:b,$attrs:c}))}]).directive("timepicker",["$log","$timepickerSuppressWarning",function(a,b){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",controllerAs:"timepicker",replace:!0,scope:{},templateUrl:function(a,b){return b.templateUrl||"template/timepicker/timepicker.html"},link:function(c,d,e,f){b||a.warn("timepicker is now deprecated. Use uib-timepicker instead.");var g=f[0],h=f[1];h&&g.init(h,d.find("input"))}}}]),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position"]).factory("uibTypeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).controller("UibTypeaheadController",["$scope","$element","$attrs","$compile","$parse","$q","$timeout","$document","$window","$rootScope","$uibPosition","uibTypeaheadParser",function(a,b,c,d,e,f,g,h,i,j,k,l){function m(){K.moveInProgress||(K.moveInProgress=!0,K.$digest()),S&&g.cancel(S),S=g(function(){K.matches.length&&n(),K.moveInProgress=!1},r)}function n(){K.position=C?k.offset(b):k.position(b),K.position.top+=b.prop("offsetHeight")}var o,p,q=[9,13,27,38,40],r=200,s=a.$eval(c.typeaheadMinLength);s||0===s||(s=1);var t,u,v=a.$eval(c.typeaheadWaitMs)||0,w=a.$eval(c.typeaheadEditable)!==!1,x=e(c.typeaheadLoading).assign||angular.noop,y=e(c.typeaheadOnSelect),z=angular.isDefined(c.typeaheadSelectOnBlur)?a.$eval(c.typeaheadSelectOnBlur):!1,A=e(c.typeaheadNoResults).assign||angular.noop,B=c.typeaheadInputFormatter?e(c.typeaheadInputFormatter):void 0,C=c.typeaheadAppendToBody?a.$eval(c.typeaheadAppendToBody):!1,D=c.typeaheadAppendToElementId||!1,E=a.$eval(c.typeaheadFocusFirst)!==!1,F=c.typeaheadSelectOnExact?a.$eval(c.typeaheadSelectOnExact):!1,G=e(c.ngModel),H=e(c.ngModel+"($$$p)"),I=function(b,c){return angular.isFunction(G(a))&&p&&p.$options&&p.$options.getterSetter?H(b,{$$$p:c}):G.assign(b,c)},J=l.parse(c.uibTypeahead),K=a.$new(),L=a.$on("$destroy",function(){K.$destroy()});K.$on("$destroy",L);var M="typeahead-"+K.$id+"-"+Math.floor(1e4*Math.random());b.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":M});var N=angular.element("
      ");N.attr({id:M,matches:"matches",active:"activeIdx",select:"select(activeIdx)","move-in-progress":"moveInProgress",query:"query",position:"position"}),angular.isDefined(c.typeaheadTemplateUrl)&&N.attr("template-url",c.typeaheadTemplateUrl),angular.isDefined(c.typeaheadPopupTemplateUrl)&&N.attr("popup-template-url",c.typeaheadPopupTemplateUrl);var O=function(){K.matches=[],K.activeIdx=-1,b.attr("aria-expanded",!1)},P=function(a){return M+"-option-"+a};K.$watch("activeIdx",function(a){0>a?b.removeAttr("aria-activedescendant"):b.attr("aria-activedescendant",P(a))});var Q=function(a,b){return K.matches.length>b&&a?a.toUpperCase()===K.matches[b].label.toUpperCase():!1},R=function(c){var d={$viewValue:c};x(a,!0),A(a,!1),f.when(J.source(a,d)).then(function(e){var f=c===o.$viewValue;if(f&&t)if(e&&e.length>0){K.activeIdx=E?0:-1,A(a,!1),K.matches.length=0;for(var g=0;g0?K.activeIdx:K.matches.length)-1,K.$digest()):13===a.which||9===a.which?K.$apply(function(){K.select(K.activeIdx)}):27===a.which&&(a.stopPropagation(),O(),K.$digest())}}),b.bind("blur",function(){z&&K.matches.length&&-1!==K.activeIdx&&!u&&(u=!0,K.$apply(function(){K.select(K.activeIdx)})),t=!1,u=!1});var W=function(a){b[0]!==a.target&&3!==a.which&&0!==K.matches.length&&(O(),j.$$phase||K.$digest())};h.bind("click",W),a.$on("$destroy",function(){h.unbind("click",W),(C||D)&&X.remove(),C&&(angular.element(i).unbind("resize",m),h.find("body").unbind("scroll",m)),N.remove()});var X=d(N)(K);C?h.find("body").append(X):D!==!1?angular.element(h[0].getElementById(D)).append(X):b.after(X),this.init=function(b,c){o=b,p=c,o.$parsers.unshift(function(b){return t=!0,0===s||b&&b.length>=s?v>0?(V(),U(b)):R(b):(x(a,!1),V(),O()),w?b:b?void o.$setValidity("editable",!1):(o.$setValidity("editable",!0),null)}),o.$formatters.push(function(b){var c,d,e={};return w||o.$setValidity("editable",!0),B?(e.$model=b,B(a,e)):(e[J.itemName]=b,c=J.viewMapper(a,e),e[J.itemName]=void 0,d=J.viewMapper(a,e),c!==d?c:b)})}}]).directive("uibTypeahead",function(){return{controller:"UibTypeaheadController",require:["ngModel","^?ngModelOptions","uibTypeahead"],link:function(a,b,c,d){d[2].init(d[0],d[1])}}}).directive("uibTypeaheadPopup",function(){return{scope:{matches:"=",query:"=",active:"=",position:"&",moveInProgress:"=",select:"&"},replace:!0,templateUrl:function(a,b){return b.popupTemplateUrl||"template/typeahead/typeahead-popup.html"},link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("uibTypeaheadMatch",["$templateRequest","$compile","$parse",function(a,b,c){return{scope:{index:"=",match:"=",query:"="},link:function(d,e,f){var g=c(f.templateUrl)(d.$parent)||"template/typeahead/typeahead-match.html";a(g).then(function(a){b(a.trim())(d,function(a){e.replaceWith(a)})})}}}]).filter("uibTypeaheadHighlight",["$sce","$injector","$log",function(a,b,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}function e(a){return/<.*>/g.test(a)}var f;return f=b.has("$sanitize"),function(b,g){return!f&&e(b)&&c.warn("Unsafe use of typeahead please use ngSanitize"),b=g?(""+b).replace(new RegExp(d(g),"gi"),"$&"):b,f||(b=a.trustAsHtml(b)),b}}]),angular.module("ui.bootstrap.typeahead").value("$typeaheadSuppressWarning",!1).service("typeaheadParser",["$parse","uibTypeaheadParser","$log","$typeaheadSuppressWarning",function(a,b,c,d){return d||c.warn("typeaheadParser is now deprecated. Use uibTypeaheadParser instead."),b}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$window","$rootScope","$uibPosition","typeaheadParser","$log","$typeaheadSuppressWarning",function(a,b,c,d,e,f,g,h,i,j,k){var l=[9,13,27,38,40],m=200;return{require:["ngModel","^?ngModelOptions"],link:function(n,o,p,q){function r(){N.moveInProgress||(N.moveInProgress=!0,N.$digest()),V&&d.cancel(V),V=d(function(){N.matches.length&&s(),N.moveInProgress=!1},m)}function s(){N.position=F?h.offset(o):h.position(o),N.position.top+=o.prop("offsetHeight")}k||j.warn("typeahead is now deprecated. Use uib-typeahead instead.");var t=q[0],u=q[1],v=n.$eval(p.typeaheadMinLength);v||0===v||(v=1);var w,x,y=n.$eval(p.typeaheadWaitMs)||0,z=n.$eval(p.typeaheadEditable)!==!1,A=b(p.typeaheadLoading).assign||angular.noop,B=b(p.typeaheadOnSelect),C=angular.isDefined(p.typeaheadSelectOnBlur)?n.$eval(p.typeaheadSelectOnBlur):!1,D=b(p.typeaheadNoResults).assign||angular.noop,E=p.typeaheadInputFormatter?b(p.typeaheadInputFormatter):void 0,F=p.typeaheadAppendToBody?n.$eval(p.typeaheadAppendToBody):!1,G=p.typeaheadAppendToElementId||!1,H=n.$eval(p.typeaheadFocusFirst)!==!1,I=p.typeaheadSelectOnExact?n.$eval(p.typeaheadSelectOnExact):!1,J=b(p.ngModel),K=b(p.ngModel+"($$$p)"),L=function(a,b){return angular.isFunction(J(n))&&u&&u.$options&&u.$options.getterSetter?K(a,{$$$p:b}):J.assign(a,b)},M=i.parse(p.typeahead),N=n.$new(),O=n.$on("$destroy",function(){N.$destroy()});N.$on("$destroy",O);var P="typeahead-"+N.$id+"-"+Math.floor(1e4*Math.random());o.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":P});var Q=angular.element("
      ");Q.attr({id:P,matches:"matches",active:"activeIdx",select:"select(activeIdx)","move-in-progress":"moveInProgress",query:"query",position:"position"}),angular.isDefined(p.typeaheadTemplateUrl)&&Q.attr("template-url",p.typeaheadTemplateUrl),angular.isDefined(p.typeaheadPopupTemplateUrl)&&Q.attr("popup-template-url",p.typeaheadPopupTemplateUrl);var R=function(){N.matches=[],N.activeIdx=-1,o.attr("aria-expanded",!1)},S=function(a){return P+"-option-"+a};N.$watch("activeIdx",function(a){0>a?o.removeAttr("aria-activedescendant"):o.attr("aria-activedescendant",S(a))});var T=function(a,b){return N.matches.length>b&&a?a.toUpperCase()===N.matches[b].label.toUpperCase():!1},U=function(a){var b={$viewValue:a};A(n,!0),D(n,!1),c.when(M.source(n,b)).then(function(c){var d=a===t.$viewValue;if(d&&w)if(c&&c.length>0){N.activeIdx=H?0:-1,D(n,!1),N.matches.length=0;for(var e=0;e=v?y>0?(Y(),X(a)):U(a):(A(n,!1),Y(),R()),z?a:a?void t.$setValidity("editable",!1):(t.$setValidity("editable",!0),null)}),t.$formatters.push(function(a){var b,c,d={};return z||t.$setValidity("editable",!0),E?(d.$model=a,E(n,d)):(d[M.itemName]=a,b=M.viewMapper(n,d),d[M.itemName]=void 0,c=M.viewMapper(n,d),b!==c?b:a)}),N.select=function(a){var b,c,e={};x=!0,e[M.itemName]=c=N.matches[a].model,b=M.modelMapper(n,e),L(n,b),t.$setValidity("editable",!0),t.$setValidity("parse",!0),B(n,{$item:c,$model:b,$label:M.viewMapper(n,e)}),R(),N.$eval(p.typeaheadFocusOnSelect)!==!1&&d(function(){o[0].focus()},0,!1)},o.bind("keydown",function(a){if(0!==N.matches.length&&-1!==l.indexOf(a.which)){if(-1===N.activeIdx&&(9===a.which||13===a.which))return R(),void N.$digest();a.preventDefault(),40===a.which?(N.activeIdx=(N.activeIdx+1)%N.matches.length,N.$digest()):38===a.which?(N.activeIdx=(N.activeIdx>0?N.activeIdx:N.matches.length)-1,N.$digest()):13===a.which||9===a.which?N.$apply(function(){N.select(N.activeIdx)}):27===a.which&&(a.stopPropagation(),R(),N.$digest())}}),o.bind("blur",function(){C&&N.matches.length&&-1!==N.activeIdx&&!x&&(x=!0,N.$apply(function(){N.select(N.activeIdx)})),w=!1,x=!1});var Z=function(a){o[0]!==a.target&&3!==a.which&&0!==N.matches.length&&(R(),g.$$phase||N.$digest())};e.bind("click",Z),n.$on("$destroy",function(){e.unbind("click",Z),(F||G)&&$.remove(),F&&(angular.element(f).unbind("resize",r),e.find("body").unbind("scroll",r)),Q.remove()});var $=a(Q)(N);F?e.find("body").append($):G!==!1?angular.element(e[0].getElementById(G)).append($):o.after($)}}}]).directive("typeaheadPopup",["$typeaheadSuppressWarning","$log",function(a,b){return{scope:{matches:"=",query:"=",active:"=",position:"&",moveInProgress:"=",select:"&"},replace:!0,templateUrl:function(a,b){return b.popupTemplateUrl||"template/typeahead/typeahead-popup.html"},link:function(c,d,e){a||b.warn("typeahead-popup is now deprecated. Use uib-typeahead-popup instead."),c.templateUrl=e.templateUrl,c.isOpen=function(){return c.matches.length>0},c.isActive=function(a){return c.active==a},c.selectActive=function(a){c.active=a},c.selectMatch=function(a){c.select({activeIdx:a})}}}}]).directive("typeaheadMatch",["$templateRequest","$compile","$parse","$typeaheadSuppressWarning","$log",function(a,b,c,d,e){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(f,g,h){d||e.warn("typeahead-match is now deprecated. Use uib-typeahead-match instead.");var i=c(h.templateUrl)(f.$parent)||"template/typeahead/typeahead-match.html";a(i).then(function(a){b(a.trim())(f,function(a){g.replaceWith(a)})})}}}]).filter("typeaheadHighlight",["$sce","$injector","$log","$typeaheadSuppressWarning",function(a,b,c,d){function e(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}function f(a){return/<.*>/g.test(a)}var g;return g=b.has("$sanitize"),function(b,h){return d||c.warn("typeaheadHighlight is now deprecated. Use uibTypeaheadHighlight instead."),!g&&f(b)&&c.warn("Unsafe use of typeahead please use ngSanitize"),b=h?(""+b).replace(new RegExp(e(h),"gi"),"$&"):b,g||(b=a.trustAsHtml(b)),b}}]),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
      \n
      \n

      \n {{heading}}\n

      \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
      ')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html",'\n')}]),angular.module("template/carousel/carousel.html",[]).run(["$templateCache",function(a){a.put("template/carousel/carousel.html",'')}]),angular.module("template/carousel/slide.html",[]).run(["$templateCache",function(a){a.put("template/carousel/slide.html",'
      \n')}]),angular.module("template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/datepicker.html",'
      \n \n \n \n
      ')}]),angular.module("template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
      {{::label.abbr}}
      {{ weekNumbers[$index] }}\n \n
      \n')}]),angular.module("template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
      \n \n
      \n')}]),angular.module("template/datepicker/popup.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/popup.html",'\n')}]),angular.module("template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
      \n \n
      \n')}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'
      \n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'\n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-popup.html",'\n
      \n
      \n
    \n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'\n
    \n
    \n
    \n')}]),angular.module("template/tooltip/tooltip-template-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-template-popup.html",'\n
    \n
    \n
    \n')}]),angular.module("template/popover/popover-html.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover-html.html",'
    \n
    \n\n
    \n

    \n
    \n
    \n
    \n')}]),angular.module("template/popover/popover-template.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover-template.html",'
    \n
    \n\n
    \n

    \n
    \n
    \n
    \n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
    \n
    \n\n
    \n

    \n
    \n
    \n
    \n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'
    \n')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
    ')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'
    \n
    \n
    \n')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n ({{ $index < value ? \'*\' : \' \' }})\n \n\n'); +}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'
  • \n {{heading}}\n
  • \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'
    \n \n
    \n
    \n
    \n
    \n
    \n')}]),angular.module("template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("template/timepicker/timepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
     
    \n \n :\n \n
     
    \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'\n')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html",'\n')}]),!angular.$$csp()&&angular.element(document).find("head").prepend(''); \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap/ui-bootstrap.js b/vendor/assets/components/angular-bootstrap/ui-bootstrap.js index d0f02df98..9287bb546 100644 --- a/vendor/assets/components/angular-bootstrap/ui-bootstrap.js +++ b/vendor/assets/components/angular-bootstrap/ui-bootstrap.js @@ -2,159 +2,160 @@ * angular-ui-bootstrap * http://angular-ui.github.io/bootstrap/ - * Version: 0.12.1 - 2015-02-20 + * Version: 0.14.3 - 2015-10-23 * License: MIT */ -angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); -angular.module('ui.bootstrap.transition', []) - -/** - * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. - * @param {DOMElement} element The DOMElement that will be animated. - * @param {string|object|function} trigger The thing that will cause the transition to start: - * - As a string, it represents the css class to be added to the element. - * - As an object, it represents a hash of style attributes to be applied to the element. - * - As a function, it represents a function to be called that will cause the transition to occur. - * @return {Promise} A promise that is resolved when the transition finishes. - */ -.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { - - var $transition = function(element, trigger, options) { - options = options || {}; - var deferred = $q.defer(); - var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; - - var transitionEndHandler = function(event) { - $rootScope.$apply(function() { - element.unbind(endEventName, transitionEndHandler); - deferred.resolve(element); - }); - }; - - if (endEventName) { - element.bind(endEventName, transitionEndHandler); - } - - // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur - $timeout(function() { - if ( angular.isString(trigger) ) { - element.addClass(trigger); - } else if ( angular.isFunction(trigger) ) { - trigger(element); - } else if ( angular.isObject(trigger) ) { - element.css(trigger); - } - //If browser does not support transitions, instantly resolve - if ( !endEventName ) { - deferred.resolve(element); - } - }); - - // Add our custom cancel function to the promise that is returned - // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, - // i.e. it will therefore never raise a transitionEnd event for that transition - deferred.promise.cancel = function() { - if ( endEventName ) { - element.unbind(endEventName, transitionEndHandler); - } - deferred.reject('Transition cancelled'); - }; - - return deferred.promise; - }; - - // Work out the name of the transitionEnd event - var transElement = document.createElement('trans'); - var transitionEndEventNames = { - 'WebkitTransition': 'webkitTransitionEnd', - 'MozTransition': 'transitionend', - 'OTransition': 'oTransitionEnd', - 'transition': 'transitionend' - }; - var animationEndEventNames = { - 'WebkitTransition': 'webkitAnimationEnd', - 'MozTransition': 'animationend', - 'OTransition': 'oAnimationEnd', - 'transition': 'animationend' - }; - function findEndEventName(endEventNames) { - for (var name in endEventNames){ - if (transElement.style[name] !== undefined) { - return endEventNames[name]; - } - } - } - $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); - $transition.animationEndEventName = findEndEventName(animationEndEventNames); - return $transition; -}]); - -angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) - - .directive('collapse', ['$transition', function ($transition) { +angular.module("ui.bootstrap", ["ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); +angular.module('ui.bootstrap.collapse', []) + .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) { + var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null; return { - link: function (scope, element, attrs) { - - var initialAnimSkip = true; - var currentTransition; - - function doTransition(change) { - var newTransition = $transition(element, change); - if (currentTransition) { - currentTransition.cancel(); - } - currentTransition = newTransition; - newTransition.then(newTransitionDone, newTransitionDone); - return newTransition; - - function newTransitionDone() { - // Make sure it's this transition, otherwise, leave it alone. - if (currentTransition === newTransition) { - currentTransition = undefined; - } - } - } - + link: function(scope, element, attrs) { function expand() { - if (initialAnimSkip) { - initialAnimSkip = false; - expandDone(); + element.removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', true) + .attr('aria-hidden', false); + + if ($animateCss) { + $animateCss(element, { + addClass: 'in', + easing: 'ease', + to: { height: element[0].scrollHeight + 'px' } + }).start().finally(expandDone); } else { - element.removeClass('collapse').addClass('collapsing'); - doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); + $animate.addClass(element, 'in', { + to: { height: element[0].scrollHeight + 'px' } + }).then(expandDone); } } function expandDone() { - element.removeClass('collapsing'); - element.addClass('collapse in'); - element.css({height: 'auto'}); + element.removeClass('collapsing') + .addClass('collapse') + .css({height: 'auto'}); } function collapse() { - if (initialAnimSkip) { - initialAnimSkip = false; - collapseDone(); - element.css({height: 0}); + if (!element.hasClass('collapse') && !element.hasClass('in')) { + return collapseDone(); + } + + element + // IMPORTANT: The height must be set before adding "collapsing" class. + // Otherwise, the browser attempts to animate from height 0 (in + // collapsing class) to the given height here. + .css({height: element[0].scrollHeight + 'px'}) + // initially all panel collapse have the collapse class, this removal + // prevents the animation from jumping to collapsed state + .removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', false) + .attr('aria-hidden', true); + + if ($animateCss) { + $animateCss(element, { + removeClass: 'in', + to: {height: '0'} + }).start().finally(collapseDone); } else { - // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value - element.css({ height: element[0].scrollHeight + 'px' }); - //trigger reflow so a browser realizes that height was updated from auto to a specific value - var x = element[0].offsetWidth; - - element.removeClass('collapse in').addClass('collapsing'); - - doTransition({ height: 0 }).then(collapseDone); + $animate.removeClass(element, 'in', { + to: {height: '0'} + }).then(collapseDone); } } function collapseDone() { - element.removeClass('collapsing'); - element.addClass('collapse'); + element.css({height: '0'}); // Required so that collapse works when animation is disabled + element.removeClass('collapsing') + .addClass('collapse'); } - scope.$watch(attrs.collapse, function (shouldCollapse) { + scope.$watch(attrs.uibCollapse, function(shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +/* Deprecated collapse below */ + +angular.module('ui.bootstrap.collapse') + + .value('$collapseSuppressWarning', false) + + .directive('collapse', ['$animate', '$injector', '$log', '$collapseSuppressWarning', function($animate, $injector, $log, $collapseSuppressWarning) { + var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null; + return { + link: function(scope, element, attrs) { + if (!$collapseSuppressWarning) { + $log.warn('collapse is now deprecated. Use uib-collapse instead.'); + } + + function expand() { + element.removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', true) + .attr('aria-hidden', false); + + if ($animateCss) { + $animateCss(element, { + easing: 'ease', + to: { height: element[0].scrollHeight + 'px' } + }).start().done(expandDone); + } else { + $animate.animate(element, {}, { + height: element[0].scrollHeight + 'px' + }).then(expandDone); + } + } + + function expandDone() { + element.removeClass('collapsing') + .addClass('collapse in') + .css({height: 'auto'}); + } + + function collapse() { + if (!element.hasClass('collapse') && !element.hasClass('in')) { + return collapseDone(); + } + + element + // IMPORTANT: The height must be set before adding "collapsing" class. + // Otherwise, the browser attempts to animate from height 0 (in + // collapsing class) to the given height here. + .css({height: element[0].scrollHeight + 'px'}) + // initially all panel collapse have the collapse class, this removal + // prevents the animation from jumping to collapsed state + .removeClass('collapse in') + .addClass('collapsing') + .attr('aria-expanded', false) + .attr('aria-hidden', true); + + if ($animateCss) { + $animateCss(element, { + to: {height: '0'} + }).start().done(collapseDone); + } else { + $animate.animate(element, {}, { + height: '0' + }).then(collapseDone); + } + } + + function collapseDone() { + element.css({height: '0'}); // Required so that collapse works when animation is disabled + element.removeClass('collapsing') + .addClass('collapse'); + } + + scope.$watch(attrs.collapse, function(shouldCollapse) { if (shouldCollapse) { collapse(); } else { @@ -167,21 +168,21 @@ angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) -.constant('accordionConfig', { +.constant('uibAccordionConfig', { closeOthers: true }) -.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { - +.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) { // This array keeps track of the accordion groups this.groups = []; // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to this.closeOthers = function(openGroup) { - var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; - if ( closeOthers ) { - angular.forEach(this.groups, function (group) { - if ( group !== openGroup ) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? + $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if (closeOthers) { + angular.forEach(this.groups, function(group) { + if (group !== openGroup) { group.isOpen = false; } }); @@ -193,7 +194,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) var that = this; this.groups.push(groupScope); - groupScope.$on('$destroy', function (event) { + groupScope.$on('$destroy', function(event) { that.removeGroup(groupScope); }); }; @@ -201,7 +202,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) // This is called from the accordion-group directive when to remove itself this.removeGroup = function(group) { var index = this.groups.indexOf(group); - if ( index !== -1 ) { + if (index !== -1) { this.groups.splice(index, 1); } }; @@ -210,24 +211,26 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) // The accordion directive simply sets up the directive controller // and adds an accordion CSS class to itself element. -.directive('accordion', function () { +.directive('uibAccordion', function() { return { - restrict:'EA', - controller:'AccordionController', + controller: 'UibAccordionController', + controllerAs: 'accordion', transclude: true, - replace: false, - templateUrl: 'template/accordion/accordion.html' + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/accordion/accordion.html'; + } }; }) // The accordion-group directive indicates a block of html that will expand and collapse in an accordion -.directive('accordionGroup', function() { +.directive('uibAccordionGroup', function() { return { - require:'^accordion', // We need this directive to be inside an accordion - restrict:'EA', - transclude:true, // It transcludes the contents of the directive into the template + require: '^uibAccordion', // We need this directive to be inside an accordion + transclude: true, // It transcludes the contents of the directive into the template replace: true, // The element containing the directive will be replaced with the template - templateUrl:'template/accordion/accordion-group.html', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/accordion/accordion-group.html'; + }, scope: { heading: '@', // Interpolate the heading attribute onto this scope isOpen: '=?', @@ -241,15 +244,20 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) link: function(scope, element, attrs, accordionCtrl) { accordionCtrl.addGroup(scope); + scope.openClass = attrs.openClass || 'panel-open'; + scope.panelClass = attrs.panelClass; scope.$watch('isOpen', function(value) { - if ( value ) { + element.toggleClass(scope.openClass, !!value); + if (value) { accordionCtrl.closeOthers(scope); } }); - scope.toggleOpen = function() { - if ( !scope.isDisabled ) { - scope.isOpen = !scope.isOpen; + scope.toggleOpen = function($event) { + if (!scope.isDisabled) { + if (!$event || $event.which === 32) { + scope.isOpen = !scope.isOpen; + } } }; } @@ -257,118 +265,264 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) }) // Use accordion-heading below an accordion-group to provide a heading containing HTML -// -// Heading containing HTML - -// -.directive('accordionHeading', function() { +.directive('uibAccordionHeading', function() { return { - restrict: 'EA', transclude: true, // Grab the contents to be used as the heading template: '', // In effect remove this element! replace: true, - require: '^accordionGroup', - link: function(scope, element, attr, accordionGroupCtrl, transclude) { + require: '^uibAccordionGroup', + link: function(scope, element, attrs, accordionGroupCtrl, transclude) { // Pass the heading to the accordion-group controller // so that it can be transcluded into the right place in the template // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] - accordionGroupCtrl.setHeading(transclude(scope, function() {})); + accordionGroupCtrl.setHeading(transclude(scope, angular.noop)); } }; }) // Use in the accordion-group template to indicate where you want the heading to be transcluded // You must provide the property on the accordion-group controller that will hold the transcluded element -//
    -// -// ... -//
    -.directive('accordionTransclude', function() { +.directive('uibAccordionTransclude', function() { return { - require: '^accordionGroup', - link: function(scope, element, attr, controller) { - scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { - if ( heading ) { - element.html(''); - element.append(heading); + require: ['?^uibAccordionGroup', '?^accordionGroup'], + link: function(scope, element, attrs, controller) { + controller = controller[0] ? controller[0] : controller[1]; // Delete after we remove deprecation + scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) { + if (heading) { + element.find('span').html(''); + element.find('span').append(heading); } }); } }; }); +/* Deprecated accordion below */ + +angular.module('ui.bootstrap.accordion') + + .value('$accordionSuppressWarning', false) + + .controller('AccordionController', ['$scope', '$attrs', '$controller', '$log', '$accordionSuppressWarning', function($scope, $attrs, $controller, $log, $accordionSuppressWarning) { + if (!$accordionSuppressWarning) { + $log.warn('AccordionController is now deprecated. Use UibAccordionController instead.'); + } + + angular.extend(this, $controller('UibAccordionController', { + $scope: $scope, + $attrs: $attrs + })); + }]) + + .directive('accordion', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { + return { + restrict: 'EA', + controller: 'AccordionController', + controllerAs: 'accordion', + transclude: true, + replace: false, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/accordion/accordion.html'; + }, + link: function() { + if (!$accordionSuppressWarning) { + $log.warn('accordion is now deprecated. Use uib-accordion instead.'); + } + } + }; + }]) + + .directive('accordionGroup', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { + return { + require: '^accordion', // We need this directive to be inside an accordion + restrict: 'EA', + transclude: true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/accordion/accordion-group.html'; + }, + scope: { + heading: '@', // Interpolate the heading attribute onto this scope + isOpen: '=?', + isDisabled: '=?' + }, + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + if (!$accordionSuppressWarning) { + $log.warn('accordion-group is now deprecated. Use uib-accordion-group instead.'); + } + + accordionCtrl.addGroup(scope); + + scope.openClass = attrs.openClass || 'panel-open'; + scope.panelClass = attrs.panelClass; + scope.$watch('isOpen', function(value) { + element.toggleClass(scope.openClass, !!value); + if (value) { + accordionCtrl.closeOthers(scope); + } + }); + + scope.toggleOpen = function($event) { + if (!scope.isDisabled) { + if (!$event || $event.which === 32) { + scope.isOpen = !scope.isOpen; + } + } + }; + } + }; + }]) + + .directive('accordionHeading', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { + return { + restrict: 'EA', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + link: function(scope, element, attr, accordionGroupCtrl, transclude) { + if (!$accordionSuppressWarning) { + $log.warn('accordion-heading is now deprecated. Use uib-accordion-heading instead.'); + } + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, angular.noop)); + } + }; + }]) + + .directive('accordionTransclude', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { + return { + require: '^accordionGroup', + link: function(scope, element, attr, controller) { + if (!$accordionSuppressWarning) { + $log.warn('accordion-transclude is now deprecated. Use uib-accordion-transclude instead.'); + } + + scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { + if (heading) { + element.find('span').html(''); + element.find('span').append(heading); + } + }); + } + }; + }]); + + angular.module('ui.bootstrap.alert', []) -.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { - $scope.closeable = 'close' in $attrs; - this.close = $scope.close; +.controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) { + $scope.closeable = !!$attrs.close; + + var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ? + $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null; + + if (dismissOnTimeout) { + $timeout(function() { + $scope.close(); + }, parseInt(dismissOnTimeout, 10)); + } }]) -.directive('alert', function () { +.directive('uibAlert', function() { return { - restrict:'EA', - controller:'AlertController', - templateUrl:'template/alert/alert.html', - transclude:true, - replace:true, + controller: 'UibAlertController', + controllerAs: 'alert', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/alert/alert.html'; + }, + transclude: true, + replace: true, scope: { type: '@', close: '&' } }; -}) +}); -.directive('dismissOnTimeout', ['$timeout', function($timeout) { - return { - require: 'alert', - link: function(scope, element, attrs, alertCtrl) { - $timeout(function(){ - alertCtrl.close(); - }, parseInt(attrs.dismissOnTimeout, 10)); +/* Deprecated alert below */ + +angular.module('ui.bootstrap.alert') + + .value('$alertSuppressWarning', false) + + .controller('AlertController', ['$scope', '$attrs', '$controller', '$log', '$alertSuppressWarning', function($scope, $attrs, $controller, $log, $alertSuppressWarning) { + if (!$alertSuppressWarning) { + $log.warn('AlertController is now deprecated. Use UibAlertController instead.'); } - }; -}]); -angular.module('ui.bootstrap.bindHtml', []) + angular.extend(this, $controller('UibAlertController', { + $scope: $scope, + $attrs: $attrs + })); + }]) - .directive('bindHtmlUnsafe', function () { - return function (scope, element, attr) { - element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); - scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { - element.html(value || ''); - }); + .directive('alert', ['$log', '$alertSuppressWarning', function($log, $alertSuppressWarning) { + return { + controller: 'AlertController', + controllerAs: 'alert', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/alert/alert.html'; + }, + transclude: true, + replace: true, + scope: { + type: '@', + close: '&' + }, + link: function() { + if (!$alertSuppressWarning) { + $log.warn('alert is now deprecated. Use uib-alert instead.'); + } + } }; - }); + }]); + angular.module('ui.bootstrap.buttons', []) -.constant('buttonConfig', { +.constant('uibButtonConfig', { activeClass: 'active', toggleEvent: 'click' }) -.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { +.controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) { this.activeClass = buttonConfig.activeClass || 'active'; this.toggleEvent = buttonConfig.toggleEvent || 'click'; }]) -.directive('btnRadio', function () { +.directive('uibBtnRadio', function() { return { - require: ['btnRadio', 'ngModel'], - controller: 'ButtonsController', - link: function (scope, element, attrs, ctrls) { + require: ['uibBtnRadio', 'ngModel'], + controller: 'UibButtonsController', + controllerAs: 'buttons', + link: function(scope, element, attrs, ctrls) { var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + element.find('input').css({display: 'none'}); + //model -> UI - ngModelCtrl.$render = function () { - element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + ngModelCtrl.$render = function() { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio))); }; //ui->model - element.bind(buttonsCtrl.toggleEvent, function () { + element.on(buttonsCtrl.toggleEvent, function() { + if (attrs.disabled) { + return; + } + var isActive = element.hasClass(buttonsCtrl.activeClass); if (!isActive || angular.isDefined(attrs.uncheckable)) { - scope.$apply(function () { - ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); + scope.$apply(function() { + ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio)); ngModelCtrl.$render(); }); } @@ -377,13 +531,16 @@ angular.module('ui.bootstrap.buttons', []) }; }) -.directive('btnCheckbox', function () { +.directive('uibBtnCheckbox', function() { return { - require: ['btnCheckbox', 'ngModel'], - controller: 'ButtonsController', - link: function (scope, element, attrs, ctrls) { + require: ['uibBtnCheckbox', 'ngModel'], + controller: 'UibButtonsController', + controllerAs: 'button', + link: function(scope, element, attrs, ctrls) { var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + element.find('input').css({display: 'none'}); + function getTrueValue() { return getCheckboxValue(attrs.btnCheckboxTrue, true); } @@ -392,19 +549,22 @@ angular.module('ui.bootstrap.buttons', []) return getCheckboxValue(attrs.btnCheckboxFalse, false); } - function getCheckboxValue(attributeValue, defaultValue) { - var val = scope.$eval(attributeValue); - return angular.isDefined(val) ? val : defaultValue; + function getCheckboxValue(attribute, defaultValue) { + return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue; } //model -> UI - ngModelCtrl.$render = function () { + ngModelCtrl.$render = function() { element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); }; //ui->model - element.bind(buttonsCtrl.toggleEvent, function () { - scope.$apply(function () { + element.on(buttonsCtrl.toggleEvent, function() { + if (attrs.disabled) { + return; + } + + scope.$apply(function() { ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); ngModelCtrl.$render(); }); @@ -413,18 +573,134 @@ angular.module('ui.bootstrap.buttons', []) }; }); +/* Deprecated buttons below */ + +angular.module('ui.bootstrap.buttons') + + .value('$buttonsSuppressWarning', false) + + .controller('ButtonsController', ['$controller', '$log', '$buttonsSuppressWarning', function($controller, $log, $buttonsSuppressWarning) { + if (!$buttonsSuppressWarning) { + $log.warn('ButtonsController is now deprecated. Use UibButtonsController instead.'); + } + + angular.extend(this, $controller('UibButtonsController')); + }]) + + .directive('btnRadio', ['$log', '$buttonsSuppressWarning', function($log, $buttonsSuppressWarning) { + return { + require: ['btnRadio', 'ngModel'], + controller: 'ButtonsController', + controllerAs: 'buttons', + link: function(scope, element, attrs, ctrls) { + if (!$buttonsSuppressWarning) { + $log.warn('btn-radio is now deprecated. Use uib-btn-radio instead.'); + } + + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + element.find('input').css({display: 'none'}); + + //model -> UI + ngModelCtrl.$render = function() { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function() { + if (attrs.disabled) { + return; + } + + var isActive = element.hasClass(buttonsCtrl.activeClass); + + if (!isActive || angular.isDefined(attrs.uncheckable)) { + scope.$apply(function() { + ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); + ngModelCtrl.$render(); + }); + } + }); + } + }; + }]) + + .directive('btnCheckbox', ['$document', '$log', '$buttonsSuppressWarning', function($document, $log, $buttonsSuppressWarning) { + return { + require: ['btnCheckbox', 'ngModel'], + controller: 'ButtonsController', + controllerAs: 'button', + link: function(scope, element, attrs, ctrls) { + if (!$buttonsSuppressWarning) { + $log.warn('btn-checkbox is now deprecated. Use uib-btn-checkbox instead.'); + } + + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + element.find('input').css({display: 'none'}); + + function getTrueValue() { + return getCheckboxValue(attrs.btnCheckboxTrue, true); + } + + function getFalseValue() { + return getCheckboxValue(attrs.btnCheckboxFalse, false); + } + + function getCheckboxValue(attributeValue, defaultValue) { + var val = scope.$eval(attributeValue); + return angular.isDefined(val) ? val : defaultValue; + } + + //model -> UI + ngModelCtrl.$render = function() { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function() { + if (attrs.disabled) { + return; + } + + scope.$apply(function() { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + + //accessibility + element.on('keypress', function(e) { + if (attrs.disabled || e.which !== 32 || $document[0].activeElement !== element[0]) { + return; + } + + scope.$apply(function() { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + } + }; + }]); + + /** -* @ngdoc overview -* @name ui.bootstrap.carousel -* -* @description -* AngularJS version of an image carousel. -* -*/ -angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) -.controller('CarouselController', ['$scope', '$timeout', '$interval', '$transition', function ($scope, $timeout, $interval, $transition) { + * @ngdoc overview + * @name ui.bootstrap.carousel + * + * @description + * AngularJS version of an image carousel. + * + */ +angular.module('ui.bootstrap.carousel', []) + +.controller('UibCarouselController', ['$scope', '$element', '$interval', '$animate', function($scope, $element, $interval, $animate) { var self = this, slides = self.slides = $scope.slides = [], + NEW_ANIMATE = angular.version.minor >= 4, + NO_TRANSITION = 'uib-noTransition', + SLIDE_DIRECTION = 'uib-slideDirection', currentIndex = -1, currentInterval, isPlaying; self.currentSlide = null; @@ -432,83 +708,100 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) var destroyed = false; /* direction: "prev" or "next" */ self.select = $scope.select = function(nextSlide, direction) { - var nextIndex = slides.indexOf(nextSlide); + var nextIndex = $scope.indexOfSlide(nextSlide); //Decide direction if it's not given if (direction === undefined) { - direction = nextIndex > currentIndex ? 'next' : 'prev'; + direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; } - if (nextSlide && nextSlide !== self.currentSlide) { - if ($scope.$currentTransition) { - $scope.$currentTransition.cancel(); - //Timeout so ng-class in template has time to fix classes for finished slide - $timeout(goNext); - } else { - goNext(); - } - } - function goNext() { - // Scope has been destroyed, stop here. - if (destroyed) { return; } - //If we have a slide to transition from and we have a transition type and we're allowed, go - if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { - //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime - nextSlide.$element.addClass(direction); - var reflow = nextSlide.$element[0].offsetWidth; //force reflow - - //Set all other slides to stop doing their stuff for the new transition - angular.forEach(slides, function(slide) { - angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); - }); - angular.extend(nextSlide, {direction: direction, active: true, entering: true}); - angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); - - $scope.$currentTransition = $transition(nextSlide.$element, {}); - //We have to create new pointers inside a closure since next & current will change - (function(next,current) { - $scope.$currentTransition.then( - function(){ transitionDone(next, current); }, - function(){ transitionDone(next, current); } - ); - }(nextSlide, self.currentSlide)); - } else { - transitionDone(nextSlide, self.currentSlide); - } - self.currentSlide = nextSlide; - currentIndex = nextIndex; - //every time you change slides, reset the timer - restartTimer(); - } - function transitionDone(next, current) { - angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); - angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); - $scope.$currentTransition = null; + //Prevent this user-triggered transition from occurring if there is already one in progress + if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) { + goNext(nextSlide, nextIndex, direction); } }; - $scope.$on('$destroy', function () { + + function goNext(slide, index, direction) { + // Scope has been destroyed, stop here. + if (destroyed) { return; } + + angular.extend(slide, {direction: direction, active: true}); + angular.extend(self.currentSlide || {}, {direction: direction, active: false}); + if ($animate.enabled() && !$scope.noTransition && !$scope.$currentTransition && + slide.$element && self.slides.length > 1) { + slide.$element.data(SLIDE_DIRECTION, slide.direction); + if (self.currentSlide && self.currentSlide.$element) { + self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction); + } + + $scope.$currentTransition = true; + if (NEW_ANIMATE) { + $animate.on('addClass', slide.$element, function(element, phase) { + if (phase === 'close') { + $scope.$currentTransition = null; + $animate.off('addClass', element); + } + }); + } else { + slide.$element.one('$animate:close', function closeFn() { + $scope.$currentTransition = null; + }); + } + } + + self.currentSlide = slide; + currentIndex = index; + + //every time you change slides, reset the timer + restartTimer(); + } + + $scope.$on('$destroy', function() { destroyed = true; }); + function getSlideByIndex(index) { + if (angular.isUndefined(slides[index].index)) { + return slides[index]; + } + var i, len = slides.length; + for (i = 0; i < slides.length; ++i) { + if (slides[i].index == index) { + return slides[i]; + } + } + } + + self.getCurrentIndex = function() { + if (self.currentSlide && angular.isDefined(self.currentSlide.index)) { + return +self.currentSlide.index; + } + return currentIndex; + }; + /* Allow outside people to call indexOf on slides array */ - self.indexOfSlide = function(slide) { - return slides.indexOf(slide); + $scope.indexOfSlide = function(slide) { + return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide); }; $scope.next = function() { - var newIndex = (currentIndex + 1) % slides.length; + var newIndex = (self.getCurrentIndex() + 1) % slides.length; - //Prevent this user-triggered transition from occurring if there is already one in progress - if (!$scope.$currentTransition) { - return self.select(slides[newIndex], 'next'); + if (newIndex === 0 && $scope.noWrap()) { + $scope.pause(); + return; } + + return self.select(getSlideByIndex(newIndex), 'next'); }; $scope.prev = function() { - var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1; - //Prevent this user-triggered transition from occurring if there is already one in progress - if (!$scope.$currentTransition) { - return self.select(slides[newIndex], 'prev'); + if ($scope.noWrap() && newIndex === slides.length - 1) { + $scope.pause(); + return; } + + return self.select(getSlideByIndex(newIndex), 'prev'); }; $scope.isActive = function(slide) { @@ -516,6 +809,7 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) }; $scope.$watch('interval', restartTimer); + $scope.$watchCollection('slides', resetTransition); $scope.$on('$destroy', resetTimer); function restartTimer() { @@ -535,13 +829,19 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) function timerFn() { var interval = +$scope.interval; - if (isPlaying && !isNaN(interval) && interval > 0) { + if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) { $scope.next(); } else { $scope.pause(); } } + function resetTransition(slides) { + if (!slides.length) { + $scope.$currentTransition = null; + } + } + $scope.play = function() { if (!isPlaying) { isPlaying = true; @@ -559,9 +859,9 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) slide.$element = element; slides.push(slide); //if this is the first slide or the slide is set to active, select it - if(slides.length === 1 || slide.active) { - self.select(slides[slides.length-1]); - if (slides.length == 1) { + if (slides.length === 1 || slide.active) { + self.select(slides[slides.length - 1]); + if (slides.length === 1) { $scope.play(); } } else { @@ -570,20 +870,34 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) }; self.removeSlide = function(slide) { + if (angular.isDefined(slide.index)) { + slides.sort(function(a, b) { + return +a.index > +b.index; + }); + } //get the index of the slide inside the carousel var index = slides.indexOf(slide); slides.splice(index, 1); if (slides.length > 0 && slide.active) { if (index >= slides.length) { - self.select(slides[index-1]); + self.select(slides[index - 1]); } else { self.select(slides[index]); } } else if (currentIndex > index) { currentIndex--; } + + //clean the currentSlide when no more slide + if (slides.length === 0) { + self.currentSlide = null; + } }; + $scope.$watch('noTransition', function(noTransition) { + $element.data(NO_TRANSITION, noTransition); + }); + }]) /** @@ -601,20 +915,20 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) * @example - - + + - - + + - - + + .carousel-indicators { @@ -624,18 +938,21 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) */ -.directive('carousel', [function() { +.directive('uibCarousel', [function() { return { - restrict: 'EA', transclude: true, replace: true, - controller: 'CarouselController', + controller: 'UibCarouselController', + controllerAs: 'carousel', require: 'carousel', - templateUrl: 'template/carousel/carousel.html', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/carousel/carousel.html'; + }, scope: { interval: '=', noTransition: '=', - noPause: '=' + noPause: '=', + noWrap: '&' } }; }]) @@ -649,20 +966,21 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. * * @param {boolean=} active Model binding, whether or not this slide is currently active. + * @param {number=} index The index of the slide. The slides will be sorted by this parameter. * * @example
    - - + + - - + + Interval, in milliseconds:
    Enter a negative number to stop the interval.
    @@ -681,15 +999,19 @@ function CarouselDemoCtrl($scope) {
    */ -.directive('slide', function() { +.directive('uibSlide', function() { return { - require: '^carousel', + require: '^uibCarousel', restrict: 'EA', transclude: true, replace: true, - templateUrl: 'template/carousel/slide.html', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/carousel/slide.html'; + }, scope: { - active: '=?' + active: '=?', + actual: '=?', + index: '=?' }, link: function (scope, element, attrs, carouselCtrl) { carouselCtrl.addSlide(scope, element); @@ -705,59 +1027,271 @@ function CarouselDemoCtrl($scope) { }); } }; -}); +}) + +.animation('.item', [ + '$injector', '$animate', +function ($injector, $animate) { + var NO_TRANSITION = 'uib-noTransition', + SLIDE_DIRECTION = 'uib-slideDirection', + $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + + function removeClass(element, className, callback) { + element.removeClass(className); + if (callback) { + callback(); + } + } + + return { + beforeAddClass: function(element, className, done) { + // Due to transclusion, noTransition property is on parent's scope + if (className == 'active' && element.parent() && element.parent().parent() && + !element.parent().parent().data(NO_TRANSITION)) { + var stopped = false; + var direction = element.data(SLIDE_DIRECTION); + var directionClass = direction == 'next' ? 'left' : 'right'; + var removeClassFn = removeClass.bind(this, element, + directionClass + ' ' + direction, done); + element.addClass(direction); + + if ($animateCss) { + $animateCss(element, {addClass: directionClass}) + .start() + .done(removeClassFn); + } else { + $animate.addClass(element, directionClass).then(function () { + if (!stopped) { + removeClassFn(); + } + done(); + }); + } + + return function () { + stopped = true; + }; + } + done(); + }, + beforeRemoveClass: function (element, className, done) { + // Due to transclusion, noTransition property is on parent's scope + if (className === 'active' && element.parent() && element.parent().parent() && + !element.parent().parent().data(NO_TRANSITION)) { + var stopped = false; + var direction = element.data(SLIDE_DIRECTION); + var directionClass = direction == 'next' ? 'left' : 'right'; + var removeClassFn = removeClass.bind(this, element, directionClass, done); + + if ($animateCss) { + $animateCss(element, {addClass: directionClass}) + .start() + .done(removeClassFn); + } else { + $animate.addClass(element, directionClass).then(function() { + if (!stopped) { + removeClassFn(); + } + done(); + }); + } + return function() { + stopped = true; + }; + } + done(); + } + }; +}]); + +/* deprecated carousel below */ + +angular.module('ui.bootstrap.carousel') + +.value('$carouselSuppressWarning', false) + +.controller('CarouselController', ['$scope', '$element', '$controller', '$log', '$carouselSuppressWarning', function($scope, $element, $controller, $log, $carouselSuppressWarning) { + if (!$carouselSuppressWarning) { + $log.warn('CarouselController is now deprecated. Use UibCarouselController instead.'); + } + + angular.extend(this, $controller('UibCarouselController', { + $scope: $scope, + $element: $element + })); +}]) + +.directive('carousel', ['$log', '$carouselSuppressWarning', function($log, $carouselSuppressWarning) { + return { + transclude: true, + replace: true, + controller: 'CarouselController', + controllerAs: 'carousel', + require: 'carousel', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/carousel/carousel.html'; + }, + scope: { + interval: '=', + noTransition: '=', + noPause: '=', + noWrap: '&' + }, + link: function() { + if (!$carouselSuppressWarning) { + $log.warn('carousel is now deprecated. Use uib-carousel instead.'); + } + } + }; +}]) + +.directive('slide', ['$log', '$carouselSuppressWarning', function($log, $carouselSuppressWarning) { + return { + require: '^carousel', + transclude: true, + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/carousel/slide.html'; + }, + scope: { + active: '=?', + actual: '=?', + index: '=?' + }, + link: function (scope, element, attrs, carouselCtrl) { + if (!$carouselSuppressWarning) { + $log.warn('slide is now deprecated. Use uib-slide instead.'); + } + + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}]); angular.module('ui.bootstrap.dateparser', []) -.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) { +.service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) { + // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js + var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; - this.parsers = {}; + var localeId; + var formatCodeToRegex; - var formatCodeToRegex = { - 'yyyy': { - regex: '\\d{4}', - apply: function(value) { this.year = +value; } - }, - 'yy': { - regex: '\\d{2}', - apply: function(value) { this.year = +value + 2000; } - }, - 'y': { - regex: '\\d{1,4}', - apply: function(value) { this.year = +value; } - }, - 'MMMM': { - regex: $locale.DATETIME_FORMATS.MONTH.join('|'), - apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } - }, - 'MMM': { - regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), - apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } - }, - 'MM': { - regex: '0[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } - }, - 'M': { - regex: '[1-9]|1[0-2]', - apply: function(value) { this.month = value - 1; } - }, - 'dd': { - regex: '[0-2][0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } - }, - 'd': { - regex: '[1-2]?[0-9]{1}|3[0-1]{1}', - apply: function(value) { this.date = +value; } - }, - 'EEEE': { - regex: $locale.DATETIME_FORMATS.DAY.join('|') - }, - 'EEE': { - regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') - } + this.init = function() { + localeId = $locale.id; + + this.parsers = {}; + + formatCodeToRegex = { + 'yyyy': { + regex: '\\d{4}', + apply: function(value) { this.year = +value; } + }, + 'yy': { + regex: '\\d{2}', + apply: function(value) { this.year = +value + 2000; } + }, + 'y': { + regex: '\\d{1,4}', + apply: function(value) { this.year = +value; } + }, + 'MMMM': { + regex: $locale.DATETIME_FORMATS.MONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } + }, + 'MMM': { + regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } + }, + 'MM': { + regex: '0[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'M': { + regex: '[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'dd': { + regex: '[0-2][0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'd': { + regex: '[1-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'EEEE': { + regex: $locale.DATETIME_FORMATS.DAY.join('|') + }, + 'EEE': { + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') + }, + 'HH': { + regex: '(?:0|1)[0-9]|2[0-3]', + apply: function(value) { this.hours = +value; } + }, + 'hh': { + regex: '0[0-9]|1[0-2]', + apply: function(value) { this.hours = +value; } + }, + 'H': { + regex: '1?[0-9]|2[0-3]', + apply: function(value) { this.hours = +value; } + }, + 'h': { + regex: '[0-9]|1[0-2]', + apply: function(value) { this.hours = +value; } + }, + 'mm': { + regex: '[0-5][0-9]', + apply: function(value) { this.minutes = +value; } + }, + 'm': { + regex: '[0-9]|[1-5][0-9]', + apply: function(value) { this.minutes = +value; } + }, + 'sss': { + regex: '[0-9][0-9][0-9]', + apply: function(value) { this.milliseconds = +value; } + }, + 'ss': { + regex: '[0-5][0-9]', + apply: function(value) { this.seconds = +value; } + }, + 's': { + regex: '[0-9]|[1-5][0-9]', + apply: function(value) { this.seconds = +value; } + }, + 'a': { + regex: $locale.DATETIME_FORMATS.AMPMS.join('|'), + apply: function(value) { + if (this.hours === 12) { + this.hours = 0; + } + + if (value === 'PM') { + this.hours += 12; + } + } + } + }; }; + this.init(); + function createParser(format) { var map = [], regex = format.split(''); @@ -785,14 +1319,19 @@ angular.module('ui.bootstrap.dateparser', []) }; } - this.parse = function(input, format) { - if ( !angular.isString(input) || !format ) { + this.parse = function(input, format, baseDate) { + if (!angular.isString(input) || !format) { return input; } format = $locale.DATETIME_FORMATS[format] || format; + format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); - if ( !this.parsers[format] ) { + if ($locale.id !== localeId) { + this.init(); + } + + if (!this.parsers[format]) { this.parsers[format] = createParser(format); } @@ -801,18 +1340,43 @@ angular.module('ui.bootstrap.dateparser', []) map = parser.map, results = input.match(regex); - if ( results && results.length ) { - var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt; + if (results && results.length) { + var fields, dt; + if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { + fields = { + year: baseDate.getFullYear(), + month: baseDate.getMonth(), + date: baseDate.getDate(), + hours: baseDate.getHours(), + minutes: baseDate.getMinutes(), + seconds: baseDate.getSeconds(), + milliseconds: baseDate.getMilliseconds() + }; + } else { + if (baseDate) { + $log.warn('dateparser:', 'baseDate is not a valid date'); + } + fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; + } - for( var i = 1, n = results.length; i < n; i++ ) { + for (var i = 1, n = results.length; i < n; i++) { var mapper = map[i-1]; - if ( mapper.apply ) { + if (mapper.apply) { mapper.apply.call(fields, results[i]); } } - if ( isValid(fields.year, fields.month, fields.date) ) { - dt = new Date( fields.year, fields.month, fields.date, fields.hours); + if (isValid(fields.year, fields.month, fields.date)) { + if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { + dt = new Date(baseDate); + dt.setFullYear(fields.year, fields.month, fields.date, + fields.hours, fields.minutes, fields.seconds, + fields.milliseconds || 0); + } else { + dt = new Date(fields.year, fields.month, fields.date, + fields.hours, fields.minutes, fields.seconds, + fields.milliseconds || 0); + } } return dt; @@ -822,18 +1386,36 @@ angular.module('ui.bootstrap.dateparser', []) // Check if date is valid for specific month (and year for February). // Month: 0 = Jan, 1 = Feb, etc function isValid(year, month, date) { - if ( month === 1 && date > 28) { - return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); + if (date < 1) { + return false; } - if ( month === 3 || month === 5 || month === 8 || month === 10) { - return date < 31; + if (month === 1 && date > 28) { + return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); + } + + if (month === 3 || month === 5 || month === 8 || month === 10) { + return date < 31; } return true; } }]); +/* Deprecated dateparser below */ + +angular.module('ui.bootstrap.dateparser') + +.value('$dateParserSuppressWarning', false) + +.service('dateParser', ['$log', '$dateParserSuppressWarning', 'uibDateParser', function($log, $dateParserSuppressWarning, uibDateParser) { + if (!$dateParserSuppressWarning) { + $log.warn('dateParser is now deprecated. Use uibDateParser instead.'); + } + + angular.extend(this, uibDateParser); +}]); + angular.module('ui.bootstrap.position', []) /** @@ -842,8 +1424,7 @@ angular.module('ui.bootstrap.position', []) * relation to other, existing elements (this is the case for tooltips, popovers, * typeahead suggestions etc.). */ - .factory('$position', ['$document', '$window', function ($document, $window) { - + .factory('$uibPosition', ['$document', '$window', function($document, $window) { function getStyle(el, cssprop) { if (el.currentStyle) { //IE return el.currentStyle[cssprop]; @@ -866,7 +1447,7 @@ angular.module('ui.bootstrap.position', []) * returns the closest, non-statically positioned parentOffset of a given element * @param element */ - var parentOffsetEl = function (element) { + var parentOffsetEl = function(element) { var docDomEl = $document[0]; var offsetParent = element.offsetParent || docDomEl; while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { @@ -880,7 +1461,7 @@ angular.module('ui.bootstrap.position', []) * Provides read-only equivalent of jQuery's position function: * http://api.jquery.com/position/ */ - position: function (element) { + position: function(element) { var elBCR = this.offset(element); var offsetParentBCR = { top: 0, left: 0 }; var offsetParentEl = parentOffsetEl(element[0]); @@ -903,7 +1484,7 @@ angular.module('ui.bootstrap.position', []) * Provides read-only equivalent of jQuery's offset function: * http://api.jquery.com/offset/ */ - offset: function (element) { + offset: function(element) { var boundingClientRect = element[0].getBoundingClientRect(); return { width: boundingClientRect.width || element.prop('offsetWidth'), @@ -916,8 +1497,7 @@ angular.module('ui.bootstrap.position', []) /** * Provides coordinates for the targetEl in relation to hostEl */ - positionElements: function (hostEl, targetEl, positionStr, appendToBody) { - + positionElements: function(hostEl, targetEl, positionStr, appendToBody) { var positionStrParts = positionStr.split('-'); var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; @@ -932,25 +1512,25 @@ angular.module('ui.bootstrap.position', []) targetElHeight = targetEl.prop('offsetHeight'); var shiftWidth = { - center: function () { + center: function() { return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; }, - left: function () { + left: function() { return hostElPos.left; }, - right: function () { + right: function() { return hostElPos.left + hostElPos.width; } }; var shiftHeight = { - center: function () { + center: function() { return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; }, - top: function () { + top: function() { return hostElPos.top; }, - bottom: function () { + bottom: function() { return hostElPos.top + hostElPos.height; } }; @@ -987,9 +1567,25 @@ angular.module('ui.bootstrap.position', []) }; }]); +/* Deprecated position below */ + +angular.module('ui.bootstrap.position') + +.value('$positionSuppressWarning', false) + +.service('$position', ['$log', '$positionSuppressWarning', '$uibPosition', function($log, $positionSuppressWarning, $uibPosition) { + if (!$positionSuppressWarning) { + $log.warn('$position is now deprecated. Use $uibPosition instead.'); + } + + angular.extend(this, $uibPosition); +}]); + angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) -.constant('datepickerConfig', { +.value('$datepickerSuppressError', false) + +.constant('uibDatepickerConfig', { formatDay: 'dd', formatMonth: 'MMMM', formatYear: 'yyyy', @@ -1003,10 +1599,11 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst startingDay: 0, yearRange: 20, minDate: null, - maxDate: null + maxDate: null, + shortcutPropagation: false }) -.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { +.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError) { var self = this, ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; @@ -1015,13 +1612,13 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst // Configuration attributes angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', - 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) { - self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; + 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) { + self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; }); // Watchable date attributes - angular.forEach(['minDate', 'maxDate'], function( key ) { - if ( $attrs[key] ) { + angular.forEach(['minDate', 'maxDate'], function(key) { + if ($attrs[key]) { $scope.$parent.$watch($parse($attrs[key]), function(value) { self[key] = value ? new Date(value) : null; self.refreshView(); @@ -1031,9 +1628,35 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst } }); + angular.forEach(['minMode', 'maxMode'], function(key) { + if ($attrs[key]) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = angular.isDefined(value) ? value : $attrs[key]; + $scope[key] = self[key]; + if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) { + $scope.datepickerMode = self[key]; + } + }); + } else { + self[key] = datepickerConfig[key] || null; + $scope[key] = self[key]; + } + }); + $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); - this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date(); + + if (angular.isDefined($attrs.initDate)) { + this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date(); + $scope.$parent.$watch($attrs.initDate, function(initDate) { + if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { + self.activeDate = initDate; + self.refreshView(); + } + }); + } else { + this.activeDate = new Date(); + } $scope.isActive = function(dateObject) { if (self.compare(dateObject.date, self.activeDate) === 0) { @@ -1043,7 +1666,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst return false; }; - this.init = function( ngModelCtrl_ ) { + this.init = function(ngModelCtrl_) { ngModelCtrl = ngModelCtrl_; ngModelCtrl.$render = function() { @@ -1052,44 +1675,48 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst }; this.render = function() { - if ( ngModelCtrl.$modelValue ) { - var date = new Date( ngModelCtrl.$modelValue ), + if (ngModelCtrl.$viewValue) { + var date = new Date(ngModelCtrl.$viewValue), isValid = !isNaN(date); - if ( isValid ) { + if (isValid) { this.activeDate = date; - } else { + } else if (!$datepickerSuppressError) { $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); } - ngModelCtrl.$setValidity('date', isValid); } this.refreshView(); }; this.refreshView = function() { - if ( this.element ) { + if (this.element) { this._refreshView(); - var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; - ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); + var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date))); } }; this.createDateObject = function(date, format) { - var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; return { date: date, label: dateFilter(date, format), selected: model && this.compare(date, model) === 0, disabled: this.isDisabled(date), - current: this.compare(date, new Date()) === 0 + current: this.compare(date, new Date()) === 0, + customClass: this.customClass(date) }; }; - this.isDisabled = function( date ) { + this.isDisabled = function(date) { return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); }; + this.customClass = function(date) { + return $scope.customClass({date: date, mode: $scope.datepickerMode}); + }; + // Split array into smaller arrays this.split = function(arr, size) { var arrays = []; @@ -1099,66 +1726,64 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst return arrays; }; - $scope.select = function( date ) { - if ( $scope.datepickerMode === self.minMode ) { - var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); - dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); - ngModelCtrl.$setViewValue( dt ); + $scope.select = function(date) { + if ($scope.datepickerMode === self.minMode) { + var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + ngModelCtrl.$setViewValue(dt); ngModelCtrl.$render(); } else { self.activeDate = date; - $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ]; + $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1]; } }; - $scope.move = function( direction ) { + $scope.move = function(direction) { var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), month = self.activeDate.getMonth() + direction * (self.step.months || 0); self.activeDate.setFullYear(year, month, 1); self.refreshView(); }; - $scope.toggleMode = function( direction ) { + $scope.toggleMode = function(direction) { direction = direction || 1; if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { return; } - $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ]; + $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction]; }; // Key event mapper - $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' }; + $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; var focusElement = function() { - $timeout(function() { - self.element[0].focus(); - }, 0 , false); + self.element[0].focus(); }; // Listen for focus requests from popup directive - $scope.$on('datepicker.focus', focusElement); + $scope.$on('uib:datepicker.focus', focusElement); - $scope.keydown = function( evt ) { + $scope.keydown = function(evt) { var key = $scope.keys[evt.which]; - if ( !key || evt.shiftKey || evt.altKey ) { + if (!key || evt.shiftKey || evt.altKey) { return; } evt.preventDefault(); - evt.stopPropagation(); + if (!self.shortcutPropagation) { + evt.stopPropagation(); + } if (key === 'enter' || key === 'space') { - if ( self.isDisabled(self.activeDate)) { + if (self.isDisabled(self.activeDate)) { return; // do nothing } $scope.select(self.activeDate); - focusElement(); } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { $scope.toggleMode(key === 'up' ? 1 : -1); - focusElement(); } else { self.handleKeyDown(key, evt); self.refreshView(); @@ -1166,532 +1791,1067 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst }; }]) -.directive( 'datepicker', function () { +.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + this.step = { months: 1 }; + this.element = $element; + function getDaysInMonth(year, month) { + return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; + } + + this.init = function(ctrl) { + angular.extend(ctrl, this); + scope.showWeeks = ctrl.showWeeks; + ctrl.refreshView(); + }; + + this.getDates = function(startDate, n) { + var dates = new Array(n), current = new Date(startDate), i = 0, date; + while (i < n) { + date = new Date(current); + dates[i++] = date; + current.setDate(current.getDate() + 1); + } + return dates; + }; + + this._refreshView = function() { + var year = this.activeDate.getFullYear(), + month = this.activeDate.getMonth(), + firstDayOfMonth = new Date(this.activeDate); + + firstDayOfMonth.setFullYear(year, month, 1); + + var difference = this.startingDay - firstDayOfMonth.getDay(), + numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, + firstDate = new Date(firstDayOfMonth); + + if (numDisplayedFromPreviousMonth > 0) { + firstDate.setDate(-numDisplayedFromPreviousMonth + 1); + } + + // 42 is the number of days on a six-month calendar + var days = this.getDates(firstDate, 42); + for (var i = 0; i < 42; i ++) { + days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), { + secondary: days[i].getMonth() !== month, + uid: scope.uniqueId + '-' + i + }); + } + + scope.labels = new Array(7); + for (var j = 0; j < 7; j++) { + scope.labels[j] = { + abbr: dateFilter(days[j].date, this.formatDayHeader), + full: dateFilter(days[j].date, 'EEEE') + }; + } + + scope.title = dateFilter(this.activeDate, this.formatDayTitle); + scope.rows = this.split(days, 7); + + if (scope.showWeeks) { + scope.weekNumbers = []; + var thursdayIndex = (4 + 7 - this.startingDay) % 7, + numWeeks = scope.rows.length; + for (var curWeek = 0; curWeek < numWeeks; curWeek++) { + scope.weekNumbers.push( + getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date)); + } + } + }; + + this.compare = function(date1, date2) { + return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())); + }; + + function getISO8601WeekNumber(date) { + var checkDate = new Date(date); + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + } + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getDate(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 7; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 7; + } else if (key === 'pageup' || key === 'pagedown') { + var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); + this.activeDate.setMonth(month, 1); + date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date); + } else if (key === 'home') { + date = 1; + } else if (key === 'end') { + date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()); + } + this.activeDate.setDate(date); + }; +}]) + +.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + this.step = { years: 1 }; + this.element = $element; + + this.init = function(ctrl) { + angular.extend(ctrl, this); + ctrl.refreshView(); + }; + + this._refreshView = function() { + var months = new Array(12), + year = this.activeDate.getFullYear(), + date; + + for (var i = 0; i < 12; i++) { + date = new Date(this.activeDate); + date.setFullYear(year, i, 1); + months[i] = angular.extend(this.createDateObject(date, this.formatMonth), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = dateFilter(this.activeDate, this.formatMonthTitle); + scope.rows = this.split(months, 3); + }; + + this.compare = function(date1, date2) { + return new Date(date1.getFullYear(), date1.getMonth()) - new Date(date2.getFullYear(), date2.getMonth()); + }; + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getMonth(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 3; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 3; + } else if (key === 'pageup' || key === 'pagedown') { + var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); + this.activeDate.setFullYear(year); + } else if (key === 'home') { + date = 0; + } else if (key === 'end') { + date = 11; + } + this.activeDate.setMonth(date); + }; +}]) + +.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { + var range; + this.element = $element; + + function getStartingYear(year) { + return parseInt((year - 1) / range, 10) * range + 1; + } + + this.yearpickerInit = function() { + range = this.yearRange; + this.step = { years: range }; + }; + + this._refreshView = function() { + var years = new Array(range), date; + + for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) { + date = new Date(this.activeDate); + date.setFullYear(start + i, 0, 1); + years[i] = angular.extend(this.createDateObject(date, this.formatYear), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = [years[0].label, years[range - 1].label].join(' - '); + scope.rows = this.split(years, 5); + }; + + this.compare = function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }; + + this.handleKeyDown = function(key, evt) { + var date = this.activeDate.getFullYear(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 5; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 5; + } else if (key === 'pageup' || key === 'pagedown') { + date += (key === 'pageup' ? - 1 : 1) * this.step.years; + } else if (key === 'home') { + date = getStartingYear(this.activeDate.getFullYear()); + } else if (key === 'end') { + date = getStartingYear(this.activeDate.getFullYear()) + range - 1; + } + this.activeDate.setFullYear(date); + }; +}]) + +.directive('uibDatepicker', function() { return { - restrict: 'EA', replace: true, - templateUrl: 'template/datepicker/datepicker.html', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/datepicker.html'; + }, scope: { datepickerMode: '=?', - dateDisabled: '&' + dateDisabled: '&', + customClass: '&', + shortcutPropagation: '&?' }, - require: ['datepicker', '?^ngModel'], - controller: 'DatepickerController', + require: ['uibDatepicker', '^ngModel'], + controller: 'UibDatepickerController', + controllerAs: 'datepicker', link: function(scope, element, attrs, ctrls) { var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - if ( ngModelCtrl ) { - datepickerCtrl.init( ngModelCtrl ); - } + datepickerCtrl.init(ngModelCtrl); } }; }) -.directive('daypicker', ['dateFilter', function (dateFilter) { +.directive('uibDaypicker', function() { return { - restrict: 'EA', replace: true, - templateUrl: 'template/datepicker/day.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - scope.showWeeks = ctrl.showWeeks; + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/day.html'; + }, + require: ['^?uibDatepicker', 'uibDaypicker', '^?datepicker'], + controller: 'UibDaypickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0] || ctrls[2], + daypickerCtrl = ctrls[1]; - ctrl.step = { months: 1 }; - ctrl.element = element; + daypickerCtrl.init(datepickerCtrl); + } + }; +}) - var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - function getDaysInMonth( year, month ) { - return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; - } +.directive('uibMonthpicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/month.html'; + }, + require: ['^?uibDatepicker', 'uibMonthpicker', '^?datepicker'], + controller: 'UibMonthpickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0] || ctrls[2], + monthpickerCtrl = ctrls[1]; - function getDates(startDate, n) { - var dates = new Array(n), current = new Date(startDate), i = 0; - current.setHours(12); // Prevent repeated dates because of timezone bug - while ( i < n ) { - dates[i++] = new Date(current); - current.setDate( current.getDate() + 1 ); - } - return dates; - } + monthpickerCtrl.init(datepickerCtrl); + } + }; +}) - ctrl._refreshView = function() { - var year = ctrl.activeDate.getFullYear(), - month = ctrl.activeDate.getMonth(), - firstDayOfMonth = new Date(year, month, 1), - difference = ctrl.startingDay - firstDayOfMonth.getDay(), - numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, - firstDate = new Date(firstDayOfMonth); - - if ( numDisplayedFromPreviousMonth > 0 ) { - firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); - } - - // 42 is the number of days on a six-month calendar - var days = getDates(firstDate, 42); - for (var i = 0; i < 42; i ++) { - days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), { - secondary: days[i].getMonth() !== month, - uid: scope.uniqueId + '-' + i - }); - } - - scope.labels = new Array(7); - for (var j = 0; j < 7; j++) { - scope.labels[j] = { - abbr: dateFilter(days[j].date, ctrl.formatDayHeader), - full: dateFilter(days[j].date, 'EEEE') - }; - } - - scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); - scope.rows = ctrl.split(days, 7); - - if ( scope.showWeeks ) { - scope.weekNumbers = []; - var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ), - numWeeks = scope.rows.length; - while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {} - } - }; - - ctrl.compare = function(date1, date2) { - return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); - }; - - function getISO8601WeekNumber(date) { - var checkDate = new Date(date); - checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday - var time = checkDate.getTime(); - checkDate.setMonth(0); // Compare with Jan 1 - checkDate.setDate(1); - return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; - } - - ctrl.handleKeyDown = function( key, evt ) { - var date = ctrl.activeDate.getDate(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 7; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 7; - } else if (key === 'pageup' || key === 'pagedown') { - var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); - ctrl.activeDate.setMonth(month, 1); - date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); - } else if (key === 'home') { - date = 1; - } else if (key === 'end') { - date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); - } - ctrl.activeDate.setDate(date); - }; +.directive('uibYearpicker', function() { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/year.html'; + }, + require: ['^?uibDatepicker', 'uibYearpicker', '^?datepicker'], + controller: 'UibYearpickerController', + link: function(scope, element, attrs, ctrls) { + var ctrl = ctrls[0] || ctrls[2]; + angular.extend(ctrl, ctrls[1]); + ctrl.yearpickerInit(); ctrl.refreshView(); } }; -}]) +}) -.directive('monthpicker', ['dateFilter', function (dateFilter) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/month.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - ctrl.step = { years: 1 }; - ctrl.element = element; - - ctrl._refreshView = function() { - var months = new Array(12), - year = ctrl.activeDate.getFullYear(); - - for ( var i = 0; i < 12; i++ ) { - months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), { - uid: scope.uniqueId + '-' + i - }); - } - - scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); - scope.rows = ctrl.split(months, 3); - }; - - ctrl.compare = function(date1, date2) { - return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); - }; - - ctrl.handleKeyDown = function( key, evt ) { - var date = ctrl.activeDate.getMonth(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 3; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 3; - } else if (key === 'pageup' || key === 'pagedown') { - var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); - ctrl.activeDate.setFullYear(year); - } else if (key === 'home') { - date = 0; - } else if (key === 'end') { - date = 11; - } - ctrl.activeDate.setMonth(date); - }; - - ctrl.refreshView(); - } - }; -}]) - -.directive('yearpicker', ['dateFilter', function (dateFilter) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/year.html', - require: '^datepicker', - link: function(scope, element, attrs, ctrl) { - var range = ctrl.yearRange; - - ctrl.step = { years: range }; - ctrl.element = element; - - function getStartingYear( year ) { - return parseInt((year - 1) / range, 10) * range + 1; - } - - ctrl._refreshView = function() { - var years = new Array(range); - - for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) { - years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), { - uid: scope.uniqueId + '-' + i - }); - } - - scope.title = [years[0].label, years[range - 1].label].join(' - '); - scope.rows = ctrl.split(years, 5); - }; - - ctrl.compare = function(date1, date2) { - return date1.getFullYear() - date2.getFullYear(); - }; - - ctrl.handleKeyDown = function( key, evt ) { - var date = ctrl.activeDate.getFullYear(); - - if (key === 'left') { - date = date - 1; // up - } else if (key === 'up') { - date = date - 5; // down - } else if (key === 'right') { - date = date + 1; // down - } else if (key === 'down') { - date = date + 5; - } else if (key === 'pageup' || key === 'pagedown') { - date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; - } else if (key === 'home') { - date = getStartingYear( ctrl.activeDate.getFullYear() ); - } else if (key === 'end') { - date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1; - } - ctrl.activeDate.setFullYear(date); - }; - - ctrl.refreshView(); - } - }; -}]) - -.constant('datepickerPopupConfig', { +.constant('uibDatepickerPopupConfig', { datepickerPopup: 'yyyy-MM-dd', + datepickerPopupTemplateUrl: 'template/datepicker/popup.html', + datepickerTemplateUrl: 'template/datepicker/datepicker.html', + html5Types: { + date: 'yyyy-MM-dd', + 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss', + 'month': 'yyyy-MM' + }, currentText: 'Today', clearText: 'Clear', closeText: 'Done', closeOnDateSelection: true, appendToBody: false, - showButtonBar: true + showButtonBar: true, + onOpenFocus: true }) -.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', -function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { +.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', +function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) { + var self = this; + var cache = {}, + isHtml5DateInput = false; + var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus, + datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, + ngModel, $popup; + + scope.watchData = {}; + + this.init = function(_ngModel_) { + ngModel = _ngModel_; + closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection; + appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; + datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl; + datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl; + + scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; + + if (datepickerPopupConfig.html5Types[attrs.type]) { + dateFormat = datepickerPopupConfig.html5Types[attrs.type]; + isHtml5DateInput = true; + } else { + dateFormat = attrs.datepickerPopup || attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; + attrs.$observe('uibDatepickerPopup', function(value, oldValue) { + var newDateFormat = value || datepickerPopupConfig.datepickerPopup; + // Invalidate the $modelValue to ensure that formatters re-run + // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764 + if (newDateFormat !== dateFormat) { + dateFormat = newDateFormat; + ngModel.$modelValue = null; + + if (!dateFormat) { + throw new Error('uibDatepickerPopup must have a date format specified.'); + } + } + }); + } + + if (!dateFormat) { + throw new Error('uibDatepickerPopup must have a date format specified.'); + } + + if (isHtml5DateInput && attrs.datepickerPopup) { + throw new Error('HTML5 date input types do not support custom formats.'); + } + + // popup element used to display calendar + popupEl = angular.element('
    '); + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection(date)', + 'template-url': datepickerPopupTemplateUrl + }); + + // datepicker element + datepickerEl = angular.element(popupEl.children()[0]); + datepickerEl.attr('template-url', datepickerTemplateUrl); + + if (isHtml5DateInput) { + if (attrs.type === 'month') { + datepickerEl.attr('datepicker-mode', '"month"'); + datepickerEl.attr('min-mode', 'month'); + } + } + + if (attrs.datepickerOptions) { + var options = scope.$parent.$eval(attrs.datepickerOptions); + if (options && options.initDate) { + scope.initDate = options.initDate; + datepickerEl.attr('init-date', 'initDate'); + delete options.initDate; + } + angular.forEach(options, function(value, option) { + datepickerEl.attr(cameltoDash(option), value); + }); + } + + angular.forEach(['minMode', 'maxMode', 'minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function(key) { + if (attrs[key]) { + var getAttribute = $parse(attrs[key]); + scope.$parent.$watch(getAttribute, function(value) { + scope.watchData[key] = value; + if (key === 'minDate' || key === 'maxDate') { + cache[key] = new Date(value); + } + }); + datepickerEl.attr(cameltoDash(key), 'watchData.' + key); + + // Propagate changes from datepicker to outside + if (key === 'datepickerMode') { + var setAttribute = getAttribute.assign; + scope.$watch('watchData.' + key, function(value, oldvalue) { + if (angular.isFunction(setAttribute) && value !== oldvalue) { + setAttribute(scope.$parent, value); + } + }); + } + } + }); + if (attrs.dateDisabled) { + datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); + } + + if (attrs.showWeeks) { + datepickerEl.attr('show-weeks', attrs.showWeeks); + } + + if (attrs.customClass) { + datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })'); + } + + if (!isHtml5DateInput) { + // Internal API to maintain the correct ng-invalid-[key] class + ngModel.$$parserName = 'date'; + ngModel.$validators.date = validator; + ngModel.$parsers.unshift(parseDate); + ngModel.$formatters.push(function(value) { + scope.date = value; + return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat); + }); + } else { + ngModel.$formatters.push(function(value) { + scope.date = value; + return value; + }); + } + + // Detect changes in the view from the text box + ngModel.$viewChangeListeners.push(function() { + scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date); + }); + + element.bind('keydown', inputKeydownBind); + + $popup = $compile(popupEl)(scope); + // Prevent jQuery cache memory leak (template is now redundant after linking) + popupEl.remove(); + + if (appendToBody) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + + scope.$on('$destroy', function() { + if (scope.isOpen === true) { + if (!$rootScope.$$phase) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + } + + $popup.remove(); + element.unbind('keydown', inputKeydownBind); + $document.unbind('click', documentClickBind); + }); + }; + + scope.getText = function(key) { + return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; + }; + + scope.isDisabled = function(date) { + if (date === 'today') { + date = new Date(); + } + + return ((scope.watchData.minDate && scope.compare(date, cache.minDate) < 0) || + (scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0)); + }; + + scope.compare = function(date1, date2) { + return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())); + }; + + // Inner change + scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + scope.date = dt; + } + var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function + element.val(date); + ngModel.$setViewValue(date); + + if (closeOnDateSelection) { + scope.isOpen = false; + element[0].focus(); + } + }; + + scope.keydown = function(evt) { + if (evt.which === 27) { + scope.isOpen = false; + element[0].focus(); + } + }; + + scope.select = function(date) { + if (date === 'today') { + var today = new Date(); + if (angular.isDate(scope.date)) { + date = new Date(scope.date); + date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); + } else { + date = new Date(today.setHours(0, 0, 0, 0)); + } + } + scope.dateSelection(date); + }; + + scope.close = function() { + scope.isOpen = false; + element[0].focus(); + }; + + scope.$watch('isOpen', function(value) { + if (value) { + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); + + $timeout(function() { + if (onOpenFocus) { + scope.$broadcast('uib:datepicker.focus'); + } + $document.bind('click', documentClickBind); + }, 0, false); + } else { + $document.unbind('click', documentClickBind); + } + }); + + function cameltoDash(string) { + return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); + } + + function parseDate(viewValue) { + if (angular.isNumber(viewValue)) { + // presumably timestamp to date object + viewValue = new Date(viewValue); + } + + if (!viewValue) { + return null; + } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { + return viewValue; + } else if (angular.isString(viewValue)) { + var date = dateParser.parse(viewValue, dateFormat, scope.date); + if (isNaN(date)) { + return undefined; + } else { + return date; + } + } else { + return undefined; + } + } + + function validator(modelValue, viewValue) { + var value = modelValue || viewValue; + + if (!attrs.ngRequired && !value) { + return true; + } + + if (angular.isNumber(value)) { + value = new Date(value); + } + if (!value) { + return true; + } else if (angular.isDate(value) && !isNaN(value)) { + return true; + } else if (angular.isString(value)) { + var date = dateParser.parse(value, dateFormat); + return !isNaN(date); + } else { + return false; + } + } + + function documentClickBind(event) { + var popup = $popup[0]; + var dpContainsTarget = element[0].contains(event.target); + // The popup node may not be an element node + // In some browsers (IE) only element nodes have the 'contains' function + var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target); + if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + } + + function inputKeydownBind(evt) { + if (evt.which === 27 && scope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + scope.$apply(function() { + scope.isOpen = false; + }); + element[0].focus(); + } else if (evt.which === 40 && !scope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + scope.$apply(function() { + scope.isOpen = true; + }); + } + } +}]) + +.directive('uibDatepickerPopup', function() { return { - restrict: 'EA', - require: 'ngModel', + require: ['ngModel', 'uibDatepickerPopup'], + controller: 'UibDatepickerPopupController', scope: { isOpen: '=?', currentText: '@', clearText: '@', closeText: '@', - dateDisabled: '&' + dateDisabled: '&', + customClass: '&' }, - link: function(scope, element, attrs, ngModel) { - var dateFormat, - closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, - appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + link: function(scope, element, attrs, ctrls) { + var ngModel = ctrls[0], + ctrl = ctrls[1]; - scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; - - scope.getText = function( key ) { - return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; - }; - - attrs.$observe('datepickerPopup', function(value) { - dateFormat = value || datepickerPopupConfig.datepickerPopup; - ngModel.$render(); - }); - - // popup element used to display calendar - var popupEl = angular.element('
    '); - popupEl.attr({ - 'ng-model': 'date', - 'ng-change': 'dateSelection()' - }); - - function cameltoDash( string ){ - return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); - } - - // datepicker element - var datepickerEl = angular.element(popupEl.children()[0]); - if ( attrs.datepickerOptions ) { - angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) { - datepickerEl.attr( cameltoDash(option), value ); - }); - } - - scope.watchData = {}; - angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function( key ) { - if ( attrs[key] ) { - var getAttribute = $parse(attrs[key]); - scope.$parent.$watch(getAttribute, function(value){ - scope.watchData[key] = value; - }); - datepickerEl.attr(cameltoDash(key), 'watchData.' + key); - - // Propagate changes from datepicker to outside - if ( key === 'datepickerMode' ) { - var setAttribute = getAttribute.assign; - scope.$watch('watchData.' + key, function(value, oldvalue) { - if ( value !== oldvalue ) { - setAttribute(scope.$parent, value); - } - }); - } - } - }); - if (attrs.dateDisabled) { - datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); - } - - function parseDate(viewValue) { - if (!viewValue) { - ngModel.$setValidity('date', true); - return null; - } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { - ngModel.$setValidity('date', true); - return viewValue; - } else if (angular.isString(viewValue)) { - var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue); - if (isNaN(date)) { - ngModel.$setValidity('date', false); - return undefined; - } else { - ngModel.$setValidity('date', true); - return date; - } - } else { - ngModel.$setValidity('date', false); - return undefined; - } - } - ngModel.$parsers.unshift(parseDate); - - // Inner change - scope.dateSelection = function(dt) { - if (angular.isDefined(dt)) { - scope.date = dt; - } - ngModel.$setViewValue(scope.date); - ngModel.$render(); - - if ( closeOnDateSelection ) { - scope.isOpen = false; - element[0].focus(); - } - }; - - element.bind('input change keyup', function() { - scope.$apply(function() { - scope.date = ngModel.$modelValue; - }); - }); - - // Outter change - ngModel.$render = function() { - var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; - element.val(date); - scope.date = parseDate( ngModel.$modelValue ); - }; - - var documentClickBind = function(event) { - if (scope.isOpen && event.target !== element[0]) { - scope.$apply(function() { - scope.isOpen = false; - }); - } - }; - - var keydown = function(evt, noApply) { - scope.keydown(evt); - }; - element.bind('keydown', keydown); - - scope.keydown = function(evt) { - if (evt.which === 27) { - evt.preventDefault(); - evt.stopPropagation(); - scope.close(); - } else if (evt.which === 40 && !scope.isOpen) { - scope.isOpen = true; - } - }; - - scope.$watch('isOpen', function(value) { - if (value) { - scope.$broadcast('datepicker.focus'); - scope.position = appendToBody ? $position.offset(element) : $position.position(element); - scope.position.top = scope.position.top + element.prop('offsetHeight'); - - $document.bind('click', documentClickBind); - } else { - $document.unbind('click', documentClickBind); - } - }); - - scope.select = function( date ) { - if (date === 'today') { - var today = new Date(); - if (angular.isDate(ngModel.$modelValue)) { - date = new Date(ngModel.$modelValue); - date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); - } else { - date = new Date(today.setHours(0, 0, 0, 0)); - } - } - scope.dateSelection( date ); - }; - - scope.close = function() { - scope.isOpen = false; - element[0].focus(); - }; - - var $popup = $compile(popupEl)(scope); - // Prevent jQuery cache memory leak (template is now redundant after linking) - popupEl.remove(); - - if ( appendToBody ) { - $document.find('body').append($popup); - } else { - element.after($popup); - } - - scope.$on('$destroy', function() { - $popup.remove(); - element.unbind('keydown', keydown); - $document.unbind('click', documentClickBind); - }); + ctrl.init(ngModel); } }; -}]) +}) -.directive('datepickerPopupWrap', function() { +.directive('uibDatepickerPopupWrap', function() { return { - restrict:'EA', replace: true, transclude: true, - templateUrl: 'template/datepicker/popup.html', - link:function (scope, element, attrs) { - element.bind('click', function(event) { - event.preventDefault(); - event.stopPropagation(); - }); + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/popup.html'; } }; }); -angular.module('ui.bootstrap.dropdown', []) +/* Deprecated datepicker below */ -.constant('dropdownConfig', { +angular.module('ui.bootstrap.datepicker') + +.value('$datepickerSuppressWarning', false) + +.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', '$datepickerSuppressWarning', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, $datepickerSuppressWarning) { + if (!$datepickerSuppressWarning) { + $log.warn('DatepickerController is now deprecated. Use UibDatepickerController instead.'); + } + + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; + + this.modes = ['day', 'month', 'year']; + + angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', + 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) { + self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; + }); + + angular.forEach(['minDate', 'maxDate'], function(key) { + if ($attrs[key]) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = value ? new Date(value) : null; + self.refreshView(); + }); + } else { + self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; + } + }); + + angular.forEach(['minMode', 'maxMode'], function(key) { + if ($attrs[key]) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = angular.isDefined(value) ? value : $attrs[key]; + $scope[key] = self[key]; + if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) { + $scope.datepickerMode = self[key]; + } + }); + } else { + self[key] = datepickerConfig[key] || null; + $scope[key] = self[key]; + } + }); + + $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; + $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); + + if (angular.isDefined($attrs.initDate)) { + this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date(); + $scope.$parent.$watch($attrs.initDate, function(initDate) { + if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { + self.activeDate = initDate; + self.refreshView(); + } + }); + } else { + this.activeDate = new Date(); + } + + $scope.isActive = function(dateObject) { + if (self.compare(dateObject.date, self.activeDate) === 0) { + $scope.activeDateId = dateObject.uid; + return true; + } + return false; + }; + + this.init = function(ngModelCtrl_) { + ngModelCtrl = ngModelCtrl_; + + ngModelCtrl.$render = function() { + self.render(); + }; + }; + + this.render = function() { + if (ngModelCtrl.$viewValue) { + var date = new Date(ngModelCtrl.$viewValue), + isValid = !isNaN(date); + + if (isValid) { + this.activeDate = date; + } else if (!$datepickerSuppressError) { + $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } + } + this.refreshView(); + }; + + this.refreshView = function() { + if (this.element) { + this._refreshView(); + + var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date))); + } + }; + + this.createDateObject = function(date, format) { + var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; + return { + date: date, + label: dateFilter(date, format), + selected: model && this.compare(date, model) === 0, + disabled: this.isDisabled(date), + current: this.compare(date, new Date()) === 0, + customClass: this.customClass(date) + }; + }; + + this.isDisabled = function(date) { + return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); + }; + + this.customClass = function(date) { + return $scope.customClass({date: date, mode: $scope.datepickerMode}); + }; + + // Split array into smaller arrays + this.split = function(arr, size) { + var arrays = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + }; + + this.fixTimeZone = function(date) { + var hours = date.getHours(); + date.setHours(hours === 23 ? hours + 2 : 0); + }; + + $scope.select = function(date) { + if ($scope.datepickerMode === self.minMode) { + var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + ngModelCtrl.$setViewValue(dt); + ngModelCtrl.$render(); + } else { + self.activeDate = date; + $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1]; + } + }; + + $scope.move = function(direction) { + var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), + month = self.activeDate.getMonth() + direction * (self.step.months || 0); + self.activeDate.setFullYear(year, month, 1); + self.refreshView(); + }; + + $scope.toggleMode = function(direction) { + direction = direction || 1; + + if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { + return; + } + + $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction]; + }; + + // Key event mapper + $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; + + var focusElement = function() { + self.element[0].focus(); + }; + + $scope.$on('uib:datepicker.focus', focusElement); + + $scope.keydown = function(evt) { + var key = $scope.keys[evt.which]; + + if (!key || evt.shiftKey || evt.altKey) { + return; + } + + evt.preventDefault(); + if (!self.shortcutPropagation) { + evt.stopPropagation(); + } + + if (key === 'enter' || key === 'space') { + if (self.isDisabled(self.activeDate)) { + return; // do nothing + } + $scope.select(self.activeDate); + } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { + $scope.toggleMode(key === 'up' ? 1 : -1); + } else { + self.handleKeyDown(key, evt); + self.refreshView(); + } + }; +}]) + +.directive('datepicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + replace: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/datepicker.html'; + }, + scope: { + datepickerMode: '=?', + dateDisabled: '&', + customClass: '&', + shortcutPropagation: '&?' + }, + require: ['datepicker', '^ngModel'], + controller: 'DatepickerController', + controllerAs: 'datepicker', + link: function(scope, element, attrs, ctrls) { + if (!$datepickerSuppressWarning) { + $log.warn('datepicker is now deprecated. Use uib-datepicker instead.'); + } + + var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + datepickerCtrl.init(ngModelCtrl); + } + }; +}]) + +.directive('daypicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + replace: true, + templateUrl: 'template/datepicker/day.html', + require: ['^datepicker', 'daypicker'], + controller: 'UibDaypickerController', + link: function(scope, element, attrs, ctrls) { + if (!$datepickerSuppressWarning) { + $log.warn('daypicker is now deprecated. Use uib-daypicker instead.'); + } + + var datepickerCtrl = ctrls[0], + daypickerCtrl = ctrls[1]; + + daypickerCtrl.init(datepickerCtrl); + } + }; +}]) + +.directive('monthpicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + replace: true, + templateUrl: 'template/datepicker/month.html', + require: ['^datepicker', 'monthpicker'], + controller: 'UibMonthpickerController', + link: function(scope, element, attrs, ctrls) { + if (!$datepickerSuppressWarning) { + $log.warn('monthpicker is now deprecated. Use uib-monthpicker instead.'); + } + + var datepickerCtrl = ctrls[0], + monthpickerCtrl = ctrls[1]; + + monthpickerCtrl.init(datepickerCtrl); + } + }; +}]) + +.directive('yearpicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + replace: true, + templateUrl: 'template/datepicker/year.html', + require: ['^datepicker', 'yearpicker'], + controller: 'UibYearpickerController', + link: function(scope, element, attrs, ctrls) { + if (!$datepickerSuppressWarning) { + $log.warn('yearpicker is now deprecated. Use uib-yearpicker instead.'); + } + + var ctrl = ctrls[0]; + angular.extend(ctrl, ctrls[1]); + ctrl.yearpickerInit(); + + ctrl.refreshView(); + } + }; +}]) + +.directive('datepickerPopup', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + require: ['ngModel', 'datepickerPopup'], + controller: 'UibDatepickerPopupController', + scope: { + isOpen: '=?', + currentText: '@', + clearText: '@', + closeText: '@', + dateDisabled: '&', + customClass: '&' + }, + link: function(scope, element, attrs, ctrls) { + if (!$datepickerSuppressWarning) { + $log.warn('datepicker-popup is now deprecated. Use uib-datepicker-popup instead.'); + } + + var ngModel = ctrls[0], + ctrl = ctrls[1]; + + ctrl.init(ngModel); + } + }; +}]) + +.directive('datepickerPopupWrap', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) { + return { + replace: true, + transclude: true, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/datepicker/popup.html'; + }, + link: function() { + if (!$datepickerSuppressWarning) { + $log.warn('datepicker-popup-wrap is now deprecated. Use uib-datepicker-popup-wrap instead.'); + } + } + }; +}]); + +angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) + +.constant('uibDropdownConfig', { openClass: 'open' }) -.service('dropdownService', ['$document', function($document) { +.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) { var openScope = null; - this.open = function( dropdownScope ) { - if ( !openScope ) { + this.open = function(dropdownScope) { + if (!openScope) { $document.bind('click', closeDropdown); - $document.bind('keydown', escapeKeyBind); + $document.bind('keydown', keybindFilter); } - if ( openScope && openScope !== dropdownScope ) { - openScope.isOpen = false; + if (openScope && openScope !== dropdownScope) { + openScope.isOpen = false; } openScope = dropdownScope; }; - this.close = function( dropdownScope ) { - if ( openScope === dropdownScope ) { + this.close = function(dropdownScope) { + if (openScope === dropdownScope) { openScope = null; $document.unbind('click', closeDropdown); - $document.unbind('keydown', escapeKeyBind); + $document.unbind('keydown', keybindFilter); } }; - var closeDropdown = function( evt ) { + var closeDropdown = function(evt) { // This method may still be called during the same mouse event that // unbound this event handler. So check openScope before proceeding. if (!openScope) { return; } + if (evt && openScope.getAutoClose() === 'disabled') { return ; } + var toggleElement = openScope.getToggleElement(); - if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { - return; + if (evt && toggleElement && toggleElement[0].contains(evt.target)) { + return; } - openScope.$apply(function() { - openScope.isOpen = false; - }); + var dropdownElement = openScope.getDropdownElement(); + if (evt && openScope.getAutoClose() === 'outsideClick' && + dropdownElement && dropdownElement[0].contains(evt.target)) { + return; + } + + openScope.isOpen = false; + + if (!$rootScope.$$phase) { + openScope.$apply(); + } }; - var escapeKeyBind = function( evt ) { - if ( evt.which === 27 ) { + var keybindFilter = function(evt) { + if (evt.which === 27) { openScope.focusToggleElement(); closeDropdown(); + } else if (openScope.isKeynavEnabled() && /(38|40)/.test(evt.which) && openScope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + openScope.focusDropdownEntry(evt.which); } }; }]) -.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) { +.controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) { var self = this, - scope = $scope.$new(), // create a child scope so we are not polluting original one - openClass = dropdownConfig.openClass, - getIsOpen, - setIsOpen = angular.noop, - toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop; + scope = $scope.$new(), // create a child scope so we are not polluting original one + templateScope, + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, + appendToBody = false, + keynavEnabled =false, + selectedOption = null; - this.init = function( element ) { - self.$element = element; - if ( $attrs.isOpen ) { + $element.addClass('dropdown'); + + this.init = function() { + if ($attrs.isOpen) { getIsOpen = $parse($attrs.isOpen); setIsOpen = getIsOpen.assign; @@ -1699,9 +2859,19 @@ angular.module('ui.bootstrap.dropdown', []) scope.isOpen = !!value; }); } + + appendToBody = angular.isDefined($attrs.dropdownAppendToBody); + keynavEnabled = angular.isDefined($attrs.uibKeyboardNav); + + if (appendToBody && self.dropdownMenu) { + $document.find('body').append(self.dropdownMenu); + $element.on('$destroy', function handleDestroyEvent() { + self.dropdownMenu.remove(); + }); + } }; - this.toggle = function( open ) { + this.toggle = function(open) { return scope.isOpen = arguments.length ? !!open : !scope.isOpen; }; @@ -1714,60 +2884,215 @@ angular.module('ui.bootstrap.dropdown', []) return self.toggleElement; }; + scope.getAutoClose = function() { + return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' + }; + + scope.getElement = function() { + return $element; + }; + + scope.isKeynavEnabled = function() { + return keynavEnabled; + }; + + scope.focusDropdownEntry = function(keyCode) { + var elems = self.dropdownMenu ? //If append to body is used. + (angular.element(self.dropdownMenu).find('a')) : + (angular.element($element).find('ul').eq(0).find('a')); + + switch (keyCode) { + case (40): { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = 0; + } else { + self.selectedOption = (self.selectedOption === elems.length - 1 ? + self.selectedOption : + self.selectedOption + 1); + } + break; + } + case (38): { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = elems.length - 1; + } else { + self.selectedOption = self.selectedOption === 0 ? + 0 : self.selectedOption - 1; + } + break; + } + } + elems[self.selectedOption].focus(); + }; + + scope.getDropdownElement = function() { + return self.dropdownMenu; + }; + scope.focusToggleElement = function() { - if ( self.toggleElement ) { + if (self.toggleElement) { self.toggleElement[0].focus(); } }; - scope.$watch('isOpen', function( isOpen, wasOpen ) { - $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); + scope.$watch('isOpen', function(isOpen, wasOpen) { + if (appendToBody && self.dropdownMenu) { + var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true); + var css = { + top: pos.top + 'px', + display: isOpen ? 'block' : 'none' + }; - if ( isOpen ) { - scope.focusToggleElement(); - dropdownService.open( scope ); - } else { - dropdownService.close( scope ); + var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right'); + if (!rightalign) { + css.left = pos.left + 'px'; + css.right = 'auto'; + } else { + css.left = 'auto'; + css.right = (window.innerWidth - (pos.left + $element.prop('offsetWidth'))) + 'px'; + } + + self.dropdownMenu.css(css); } - setIsOpen($scope, isOpen); - if (angular.isDefined(isOpen) && isOpen !== wasOpen) { - toggleInvoker($scope, { open: !!isOpen }); + $animate[isOpen ? 'addClass' : 'removeClass']($element, openClass).then(function() { + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + + if (isOpen) { + if (self.dropdownMenuTemplateUrl) { + $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) { + templateScope = scope.$new(); + $compile(tplContent.trim())(templateScope, function(dropdownElement) { + var newEl = dropdownElement; + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + }); + }); + } + + scope.focusToggleElement(); + uibDropdownService.open(scope); + } else { + if (self.dropdownMenuTemplateUrl) { + if (templateScope) { + templateScope.$destroy(); + } + var newEl = angular.element(''); + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + } + + uibDropdownService.close(scope); + self.selectedOption = null; + } + + if (angular.isFunction(setIsOpen)) { + setIsOpen($scope, isOpen); } }); $scope.$on('$locationChangeSuccess', function() { - scope.isOpen = false; + if (scope.getAutoClose() !== 'disabled') { + scope.isOpen = false; + } }); - $scope.$on('$destroy', function() { + var offDestroy = $scope.$on('$destroy', function() { scope.$destroy(); }); + scope.$on('$destroy', offDestroy); }]) -.directive('dropdown', function() { +.directive('uibDropdown', function() { return { - controller: 'DropdownController', + controller: 'UibDropdownController', link: function(scope, element, attrs, dropdownCtrl) { - dropdownCtrl.init( element ); + dropdownCtrl.init(); } }; }) -.directive('dropdownToggle', function() { +.directive('uibDropdownMenu', function() { return { - require: '?^dropdown', + restrict: 'AC', + require: '?^uibDropdown', link: function(scope, element, attrs, dropdownCtrl) { - if ( !dropdownCtrl ) { + if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) { return; } + element.addClass('dropdown-menu'); + + var tplUrl = attrs.templateUrl; + if (tplUrl) { + dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; + } + + if (!dropdownCtrl.dropdownMenu) { + dropdownCtrl.dropdownMenu = element; + } + } + }; +}) + +.directive('uibKeyboardNav', function() { + return { + restrict: 'A', + require: '?^uibDropdown', + link: function(scope, element, attrs, dropdownCtrl) { + element.bind('keydown', function(e) { + if ([38, 40].indexOf(e.which) !== -1) { + e.preventDefault(); + e.stopPropagation(); + + var elems = dropdownCtrl.dropdownMenu.find('a'); + + switch (e.which) { + case (40): { // Down + if (!angular.isNumber(dropdownCtrl.selectedOption)) { + dropdownCtrl.selectedOption = 0; + } else { + dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ? + dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1; + } + break; + } + case (38): { // Up + if (!angular.isNumber(dropdownCtrl.selectedOption)) { + dropdownCtrl.selectedOption = elems.length - 1; + } else { + dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ? + 0 : dropdownCtrl.selectedOption - 1; + } + break; + } + } + elems[dropdownCtrl.selectedOption].focus(); + } + }); + } + }; +}) + +.directive('uibDropdownToggle', function() { + return { + require: '?^uibDropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!dropdownCtrl) { + return; + } + + element.addClass('dropdown-toggle'); + dropdownCtrl.toggleElement = element; var toggleDropdown = function(event) { event.preventDefault(); - if ( !element.hasClass('disabled') && !attrs.disabled ) { + if (!element.hasClass('disabled') && !attrs.disabled) { scope.$apply(function() { dropdownCtrl.toggle(); }); @@ -1778,7 +3103,7 @@ angular.module('ui.bootstrap.dropdown', []) // WAI-ARIA element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); - scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { + scope.$watch(dropdownCtrl.isOpen, function(isOpen) { element.attr('aria-expanded', !!isOpen); }); @@ -1789,25 +3114,337 @@ angular.module('ui.bootstrap.dropdown', []) }; }); -angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) +/* Deprecated dropdown below */ +angular.module('ui.bootstrap.dropdown') + +.value('$dropdownSuppressWarning', false) + +.service('dropdownService', ['$log', '$dropdownSuppressWarning', 'uibDropdownService', function($log, $dropdownSuppressWarning, uibDropdownService) { + if (!$dropdownSuppressWarning) { + $log.warn('dropdownService is now deprecated. Use uibDropdownService instead.'); + } + + angular.extend(this, uibDropdownService); +}]) + +.controller('DropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', '$log', '$dropdownSuppressWarning', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest, $log, $dropdownSuppressWarning) { + if (!$dropdownSuppressWarning) { + $log.warn('DropdownController is now deprecated. Use UibDropdownController instead.'); + } + + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + templateScope, + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, + appendToBody = false, + keynavEnabled =false, + selectedOption = null; + + + $element.addClass('dropdown'); + + this.init = function() { + if ($attrs.isOpen) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + + appendToBody = angular.isDefined($attrs.dropdownAppendToBody); + keynavEnabled = angular.isDefined($attrs.uibKeyboardNav); + + if (appendToBody && self.dropdownMenu) { + $document.find('body').append(self.dropdownMenu); + $element.on('$destroy', function handleDestroyEvent() { + self.dropdownMenu.remove(); + }); + } + }; + + this.toggle = function(open) { + return scope.isOpen = arguments.length ? !!open : !scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.getToggleElement = function() { + return self.toggleElement; + }; + + scope.getAutoClose = function() { + return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' + }; + + scope.getElement = function() { + return $element; + }; + + scope.isKeynavEnabled = function() { + return keynavEnabled; + }; + + scope.focusDropdownEntry = function(keyCode) { + var elems = self.dropdownMenu ? //If append to body is used. + (angular.element(self.dropdownMenu).find('a')) : + (angular.element($element).find('ul').eq(0).find('a')); + + switch (keyCode) { + case (40): { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = 0; + } else { + self.selectedOption = (self.selectedOption === elems.length -1 ? + self.selectedOption : + self.selectedOption + 1); + } + break; + } + case (38): { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = elems.length - 1; + } else { + self.selectedOption = self.selectedOption === 0 ? + 0 : self.selectedOption - 1; + } + break; + } + } + elems[self.selectedOption].focus(); + }; + + scope.getDropdownElement = function() { + return self.dropdownMenu; + }; + + scope.focusToggleElement = function() { + if (self.toggleElement) { + self.toggleElement[0].focus(); + } + }; + + scope.$watch('isOpen', function(isOpen, wasOpen) { + if (appendToBody && self.dropdownMenu) { + var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true); + var css = { + top: pos.top + 'px', + display: isOpen ? 'block' : 'none' + }; + + var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right'); + if (!rightalign) { + css.left = pos.left + 'px'; + css.right = 'auto'; + } else { + css.left = 'auto'; + css.right = (window.innerWidth - (pos.left + $element.prop('offsetWidth'))) + 'px'; + } + + self.dropdownMenu.css(css); + } + + $animate[isOpen ? 'addClass' : 'removeClass']($element, openClass).then(function() { + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + + if (isOpen) { + if (self.dropdownMenuTemplateUrl) { + $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) { + templateScope = scope.$new(); + $compile(tplContent.trim())(templateScope, function(dropdownElement) { + var newEl = dropdownElement; + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + }); + }); + } + + scope.focusToggleElement(); + uibDropdownService.open(scope); + } else { + if (self.dropdownMenuTemplateUrl) { + if (templateScope) { + templateScope.$destroy(); + } + var newEl = angular.element(''); + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + } + + uibDropdownService.close(scope); + self.selectedOption = null; + } + + if (angular.isFunction(setIsOpen)) { + setIsOpen($scope, isOpen); + } + }); + + $scope.$on('$locationChangeSuccess', function() { + if (scope.getAutoClose() !== 'disabled') { + scope.isOpen = false; + } + }); + + var offDestroy = $scope.$on('$destroy', function() { + scope.$destroy(); + }); + scope.$on('$destroy', offDestroy); +}]) + +.directive('dropdown', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) { + return { + controller: 'DropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + if (!$dropdownSuppressWarning) { + $log.warn('dropdown is now deprecated. Use uib-dropdown instead.'); + } + + dropdownCtrl.init(); + } + }; +}]) + +.directive('dropdownMenu', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) { + return { + restrict: 'AC', + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) { + return; + } + + if (!$dropdownSuppressWarning) { + $log.warn('dropdown-menu is now deprecated. Use uib-dropdown-menu instead.'); + } + + element.addClass('dropdown-menu'); + + var tplUrl = attrs.templateUrl; + if (tplUrl) { + dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; + } + + if (!dropdownCtrl.dropdownMenu) { + dropdownCtrl.dropdownMenu = element; + } + } + }; +}]) + +.directive('keyboardNav', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) { + return { + restrict: 'A', + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!$dropdownSuppressWarning) { + $log.warn('keyboard-nav is now deprecated. Use uib-keyboard-nav instead.'); + } + + element.bind('keydown', function(e) { + if ([38, 40].indexOf(e.which) !== -1) { + e.preventDefault(); + e.stopPropagation(); + + var elems = dropdownCtrl.dropdownMenu.find('a'); + + switch (e.which) { + case (40): { // Down + if (!angular.isNumber(dropdownCtrl.selectedOption)) { + dropdownCtrl.selectedOption = 0; + } else { + dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ? + dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1; + } + break; + } + case (38): { // Up + if (!angular.isNumber(dropdownCtrl.selectedOption)) { + dropdownCtrl.selectedOption = elems.length - 1; + } else { + dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ? + 0 : dropdownCtrl.selectedOption - 1; + } + break; + } + } + elems[dropdownCtrl.selectedOption].focus(); + } + }); + } + }; +}]) + +.directive('dropdownToggle', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) { + return { + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!$dropdownSuppressWarning) { + $log.warn('dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead.'); + } + + if (!dropdownCtrl) { + return; + } + + element.addClass('dropdown-toggle'); + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if (!element.hasClass('disabled') && !attrs.disabled) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function(isOpen) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } + }; +}]); + +angular.module('ui.bootstrap.stackedMap', []) /** * A helper, internal data structure that acts as a map but also allows getting / removing * elements in the LIFO order */ - .factory('$$stackedMap', function () { + .factory('$$stackedMap', function() { return { - createNew: function () { + createNew: function() { var stack = []; return { - add: function (key, value) { + add: function(key, value) { stack.push({ key: key, value: value }); }, - get: function (key) { + get: function(key) { for (var i = 0; i < stack.length; i++) { if (key == stack[i].key) { return stack[i]; @@ -1821,10 +3458,10 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) } return keys; }, - top: function () { + top: function() { return stack[stack.length - 1]; }, - remove: function (key) { + remove: function(key) { var idx = -1; for (var i = 0; i < stack.length; i++) { if (key == stack[i].key) { @@ -1834,84 +3471,233 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) } return stack.splice(idx, 1)[0]; }, - removeTop: function () { + removeTop: function() { return stack.splice(stack.length - 1, 1)[0]; }, - length: function () { + length: function() { return stack.length; } }; } }; + }); +angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) +/** + * A helper, internal data structure that stores all references attached to key + */ + .factory('$$multiMap', function() { + return { + createNew: function() { + var map = {}; + + return { + entries: function() { + return Object.keys(map).map(function(key) { + return { + key: key, + value: map[key] + }; + }); + }, + get: function(key) { + return map[key]; + }, + hasKey: function(key) { + return !!map[key]; + }, + keys: function() { + return Object.keys(map); + }, + put: function(key, value) { + if (!map[key]) { + map[key] = []; + } + + map[key].push(value); + }, + remove: function(key, value) { + var values = map[key]; + + if (!values) { + return; + } + + var idx = values.indexOf(value); + + if (idx !== -1) { + values.splice(idx, 1); + } + + if (!values.length) { + delete map[key]; + } + } + }; + } + }; }) /** * A helper directive for the $modal service. It creates a backdrop element. */ - .directive('modalBackdrop', ['$timeout', function ($timeout) { + .directive('uibModalBackdrop', [ + '$animate', '$injector', '$uibModalStack', + function($animate , $injector, $modalStack) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + return { - restrict: 'EA', replace: true, templateUrl: 'template/modal/backdrop.html', - link: function (scope, element, attrs) { - scope.backdropClass = attrs.backdropClass || ''; - - scope.animate = false; - - //trigger CSS transitions - $timeout(function () { - scope.animate = true; - }); + compile: function(tElement, tAttrs) { + tElement.addClass(tAttrs.backdropClass); + return linkFn; } }; + + function linkFn(scope, element, attrs) { + // Temporary fix for prefixing + element.addClass('modal-backdrop'); + + if (attrs.modalInClass) { + if ($animateCss) { + $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + } else { + $animate.addClass(element, attrs.modalInClass); + } + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if ($animateCss) { + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + } else { + $animate.removeClass(element, attrs.modalInClass).then(done); + } + }); + } + } }]) - .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + .directive('uibModalWindow', [ + '$uibModalStack', '$q', '$animate', '$injector', + function($modalStack , $q , $animate, $injector) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + return { - restrict: 'EA', scope: { - index: '@', - animate: '=' + index: '@' }, replace: true, transclude: true, templateUrl: function(tElement, tAttrs) { return tAttrs.templateUrl || 'template/modal/window.html'; }, - link: function (scope, element, attrs) { + link: function(scope, element, attrs) { element.addClass(attrs.windowClass || ''); + element.addClass(attrs.windowTopClass || ''); scope.size = attrs.size; - $timeout(function () { - // trigger CSS transitions - scope.animate = true; - - /** - * Auto-focusing of a freshly-opened modal element causes any child elements - * with the autofocus attribute to lose focus. This is an issue on touch - * based devices which will show and then hide the onscreen keyboard. - * Attempts to refocus the autofocus element via JavaScript will not reopen - * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus - * the modal element if the modal does not contain an autofocus element. - */ - if (!element[0].querySelectorAll('[autofocus]').length) { - element[0].focus(); - } - }); - - scope.close = function (evt) { + scope.close = function(evt) { var modal = $modalStack.getTop(); - if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) { evt.preventDefault(); evt.stopPropagation(); $modalStack.dismiss(modal.key, 'backdrop click'); } }; + + // moved from template to fix issue #2280 + element.on('click', scope.close); + + // This property is only added to the scope for the purpose of detecting when this directive is rendered. + // We can detect that by using this property in the template associated with this directive and then use + // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}. + scope.$isRendered = true; + + // Deferred object that will be resolved when this modal is render. + var modalRenderDeferObj = $q.defer(); + // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready. + // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template. + attrs.$observe('modalRender', function(value) { + if (value == 'true') { + modalRenderDeferObj.resolve(); + } + }); + + modalRenderDeferObj.promise.then(function() { + var animationPromise = null; + + if (attrs.modalInClass) { + if ($animateCss) { + animationPromise = $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + } else { + animationPromise = $animate.addClass(element, attrs.modalInClass); + } + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if ($animateCss) { + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + } else { + $animate.removeClass(element, attrs.modalInClass).then(done); + } + }); + } + + + $q.when(animationPromise).then(function() { + var inputWithAutofocus = element[0].querySelector('[autofocus]'); + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to lose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (inputWithAutofocus) { + inputWithAutofocus.focus(); + } else { + element[0].focus(); + } + }); + + // Notify {@link $modalStack} that modal is rendered. + var modal = $modalStack.getTop(); + if (modal) { + $modalStack.modalRendered(modal.key); + } + }); } }; }]) - .directive('modalTransclude', function () { + .directive('uibModalAnimationClass', function() { + return { + compile: function(tElement, tAttrs) { + if (tAttrs.modalAnimation) { + tElement.addClass(tAttrs.uibModalAnimationClass); + } + } + }; + }) + + .directive('uibModalTransclude', function() { return { link: function($scope, $element, $attrs, controller, $transclude) { $transclude($scope.$parent, function(clone) { @@ -1922,14 +3708,38 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) }; }) - .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', - function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + .factory('$uibModalStack', [ + '$animate', '$timeout', '$document', '$compile', '$rootScope', + '$q', + '$injector', + '$$multiMap', + '$$stackedMap', + function($animate , $timeout , $document , $compile , $rootScope , + $q, + $injector, + $$multiMap, + $$stackedMap) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } var OPENED_MODAL_CLASS = 'modal-open'; var backdropDomEl, backdropScope; var openedWindows = $$stackedMap.createNew(); - var $modalStack = {}; + var openedClasses = $$multiMap.createNew(); + var $modalStack = { + NOW_CLOSING_EVENT: 'modal.stack.now-closing' + }; + + //Modal focus behavior + var focusableElementList; + var focusIndex = 0; + var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' + + 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' + + 'iframe, object, embed, *[tabindex], *[contenteditable=true]'; function backdropIndex() { var topBackdropIndex = -1; @@ -1942,59 +3752,76 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) return topBackdropIndex; } - $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + $rootScope.$watch(backdropIndex, function(newBackdropIndex) { if (backdropScope) { backdropScope.index = newBackdropIndex; } }); - function removeModalWindow(modalInstance) { - + function removeModalWindow(modalInstance, elementToReceiveFocus) { var body = $document.find('body').eq(0); var modalWindow = openedWindows.get(modalInstance).value; //clean up the stack openedWindows.remove(modalInstance); - //remove window DOM element - removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() { - modalWindow.modalScope.$destroy(); - body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); - checkRemoveBackdrop(); + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { + var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; + openedClasses.remove(modalBodyClass, modalInstance); + body.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass)); + toggleTopWindowClass(true); }); + checkRemoveBackdrop(); + + //move focus to specified element if available, or else to body + if (elementToReceiveFocus && elementToReceiveFocus.focus) { + elementToReceiveFocus.focus(); + } else { + body.focus(); + } + } + + // Add or remove "windowTopClass" from the top window in the stack + function toggleTopWindowClass(toggleSwitch) { + var modalWindow; + + if (openedWindows.length() > 0) { + modalWindow = openedWindows.top().value; + modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch); + } } function checkRemoveBackdrop() { - //remove backdrop if no longer needed - if (backdropDomEl && backdropIndex() == -1) { - var backdropScopeRef = backdropScope; - removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { - backdropScopeRef.$destroy(); - backdropScopeRef = null; - }); - backdropDomEl = undefined; - backdropScope = undefined; - } + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, function() { + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } } - function removeAfterAnimate(domEl, scope, emulateTime, done) { - // Closing animation - scope.animate = false; + function removeAfterAnimate(domEl, scope, done) { + var asyncDeferred; + var asyncPromise = null; + var setIsAsync = function() { + if (!asyncDeferred) { + asyncDeferred = $q.defer(); + asyncPromise = asyncDeferred.promise; + } - var transitionEndEventName = $transition.transitionEndEventName; - if (transitionEndEventName) { - // transition out - var timeout = $timeout(afterAnimating, emulateTime); + return function asyncDone() { + asyncDeferred.resolve(); + }; + }; + scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync); - domEl.bind(transitionEndEventName, function () { - $timeout.cancel(timeout); - afterAnimating(); - scope.$apply(); - }); - } else { - // Ensure this call is async - $timeout(afterAnimating); - } + // Note that it's intentional that asyncPromise might be null. + // That's when setIsAsync has not been called during the + // NOW_CLOSING_EVENT broadcast. + return $q.when(asyncPromise).then(afterAnimating); function afterAnimating() { if (afterAnimating.done) { @@ -2002,138 +3829,257 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) } afterAnimating.done = true; - domEl.remove(); + if ($animateCss) { + $animateCss(domEl, { + event: 'leave' + }).start().then(function() { + domEl.remove(); + }); + } else { + $animate.leave(domEl); + } + scope.$destroy(); if (done) { done(); } } } - $document.bind('keydown', function (evt) { - var modal; + $document.bind('keydown', function(evt) { + if (evt.isDefaultPrevented()) { + return evt; + } - if (evt.which === 27) { - modal = openedWindows.top(); - if (modal && modal.value.keyboard) { - evt.preventDefault(); - $rootScope.$apply(function () { - $modalStack.dismiss(modal.key, 'escape key press'); - }); + var modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + switch (evt.which) { + case 27: { + evt.preventDefault(); + $rootScope.$apply(function() { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + break; + } + case 9: { + $modalStack.loadFocusElementList(modal); + var focusChanged = false; + if (evt.shiftKey) { + if ($modalStack.isFocusInFirstItem(evt)) { + focusChanged = $modalStack.focusLastFocusableElement(); + } + } else { + if ($modalStack.isFocusInLastItem(evt)) { + focusChanged = $modalStack.focusFirstFocusableElement(); + } + } + + if (focusChanged) { + evt.preventDefault(); + evt.stopPropagation(); + } + break; + } } } }); - $modalStack.open = function (modalInstance, modal) { + $modalStack.open = function(modalInstance, modal) { + var modalOpener = $document[0].activeElement, + modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; + + toggleTopWindowClass(false); openedWindows.add(modalInstance, { deferred: modal.deferred, + renderDeferred: modal.renderDeferred, modalScope: modal.scope, backdrop: modal.backdrop, - keyboard: modal.keyboard + keyboard: modal.keyboard, + openedClass: modal.openedClass, + windowTopClass: modal.windowTopClass }); + openedClasses.put(modalBodyClass, modalInstance); + var body = $document.find('body').eq(0), currBackdropIndex = backdropIndex(); if (currBackdropIndex >= 0 && !backdropDomEl) { backdropScope = $rootScope.$new(true); backdropScope.index = currBackdropIndex; - var angularBackgroundDomEl = angular.element('
    '); + var angularBackgroundDomEl = angular.element('
    '); angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); + if (modal.animation) { + angularBackgroundDomEl.attr('modal-animation', 'true'); + } backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope); body.append(backdropDomEl); } - var angularDomEl = angular.element('
    '); + var angularDomEl = angular.element('
    '); angularDomEl.attr({ 'template-url': modal.windowTemplateUrl, 'window-class': modal.windowClass, + 'window-top-class': modal.windowTopClass, 'size': modal.size, 'index': openedWindows.length() - 1, 'animate': 'animate' }).html(modal.content); + if (modal.animation) { + angularDomEl.attr('modal-animation', 'true'); + } var modalDomEl = $compile(angularDomEl)(modal.scope); openedWindows.top().value.modalDomEl = modalDomEl; + openedWindows.top().value.modalOpener = modalOpener; body.append(modalDomEl); - body.addClass(OPENED_MODAL_CLASS); + body.addClass(modalBodyClass); + + $modalStack.clearFocusListCache(); }; - $modalStack.close = function (modalInstance, result) { + function broadcastClosing(modalWindow, resultOrReason, closing) { + return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; + } + + $modalStack.close = function(modalInstance, result) { var modalWindow = openedWindows.get(modalInstance); - if (modalWindow) { + if (modalWindow && broadcastClosing(modalWindow, result, true)) { + modalWindow.value.modalScope.$$uibDestructionScheduled = true; modalWindow.value.deferred.resolve(result); - removeModalWindow(modalInstance); + removeModalWindow(modalInstance, modalWindow.value.modalOpener); + return true; } + return !modalWindow; }; - $modalStack.dismiss = function (modalInstance, reason) { + $modalStack.dismiss = function(modalInstance, reason) { var modalWindow = openedWindows.get(modalInstance); - if (modalWindow) { + if (modalWindow && broadcastClosing(modalWindow, reason, false)) { + modalWindow.value.modalScope.$$uibDestructionScheduled = true; modalWindow.value.deferred.reject(reason); - removeModalWindow(modalInstance); + removeModalWindow(modalInstance, modalWindow.value.modalOpener); + return true; } + return !modalWindow; }; - $modalStack.dismissAll = function (reason) { + $modalStack.dismissAll = function(reason) { var topModal = this.getTop(); - while (topModal) { - this.dismiss(topModal.key, reason); + while (topModal && this.dismiss(topModal.key, reason)) { topModal = this.getTop(); } }; - $modalStack.getTop = function () { + $modalStack.getTop = function() { return openedWindows.top(); }; + $modalStack.modalRendered = function(modalInstance) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.renderDeferred.resolve(); + } + }; + + $modalStack.focusFirstFocusableElement = function() { + if (focusableElementList.length > 0) { + focusableElementList[0].focus(); + return true; + } + return false; + }; + $modalStack.focusLastFocusableElement = function() { + if (focusableElementList.length > 0) { + focusableElementList[focusableElementList.length - 1].focus(); + return true; + } + return false; + }; + + $modalStack.isFocusInFirstItem = function(evt) { + if (focusableElementList.length > 0) { + return (evt.target || evt.srcElement) == focusableElementList[0]; + } + return false; + }; + + $modalStack.isFocusInLastItem = function(evt) { + if (focusableElementList.length > 0) { + return (evt.target || evt.srcElement) == focusableElementList[focusableElementList.length - 1]; + } + return false; + }; + + $modalStack.clearFocusListCache = function() { + focusableElementList = []; + focusIndex = 0; + }; + + $modalStack.loadFocusElementList = function(modalWindow) { + if (focusableElementList === undefined || !focusableElementList.length) { + if (modalWindow) { + var modalDomE1 = modalWindow.value.modalDomEl; + if (modalDomE1 && modalDomE1.length) { + focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector); + } + } + } + }; + return $modalStack; }]) - .provider('$modal', function () { - + .provider('$uibModal', function() { var $modalProvider = { options: { - backdrop: true, //can be also false or 'static' + animation: true, + backdrop: true, //can also be false or 'static' keyboard: true }, - $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', - function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { - + $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$uibModalStack', '$modalSuppressWarning', '$log', + function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack, $modalSuppressWarning, $log) { var $modal = {}; function getTemplatePromise(options) { return options.template ? $q.when(options.template) : - $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl, - {cache: $templateCache}).then(function (result) { - return result.data; - }); + $templateRequest(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl); } function getResolvePromises(resolves) { var promisesArr = []; - angular.forEach(resolves, function (value) { + angular.forEach(resolves, function(value) { if (angular.isFunction(value) || angular.isArray(value)) { promisesArr.push($q.when($injector.invoke(value))); + } else if (angular.isString(value)) { + promisesArr.push($q.when($injector.get(value))); + } else { + promisesArr.push($q.when(value)); } }); return promisesArr; } - $modal.open = function (modalOptions) { + var promiseChain = null; + $modal.getPromiseChain = function() { + return promiseChain; + }; + $modal.open = function(modalOptions) { var modalResultDeferred = $q.defer(); var modalOpenedDeferred = $q.defer(); + var modalRenderDeferred = $q.defer(); //prepare an instance of a modal to be injected into controllers and returned to a caller var modalInstance = { result: modalResultDeferred.promise, opened: modalOpenedDeferred.promise, + rendered: modalRenderDeferred.promise, close: function (result) { - $modalStack.close(modalInstance, result); + return $modalStack.close(modalInstance, result); }, dismiss: function (reason) { - $modalStack.dismiss(modalInstance, reason); + return $modalStack.dismiss(modalInstance, reason); } }; @@ -2149,65 +4095,328 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) var templateAndResolvePromise = $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + function resolveWithTemplate() { + return templateAndResolvePromise; + } - templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + // Wait for the resolution of the existing promise chain. + // Then switch to our own combined promise dependency (regardless of how the previous modal fared). + // Then add to $modalStack and resolve opened. + // Finally clean up the chain variable if no subsequent modal has overwritten it. + var samePromise; + samePromise = promiseChain = $q.all([promiseChain]) + .then(resolveWithTemplate, resolveWithTemplate) + .then(function resolveSuccess(tplAndVars) { - var modalScope = (modalOptions.scope || $rootScope).$new(); - modalScope.$close = modalInstance.close; - modalScope.$dismiss = modalInstance.dismiss; + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; - var ctrlInstance, ctrlLocals = {}; - var resolveIter = 1; - - //controllers - if (modalOptions.controller) { - ctrlLocals.$scope = modalScope; - ctrlLocals.$modalInstance = modalInstance; - angular.forEach(modalOptions.resolve, function (value, key) { - ctrlLocals[key] = tplAndVars[resolveIter++]; + modalScope.$on('$destroy', function() { + if (!modalScope.$$uibDestructionScheduled) { + modalScope.$dismiss('$uibUnscheduledDestruction'); + } }); - ctrlInstance = $controller(modalOptions.controller, ctrlLocals); - if (modalOptions.controllerAs) { - modalScope[modalOptions.controllerAs] = ctrlInstance; - } - } + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; - $modalStack.open(modalInstance, { - scope: modalScope, - deferred: modalResultDeferred, - content: tplAndVars[0], - backdrop: modalOptions.backdrop, - keyboard: modalOptions.keyboard, - backdropClass: modalOptions.backdropClass, - windowClass: modalOptions.windowClass, - windowTemplateUrl: modalOptions.windowTemplateUrl, - size: modalOptions.size - }); + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$uibModalInstance = modalInstance; + Object.defineProperty(ctrlLocals, '$modalInstance', { + get: function() { + if (!$modalSuppressWarning) { + $log.warn('$modalInstance is now deprecated. Use $uibModalInstance instead.'); + } + + return modalInstance; + } + }); + angular.forEach(modalOptions.resolve, function(value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + if (modalOptions.controllerAs) { + if (modalOptions.bindToController) { + angular.extend(ctrlInstance, modalScope); + } + + modalScope[modalOptions.controllerAs] = ctrlInstance; + } + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + renderDeferred: modalRenderDeferred, + content: tplAndVars[0], + animation: modalOptions.animation, + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + backdropClass: modalOptions.backdropClass, + windowTopClass: modalOptions.windowTopClass, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size, + openedClass: modalOptions.openedClass + }); + modalOpenedDeferred.resolve(true); }, function resolveError(reason) { + modalOpenedDeferred.reject(reason); modalResultDeferred.reject(reason); - }); - - templateAndResolvePromise.then(function () { - modalOpenedDeferred.resolve(true); - }, function () { - modalOpenedDeferred.reject(false); + }) + .finally(function() { + if (promiseChain === samePromise) { + promiseChain = null; + } }); return modalInstance; }; return $modal; - }] + } + ] }; return $modalProvider; }); -angular.module('ui.bootstrap.pagination', []) +/* deprecated modal below */ -.controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) { +angular.module('ui.bootstrap.modal') + + .value('$modalSuppressWarning', false) + + /** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', [ + '$animate', '$injector', '$modalStack', '$log', '$modalSuppressWarning', + function($animate , $injector, $modalStack, $log, $modalSuppressWarning) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + + return { + replace: true, + templateUrl: 'template/modal/backdrop.html', + compile: function(tElement, tAttrs) { + tElement.addClass(tAttrs.backdropClass); + return linkFn; + } + }; + + function linkFn(scope, element, attrs) { + if (!$modalSuppressWarning) { + $log.warn('modal-backdrop is now deprecated. Use uib-modal-backdrop instead.'); + } + element.addClass('modal-backdrop'); + + if (attrs.modalInClass) { + if ($animateCss) { + $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + } else { + $animate.addClass(element, attrs.modalInClass); + } + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if ($animateCss) { + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + } else { + $animate.removeClass(element, attrs.modalInClass).then(done); + } + }); + } + } + }]) + + .directive('modalWindow', [ + '$modalStack', '$q', '$animate', '$injector', '$log', '$modalSuppressWarning', + function($modalStack , $q , $animate, $injector, $log, $modalSuppressWarning) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + + return { + scope: { + index: '@' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'template/modal/window.html'; + }, + link: function(scope, element, attrs) { + if (!$modalSuppressWarning) { + $log.warn('modal-window is now deprecated. Use uib-modal-window instead.'); + } + element.addClass(attrs.windowClass || ''); + element.addClass(attrs.windowTopClass || ''); + scope.size = attrs.size; + + scope.close = function(evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + + // moved from template to fix issue #2280 + element.on('click', scope.close); + + // This property is only added to the scope for the purpose of detecting when this directive is rendered. + // We can detect that by using this property in the template associated with this directive and then use + // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}. + scope.$isRendered = true; + + // Deferred object that will be resolved when this modal is render. + var modalRenderDeferObj = $q.defer(); + // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready. + // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template. + attrs.$observe('modalRender', function(value) { + if (value == 'true') { + modalRenderDeferObj.resolve(); + } + }); + + modalRenderDeferObj.promise.then(function() { + var animationPromise = null; + + if (attrs.modalInClass) { + if ($animateCss) { + animationPromise = $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + } else { + animationPromise = $animate.addClass(element, attrs.modalInClass); + } + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if ($animateCss) { + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + } else { + $animate.removeClass(element, attrs.modalInClass).then(done); + } + }); + } + + + $q.when(animationPromise).then(function() { + var inputWithAutofocus = element[0].querySelector('[autofocus]'); + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to lose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (inputWithAutofocus) { + inputWithAutofocus.focus(); + } else { + element[0].focus(); + } + }); + + // Notify {@link $modalStack} that modal is rendered. + var modal = $modalStack.getTop(); + if (modal) { + $modalStack.modalRendered(modal.key); + } + }); + } + }; + }]) + + .directive('modalAnimationClass', [ + '$log', '$modalSuppressWarning', + function ($log, $modalSuppressWarning) { + return { + compile: function(tElement, tAttrs) { + if (!$modalSuppressWarning) { + $log.warn('modal-animation-class is now deprecated. Use uib-modal-animation-class instead.'); + } + if (tAttrs.modalAnimation) { + tElement.addClass(tAttrs.modalAnimationClass); + } + } + }; + }]) + + .directive('modalTransclude', [ + '$log', '$modalSuppressWarning', + function ($log, $modalSuppressWarning) { + return { + link: function($scope, $element, $attrs, controller, $transclude) { + if (!$modalSuppressWarning) { + $log.warn('modal-transclude is now deprecated. Use uib-modal-transclude instead.'); + } + $transclude($scope.$parent, function(clone) { + $element.empty(); + $element.append(clone); + }); + } + }; + }]) + + .service('$modalStack', [ + '$animate', '$timeout', '$document', '$compile', '$rootScope', + '$q', + '$injector', + '$$multiMap', + '$$stackedMap', + '$uibModalStack', + '$log', + '$modalSuppressWarning', + function($animate , $timeout , $document , $compile , $rootScope , + $q, + $injector, + $$multiMap, + $$stackedMap, + $uibModalStack, + $log, + $modalSuppressWarning) { + if (!$modalSuppressWarning) { + $log.warn('$modalStack is now deprecated. Use $uibModalStack instead.'); + } + + angular.extend(this, $uibModalStack); + }]) + + .provider('$modal', ['$uibModalProvider', function($uibModalProvider) { + angular.extend(this, $uibModalProvider); + + this.$get = ['$injector', '$log', '$modalSuppressWarning', + function ($injector, $log, $modalSuppressWarning) { + if (!$modalSuppressWarning) { + $log.warn('$modal is now deprecated. Use $uibModal instead.'); + } + + return $injector.invoke($uibModalProvider.$get); + }]; + }]); + +angular.module('ui.bootstrap.pagination', []) +.controller('UibPaginationController', ['$scope', '$attrs', '$parse', function($scope, $attrs, $parse) { var self = this, ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; @@ -2228,6 +4437,20 @@ angular.module('ui.bootstrap.pagination', []) } else { this.itemsPerPage = config.itemsPerPage; } + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( $scope.page > value ) { + $scope.selectPage(value); + } else { + ngModelCtrl.$render(); + } + }); }; this.calculateTotalPages = function() { @@ -2239,39 +4462,35 @@ angular.module('ui.bootstrap.pagination', []) $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; }; - $scope.selectPage = function(page) { - if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) { + $scope.selectPage = function(page, evt) { + if (evt) { + evt.preventDefault(); + } + + var clickAllowed = !$scope.ngDisabled || !evt; + if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) { + if (evt && evt.target) { + evt.target.blur(); + } ngModelCtrl.$setViewValue(page); ngModelCtrl.$render(); } }; - $scope.getText = function( key ) { + $scope.getText = function(key) { return $scope[key + 'Text'] || self.config[key + 'Text']; }; + $scope.noPrevious = function() { return $scope.page === 1; }; + $scope.noNext = function() { return $scope.page === $scope.totalPages; }; - - $scope.$watch('totalItems', function() { - $scope.totalPages = self.calculateTotalPages(); - }); - - $scope.$watch('totalPages', function(value) { - setNumPages($scope.$parent, value); // Readonly variable - - if ( $scope.page > value ) { - $scope.selectPage(value); - } else { - ngModelCtrl.$render(); - } - }); }]) -.constant('paginationConfig', { +.constant('uibPaginationConfig', { itemsPerPage: 10, boundaryLinks: false, directionLinks: true, @@ -2282,7 +4501,7 @@ angular.module('ui.bootstrap.pagination', []) rotate: true }) -.directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) { +.directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, paginationConfig) { return { restrict: 'EA', scope: { @@ -2290,11 +4509,15 @@ angular.module('ui.bootstrap.pagination', []) firstText: '@', previousText: '@', nextText: '@', - lastText: '@' + lastText: '@', + ngDisabled:'=' + }, + require: ['uibPagination', '?ngModel'], + controller: 'UibPaginationController', + controllerAs: 'pagination', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/pagination/pagination.html'; }, - require: ['pagination', '?ngModel'], - controller: 'PaginationController', - templateUrl: 'template/pagination/pagination.html', replace: true, link: function(scope, element, attrs, ctrls) { var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; @@ -2332,11 +4555,11 @@ angular.module('ui.bootstrap.pagination', []) // Default page limits var startPage = 1, endPage = totalPages; - var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); + var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages; // recompute if maxSize - if ( isMaxSized ) { - if ( rotate ) { + if (isMaxSized) { + if (rotate) { // Current page is displayed in the middle of the visible ones startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); endPage = startPage + maxSize - 1; @@ -2362,13 +4585,13 @@ angular.module('ui.bootstrap.pagination', []) } // Add links to move between page sets - if ( isMaxSized && ! rotate ) { - if ( startPage > 1 ) { + if (isMaxSized && ! rotate) { + if (startPage > 1) { var previousPageSet = makePage(startPage - 1, '...', false); pages.unshift(previousPageSet); } - if ( endPage < totalPages ) { + if (endPage < totalPages) { var nextPageSet = makePage(endPage + 1, '...', false); pages.push(nextPageSet); } @@ -2388,26 +4611,256 @@ angular.module('ui.bootstrap.pagination', []) }; }]) -.constant('pagerConfig', { +.constant('uibPagerConfig', { itemsPerPage: 10, previousText: '« Previous', nextText: 'Next »', align: true }) -.directive('pager', ['pagerConfig', function(pagerConfig) { +.directive('uibPager', ['uibPagerConfig', function(pagerConfig) { return { restrict: 'EA', scope: { totalItems: '=', previousText: '@', - nextText: '@' + nextText: '@', + ngDisabled: '=' + }, + require: ['uibPager', '?ngModel'], + controller: 'UibPaginationController', + controllerAs: 'pagination', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/pagination/pager.html'; + }, + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align; + paginationCtrl.init(ngModelCtrl, pagerConfig); + } + }; +}]); + +/* Deprecated Pagination Below */ + +angular.module('ui.bootstrap.pagination') +.value('$paginationSuppressWarning', false) +.controller('PaginationController', ['$scope', '$attrs', '$parse', '$log', '$paginationSuppressWarning', function($scope, $attrs, $parse, $log, $paginationSuppressWarning) { + if (!$paginationSuppressWarning) { + $log.warn('PaginationController is now deprecated. Use UibPaginationController instead.'); + } + + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; + + this.init = function(ngModelCtrl_, config) { + ngModelCtrl = ngModelCtrl_; + this.config = config; + + ngModelCtrl.$render = function() { + self.render(); + }; + + if ($attrs.itemsPerPage) { + $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { + self.itemsPerPage = parseInt(value, 10); + $scope.totalPages = self.calculateTotalPages(); + }); + } else { + this.itemsPerPage = config.itemsPerPage; + } + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( $scope.page > value ) { + $scope.selectPage(value); + } else { + ngModelCtrl.$render(); + } + }); + }; + + this.calculateTotalPages = function() { + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); + }; + + this.render = function() { + $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; + }; + + $scope.selectPage = function(page, evt) { + if (evt) { + evt.preventDefault(); + } + + var clickAllowed = !$scope.ngDisabled || !evt; + if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) { + if (evt && evt.target) { + evt.target.blur(); + } + ngModelCtrl.$setViewValue(page); + ngModelCtrl.$render(); + } + }; + + $scope.getText = function(key) { + return $scope[key + 'Text'] || self.config[key + 'Text']; + }; + + $scope.noPrevious = function() { + return $scope.page === 1; + }; + + $scope.noNext = function() { + return $scope.page === $scope.totalPages; + }; +}]) +.directive('pagination', ['$parse', 'uibPaginationConfig', '$log', '$paginationSuppressWarning', function($parse, paginationConfig, $log, $paginationSuppressWarning) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + firstText: '@', + previousText: '@', + nextText: '@', + lastText: '@', + ngDisabled:'=' + }, + require: ['pagination', '?ngModel'], + controller: 'PaginationController', + controllerAs: 'pagination', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/pagination/pagination.html'; + }, + replace: true, + link: function(scope, element, attrs, ctrls) { + if (!$paginationSuppressWarning) { + $log.warn('pagination is now deprecated. Use uib-pagination instead.'); + } + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + // Setup configuration parameters + var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize, + rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate; + scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; + scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks; + + paginationCtrl.init(ngModelCtrl, paginationConfig); + + if (attrs.maxSize) { + scope.$parent.$watch($parse(attrs.maxSize), function(value) { + maxSize = parseInt(value, 10); + paginationCtrl.render(); + }); + } + + // Create page object used in template + function makePage(number, text, isActive) { + return { + number: number, + text: text, + active: isActive + }; + } + + function getPages(currentPage, totalPages) { + var pages = []; + + // Default page limits + var startPage = 1, endPage = totalPages; + var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages; + + // recompute if maxSize + if (isMaxSized) { + if (rotate) { + // Current page is displayed in the middle of the visible ones + startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); + endPage = startPage + maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > totalPages) { + endPage = totalPages; + startPage = endPage - maxSize + 1; + } + } else { + // Visible pages are paginated with maxSize + startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; + + // Adjust last page if limit is exceeded + endPage = Math.min(startPage + maxSize - 1, totalPages); + } + } + + // Add page number links + for (var number = startPage; number <= endPage; number++) { + var page = makePage(number, number, number === currentPage); + pages.push(page); + } + + // Add links to move between page sets + if (isMaxSized && ! rotate) { + if (startPage > 1) { + var previousPageSet = makePage(startPage - 1, '...', false); + pages.unshift(previousPageSet); + } + + if (endPage < totalPages) { + var nextPageSet = makePage(endPage + 1, '...', false); + pages.push(nextPageSet); + } + } + + return pages; + } + + var originalRender = paginationCtrl.render; + paginationCtrl.render = function() { + originalRender(); + if (scope.page > 0 && scope.page <= scope.totalPages) { + scope.pages = getPages(scope.page, scope.totalPages); + } + }; + } + }; +}]) + +.directive('pager', ['uibPagerConfig', '$log', '$paginationSuppressWarning', function(pagerConfig, $log, $paginationSuppressWarning) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + previousText: '@', + nextText: '@', + ngDisabled: '=' }, require: ['pager', '?ngModel'], controller: 'PaginationController', - templateUrl: 'template/pagination/pager.html', + controllerAs: 'pagination', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/pagination/pager.html'; + }, replace: true, link: function(scope, element, attrs, ctrls) { + if (!$paginationSuppressWarning) { + $log.warn('pager is now deprecated. Use uib-pager instead.'); + } var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; if (!ngModelCtrl) { @@ -2425,25 +4878,28 @@ angular.module('ui.bootstrap.pagination', []) * function, placement as a function, inside, support for more triggers than * just mouse enter/leave, html tooltips, and selector delegation. */ -angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) +angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap']) /** * The $tooltip service creates tooltip- and popover-like directives as well as * houses global options for them. */ -.provider( '$tooltip', function () { +.provider('$uibTooltip', function() { // The default options tooltip and popover. var defaultOptions = { placement: 'top', animation: true, - popupDelay: 0 + popupDelay: 0, + popupCloseDelay: 0, + useContentExp: false }; // Default hide triggers for each show trigger var triggerMap = { 'mouseenter': 'mouseleave', 'click': 'click', - 'focus': 'blur' + 'focus': 'blur', + 'none': '' }; // The options specified to the provider globally. @@ -2458,8 +4914,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * $tooltipProvider.options( { placement: 'left' } ); * }); */ - this.options = function( value ) { - angular.extend( globalOptions, value ); + this.options = function(value) { + angular.extend(globalOptions, value); }; /** @@ -2467,14 +4923,14 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); */ - this.setTriggers = function setTriggers ( triggers ) { - angular.extend( triggerMap, triggers ); + this.setTriggers = function setTriggers(triggers) { + angular.extend(triggerMap, triggers); }; /** * This is a helper function for translating camel-case to snake-case. */ - function snake_case(name){ + function snake_case(name) { var regexp = /[A-Z]/g; var separator = '-'; return name.replace(regexp, function(letter, pos) { @@ -2486,9 +4942,21 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * Returns the actual instance of the $tooltip service. * TODO support multiple triggers */ - this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) { - return function $tooltip ( type, prefix, defaultTriggerShow ) { - var options = angular.extend( {}, defaultOptions, globalOptions ); + this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) { + var openedTooltips = $$stackedMap.createNew(); + $document.on('keypress', function(e) { + if (e.which === 27) { + var last = openedTooltips.top(); + if (last) { + last.value.close(); + openedTooltips.removeTop(); + last = null; + } + } + }); + + return function $tooltip(ttType, prefix, defaultTriggerShow, options) { + options = angular.extend({}, defaultOptions, globalOptions, options); /** * Returns an object of show and hide triggers. @@ -2504,60 +4972,89 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * undefined; otherwise, it uses the `triggerMap` value of the show * trigger; else it will just use the show trigger. */ - function getTriggers ( trigger ) { - var show = trigger || options.trigger || defaultTriggerShow; - var hide = triggerMap[show] || show; + function getTriggers(trigger) { + var show = (trigger || options.trigger || defaultTriggerShow).split(' '); + var hide = show.map(function(trigger) { + return triggerMap[trigger] || trigger; + }); return { show: show, hide: hide }; } - var directiveName = snake_case( type ); + var directiveName = snake_case(ttType); var startSym = $interpolate.startSymbol(); var endSym = $interpolate.endSymbol(); var template = - '
    '+ + '
    ' + '
    '; return { - restrict: 'EA', - compile: function (tElem, tAttrs) { - var tooltipLinker = $compile( template ); + compile: function(tElem, tAttrs) { + var tooltipLinker = $compile(template); - return function link ( scope, element, attrs ) { + return function link(scope, element, attrs, tooltipCtrl) { var tooltip; var tooltipLinkedScope; var transitionTimeout; - var popupTimeout; - var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; - var triggers = getTriggers( undefined ); - var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + var showTimeout; + var hideTimeout; + var positionTimeout; + var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false; + var triggers = getTriggers(undefined); + var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']); var ttScope = scope.$new(true); + var repositionScheduled = false; + var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false; + var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false; + var observers = []; - var positionTooltip = function () { + var positionTooltip = function() { + // check if tooltip exists and is not empty + if (!tooltip || !tooltip.html()) { return; } - var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); - ttPosition.top += 'px'; - ttPosition.left += 'px'; + if (!positionTimeout) { + positionTimeout = $timeout(function() { + // Reset the positioning. + tooltip.css({ top: 0, left: 0 }); - // Now set the calculated positioning. - tooltip.css( ttPosition ); + // Now set the calculated positioning. + var ttCss = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); + ttCss.top += 'px'; + ttCss.left += 'px'; + ttCss.visibility = 'visible'; + tooltip.css(ttCss); + + positionTimeout = null; + }, 0, false); + } }; + // Set up the correct scope to allow transclusion later + ttScope.origScope = scope; + // By default, the tooltip is not open. // TODO add ability to start tooltip opened ttScope.isOpen = false; + openedTooltips.add(ttScope, { + close: hide + }); - function toggleTooltipBind () { - if ( ! ttScope.isOpen ) { + function toggleTooltipBind() { + if (!ttScope.isOpen) { showTooltipBind(); } else { hideTooltipBind(); @@ -2566,101 +5063,127 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // Show the tooltip with delay if specified, otherwise show it immediately function showTooltipBind() { - if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) { return; } + cancelHide(); prepareTooltip(); - if ( ttScope.popupDelay ) { + if (ttScope.popupDelay) { // Do nothing if the tooltip was already scheduled to pop-up. // This happens if show is triggered multiple times before any hide is triggered. - if (!popupTimeout) { - popupTimeout = $timeout( show, ttScope.popupDelay, false ); - popupTimeout.then(function(reposition){reposition();}); + if (!showTimeout) { + showTimeout = $timeout(show, ttScope.popupDelay, false); } } else { - show()(); + show(); } } - function hideTooltipBind () { - scope.$apply(function () { + function hideTooltipBind() { + cancelShow(); + + if (ttScope.popupCloseDelay) { + if (!hideTimeout) { + hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false); + } + } else { hide(); - }); + } } // Show the tooltip popup element. function show() { - - popupTimeout = null; - - // If there is a pending remove transition, we must cancel it, lest the - // tooltip be mysteriously removed. - if ( transitionTimeout ) { - $timeout.cancel( transitionTimeout ); - transitionTimeout = null; - } + cancelShow(); + cancelHide(); // Don't show empty tooltips. - if ( ! ttScope.content ) { + if (!ttScope.content) { return angular.noop; } createTooltip(); - // Set the initial positioning. - tooltip.css({ top: 0, left: 0, display: 'block' }); - ttScope.$digest(); - - positionTooltip(); - // And show the tooltip. - ttScope.isOpen = true; - ttScope.$digest(); // digest required as $apply is not called + ttScope.$evalAsync(function() { + ttScope.isOpen = true; + assignIsOpen(true); + positionTooltip(); + }); + } - // Return positioning function as promise callback for correct - // positioning after draw. - return positionTooltip; + function cancelShow() { + if (showTimeout) { + $timeout.cancel(showTimeout); + showTimeout = null; + } + + if (positionTimeout) { + $timeout.cancel(positionTimeout); + positionTimeout = null; + } } // Hide the tooltip popup element. function hide() { + cancelShow(); + cancelHide(); + + if (!ttScope) { + return; + } + // First things first: we don't show it anymore. - ttScope.isOpen = false; - - //if tooltip is going to be shown after delay, we must cancel this - $timeout.cancel( popupTimeout ); - popupTimeout = null; - - // And now we remove it from the DOM. However, if we have animation, we - // need to wait for it to expire beforehand. - // FIXME: this is a placeholder for a port of the transitions library. - if ( ttScope.animation ) { - if (!transitionTimeout) { - transitionTimeout = $timeout(removeTooltip, 500); + ttScope.$evalAsync(function() { + ttScope.isOpen = false; + assignIsOpen(false); + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + // The fade transition in TWBS is 150ms. + if (ttScope.animation) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 150, false); + } + } else { + removeTooltip(); } - } else { - removeTooltip(); + }); + } + + function cancelHide() { + if (hideTimeout) { + $timeout.cancel(hideTimeout); + hideTimeout = null; + } + if (transitionTimeout) { + $timeout.cancel(transitionTimeout); + transitionTimeout = null; } } function createTooltip() { // There can only be one tooltip element per directive shown at once. if (tooltip) { - removeTooltip(); + return; } + tooltipLinkedScope = ttScope.$new(); - tooltip = tooltipLinker(tooltipLinkedScope, function (tooltip) { - if ( appendToBody ) { - $document.find( 'body' ).append( tooltip ); + tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) { + if (appendToBody) { + $document.find('body').append(tooltip); } else { - element.after( tooltip ); + element.after(tooltip); } }); + + prepObservers(); } function removeTooltip() { + unregisterObservers(); + transitionTimeout = null; if (tooltip) { tooltip.remove(); @@ -2672,55 +5195,166 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap } } + /** + * Set the inital scope values. Once + * the tooltip is created, the observers + * will be added to keep things in synch. + */ function prepareTooltip() { - prepPlacement(); - prepPopupDelay(); + ttScope.title = attrs[prefix + 'Title']; + if (contentParse) { + ttScope.content = contentParse(scope); + } else { + ttScope.content = attrs[ttType]; + } + + ttScope.popupClass = attrs[prefix + 'Class']; + ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement; + + var delay = parseInt(attrs[prefix + 'PopupDelay'], 10); + var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10); + ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay; + ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay; } + function assignIsOpen(isOpen) { + if (isOpenParse && angular.isFunction(isOpenParse.assign)) { + isOpenParse.assign(scope, isOpen); + } + } + + ttScope.contentExp = function() { + return ttScope.content; + }; + /** * Observe the relevant attributes. */ - attrs.$observe( type, function ( val ) { - ttScope.content = val; + attrs.$observe('disabled', function(val) { + if (val) { + cancelShow(); + } - if (!val && ttScope.isOpen ) { + if (val && ttScope.isOpen) { hide(); } }); - attrs.$observe( prefix+'Title', function ( val ) { - ttScope.title = val; - }); - - function prepPlacement() { - var val = attrs[ prefix + 'Placement' ]; - ttScope.placement = angular.isDefined( val ) ? val : options.placement; + if (isOpenParse) { + scope.$watch(isOpenParse, function(val) { + /*jshint -W018 */ + if (ttScope && !val === ttScope.isOpen) { + toggleTooltipBind(); + } + /*jshint +W018 */ + }); } - function prepPopupDelay() { - var val = attrs[ prefix + 'PopupDelay' ]; - var delay = parseInt( val, 10 ); - ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + function prepObservers() { + observers.length = 0; + + if (contentParse) { + observers.push( + scope.$watch(contentParse, function(val) { + ttScope.content = val; + if (!val && ttScope.isOpen) { + hide(); + } + }) + ); + + observers.push( + tooltipLinkedScope.$watch(function() { + if (!repositionScheduled) { + repositionScheduled = true; + tooltipLinkedScope.$$postDigest(function() { + repositionScheduled = false; + if (ttScope && ttScope.isOpen) { + positionTooltip(); + } + }); + } + }) + ); + } else { + observers.push( + attrs.$observe(ttType, function(val) { + ttScope.content = val; + if (!val && ttScope.isOpen) { + hide(); + } else { + positionTooltip(); + } + }) + ); + } + + observers.push( + attrs.$observe(prefix + 'Title', function(val) { + ttScope.title = val; + if (ttScope.isOpen) { + positionTooltip(); + } + }) + ); + + observers.push( + attrs.$observe(prefix + 'Placement', function(val) { + ttScope.placement = val ? val : options.placement; + if (ttScope.isOpen) { + positionTooltip(); + } + }) + ); } - var unregisterTriggers = function () { - element.unbind(triggers.show, showTooltipBind); - element.unbind(triggers.hide, hideTooltipBind); + function unregisterObservers() { + if (observers.length) { + angular.forEach(observers, function(observer) { + observer(); + }); + observers.length = 0; + } + } + + var unregisterTriggers = function() { + triggers.show.forEach(function(trigger) { + element.unbind(trigger, showTooltipBind); + }); + triggers.hide.forEach(function(trigger) { + trigger.split(' ').forEach(function(hideTrigger) { + element[0].removeEventListener(hideTrigger, hideTooltipBind); + }); + }); }; function prepTriggers() { - var val = attrs[ prefix + 'Trigger' ]; + var val = attrs[prefix + 'Trigger']; unregisterTriggers(); - triggers = getTriggers( val ); + triggers = getTriggers(val); - if ( triggers.show === triggers.hide ) { - element.bind( triggers.show, toggleTooltipBind ); - } else { - element.bind( triggers.show, showTooltipBind ); - element.bind( triggers.hide, hideTooltipBind ); + if (triggers.show !== 'none') { + triggers.show.forEach(function(trigger, idx) { + // Using raw addEventListener due to jqLite/jQuery bug - #4060 + if (trigger === triggers.hide[idx]) { + element[0].addEventListener(trigger, toggleTooltipBind); + } else if (trigger) { + element[0].addEventListener(trigger, showTooltipBind); + triggers.hide[idx].split(' ').forEach(function(trigger) { + element[0].addEventListener(trigger, hideTooltipBind); + }); + } + + element.on('keypress', function(e) { + if (e.which === 27) { + hideTooltipBind(); + } + }); + }); } } + prepTriggers(); var animation = scope.$eval(attrs[prefix + 'Animation']); @@ -2732,20 +5366,21 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // if a tooltip is attached to we need to remove it on // location change as its parent scope will probably not be destroyed // by the change. - if ( appendToBody ) { - scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { - if ( ttScope.isOpen ) { - hide(); - } - }); + if (appendToBody) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() { + if (ttScope.isOpen) { + hide(); + } + }); } // Make sure tooltip is destroyed and removed. scope.$on('$destroy', function onDestroyTooltip() { - $timeout.cancel( transitionTimeout ); - $timeout.cancel( popupTimeout ); + cancelShow(); + cancelHide(); unregisterTriggers(); removeTooltip(); + openedTooltips.remove(ttScope); ttScope = null; }); }; @@ -2755,172 +5390,733 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap }]; }) -.directive( 'tooltipPopup', function () { +// This is mostly ngInclude code but with a custom scope +.directive('uibTooltipTemplateTransclude', [ + '$animate', '$sce', '$compile', '$templateRequest', +function ($animate , $sce , $compile , $templateRequest) { return { - restrict: 'EA', - replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-popup.html' - }; -}) + link: function(scope, elem, attrs) { + var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope); -.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); + var changeCounter = 0, + currentScope, + previousElement, + currentElement; + + var cleanupLastIncludeContent = function() { + if (previousElement) { + previousElement.remove(); + previousElement = null; + } + + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + + if (currentElement) { + $animate.leave(currentElement).then(function() { + previousElement = null; + }); + previousElement = currentElement; + currentElement = null; + } + }; + + scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) { + var thisChangeId = ++changeCounter; + + if (src) { + //set the 2nd param to true to ignore the template request error so that the inner + //contents and scope can be cleaned up. + $templateRequest(src, true).then(function(response) { + if (thisChangeId !== changeCounter) { return; } + var newScope = origScope.$new(); + var template = response; + + var clone = $compile(template)(newScope, function(clone) { + cleanupLastIncludeContent(); + $animate.enter(clone, elem); + }); + + currentScope = newScope; + currentElement = clone; + + currentScope.$emit('$includeContentLoaded', src); + }, function() { + if (thisChangeId === changeCounter) { + cleanupLastIncludeContent(); + scope.$emit('$includeContentError', src); + } + }); + scope.$emit('$includeContentRequested', src); + } else { + cleanupLastIncludeContent(); + } + }); + + scope.$on('$destroy', cleanupLastIncludeContent); + } + }; }]) -.directive( 'tooltipHtmlUnsafePopup', function () { +/** + * Note that it's intentional that these classes are *not* applied through $animate. + * They must not be animated as they're expected to be present on the tooltip on + * initialization. + */ +.directive('uibTooltipClasses', function() { return { - restrict: 'EA', - replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + restrict: 'A', + link: function(scope, element, attrs) { + if (scope.placement) { + element.addClass(scope.placement); + } + + if (scope.popupClass) { + element.addClass(scope.popupClass); + } + + if (scope.animation()) { + element.addClass(attrs.tooltipAnimationClass); + } + } }; }) -.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +.directive('uibTooltipPopup', function() { + return { + replace: true, + scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html', + link: function(scope, element) { + element.addClass('tooltip'); + } + }; +}) + +.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter'); +}]) + +.directive('uibTooltipTemplatePopup', function() { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'template/tooltip/tooltip-template-popup.html', + link: function(scope, element) { + element.addClass('tooltip'); + } + }; +}) + +.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', { + useContentExp: true + }); +}]) + +.directive('uibTooltipHtmlPopup', function() { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-popup.html', + link: function(scope, element) { + element.addClass('tooltip'); + } + }; +}) + +.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', { + useContentExp: true + }); +}]); + +/* Deprecated tooltip below */ + +angular.module('ui.bootstrap.tooltip') + +.value('$tooltipSuppressWarning', false) + +.provider('$tooltip', ['$uibTooltipProvider', function($uibTooltipProvider) { + angular.extend(this, $uibTooltipProvider); + + this.$get = ['$log', '$tooltipSuppressWarning', '$injector', function($log, $tooltipSuppressWarning, $injector) { + if (!$tooltipSuppressWarning) { + $log.warn('$tooltip is now deprecated. Use $uibTooltip instead.'); + } + + return $injector.invoke($uibTooltipProvider.$get); + }]; +}]) + +// This is mostly ngInclude code but with a custom scope +.directive('tooltipTemplateTransclude', [ + '$animate', '$sce', '$compile', '$templateRequest', '$log', '$tooltipSuppressWarning', +function ($animate , $sce , $compile , $templateRequest, $log, $tooltipSuppressWarning) { + return { + link: function(scope, elem, attrs) { + if (!$tooltipSuppressWarning) { + $log.warn('tooltip-template-transclude is now deprecated. Use uib-tooltip-template-transclude instead.'); + } + + var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope); + + var changeCounter = 0, + currentScope, + previousElement, + currentElement; + + var cleanupLastIncludeContent = function() { + if (previousElement) { + previousElement.remove(); + previousElement = null; + } + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if (currentElement) { + $animate.leave(currentElement).then(function() { + previousElement = null; + }); + previousElement = currentElement; + currentElement = null; + } + }; + + scope.$watch($sce.parseAsResourceUrl(attrs.tooltipTemplateTransclude), function(src) { + var thisChangeId = ++changeCounter; + + if (src) { + //set the 2nd param to true to ignore the template request error so that the inner + //contents and scope can be cleaned up. + $templateRequest(src, true).then(function(response) { + if (thisChangeId !== changeCounter) { return; } + var newScope = origScope.$new(); + var template = response; + + var clone = $compile(template)(newScope, function(clone) { + cleanupLastIncludeContent(); + $animate.enter(clone, elem); + }); + + currentScope = newScope; + currentElement = clone; + + currentScope.$emit('$includeContentLoaded', src); + }, function() { + if (thisChangeId === changeCounter) { + cleanupLastIncludeContent(); + scope.$emit('$includeContentError', src); + } + }); + scope.$emit('$includeContentRequested', src); + } else { + cleanupLastIncludeContent(); + } + }); + + scope.$on('$destroy', cleanupLastIncludeContent); + } + }; +}]) + +.directive('tooltipClasses', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) { + return { + restrict: 'A', + link: function(scope, element, attrs) { + if (!$tooltipSuppressWarning) { + $log.warn('tooltip-classes is now deprecated. Use uib-tooltip-classes instead.'); + } + + if (scope.placement) { + element.addClass(scope.placement); + } + if (scope.popupClass) { + element.addClass(scope.popupClass); + } + if (scope.animation()) { + element.addClass(attrs.tooltipAnimationClass); + } + } + }; +}]) + +.directive('tooltipPopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) { + return { + replace: true, + scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html', + link: function(scope, element) { + if (!$tooltipSuppressWarning) { + $log.warn('tooltip-popup is now deprecated. Use uib-tooltip-popup instead.'); + } + + element.addClass('tooltip'); + } + }; +}]) + +.directive('tooltip', ['$tooltip', function($tooltip) { + return $tooltip('tooltip', 'tooltip', 'mouseenter'); +}]) + +.directive('tooltipTemplatePopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'template/tooltip/tooltip-template-popup.html', + link: function(scope, element) { + if (!$tooltipSuppressWarning) { + $log.warn('tooltip-template-popup is now deprecated. Use uib-tooltip-template-popup instead.'); + } + + element.addClass('tooltip'); + } + }; +}]) + +.directive('tooltipTemplate', ['$tooltip', function($tooltip) { + return $tooltip('tooltipTemplate', 'tooltip', 'mouseenter', { + useContentExp: true + }); +}]) + +.directive('tooltipHtmlPopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) { + return { + replace: true, + scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-popup.html', + link: function(scope, element) { + if (!$tooltipSuppressWarning) { + $log.warn('tooltip-html-popup is now deprecated. Use uib-tooltip-html-popup instead.'); + } + + element.addClass('tooltip'); + } + }; +}]) + +.directive('tooltipHtml', ['$tooltip', function($tooltip) { + return $tooltip('tooltipHtml', 'tooltip', 'mouseenter', { + useContentExp: true + }); }]); /** * The following features are still outstanding: popup delay, animation as a * function, placement as a function, inside, support for more triggers than - * just mouse enter/leave, html popovers, and selector delegatation. + * just mouse enter/leave, and selector delegatation. */ -angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) +angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip']) -.directive( 'popoverPopup', function () { +.directive('uibPopoverTemplatePopup', function() { return { - restrict: 'EA', replace: true, - scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/popover/popover.html' + scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'template/popover/popover-template.html', + link: function(scope, element) { + element.addClass('popover'); + } }; }) -.directive( 'popover', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'popover', 'popover', 'click' ); +.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopoverTemplate', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('uibPopoverHtmlPopup', function() { + return { + replace: true, + scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover-html.html', + link: function(scope, element) { + element.addClass('popover'); + } + }; +}) + +.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopoverHtml', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('uibPopoverPopup', function() { + return { + replace: true, + scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html', + link: function(scope, element) { + element.addClass('popover'); + } + }; +}) + +.directive('uibPopover', ['$uibTooltip', function($uibTooltip) { + return $uibTooltip('uibPopover', 'popover', 'click'); +}]); + +/* Deprecated popover below */ + +angular.module('ui.bootstrap.popover') + +.value('$popoverSuppressWarning', false) + +.directive('popoverTemplatePopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) { + return { + replace: true, + scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', + originScope: '&' }, + templateUrl: 'template/popover/popover-template.html', + link: function(scope, element) { + if (!$popoverSuppressWarning) { + $log.warn('popover-template-popup is now deprecated. Use uib-popover-template-popup instead.'); + } + + element.addClass('popover'); + } + }; +}]) + +.directive('popoverTemplate', ['$tooltip', function($tooltip) { + return $tooltip('popoverTemplate', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('popoverHtmlPopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) { + return { + replace: true, + scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover-html.html', + link: function(scope, element) { + if (!$popoverSuppressWarning) { + $log.warn('popover-html-popup is now deprecated. Use uib-popover-html-popup instead.'); + } + + element.addClass('popover'); + } + }; +}]) + +.directive('popoverHtml', ['$tooltip', function($tooltip) { + return $tooltip('popoverHtml', 'popover', 'click', { + useContentExp: true + }); +}]) + +.directive('popoverPopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) { + return { + replace: true, + scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html', + link: function(scope, element) { + if (!$popoverSuppressWarning) { + $log.warn('popover-popup is now deprecated. Use uib-popover-popup instead.'); + } + + element.addClass('popover'); + } + }; +}]) + +.directive('popover', ['$tooltip', function($tooltip) { + + return $tooltip('popover', 'popover', 'click'); }]); angular.module('ui.bootstrap.progressbar', []) -.constant('progressConfig', { +.constant('uibProgressConfig', { animate: true, max: 100 }) -.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { - var self = this, - animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; +.controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) { + var self = this, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; - this.bars = []; - $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max; + this.bars = []; + $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max; - this.addBar = function(bar, element) { - if ( !animate ) { - element.css({'transition': 'none'}); - } + this.addBar = function(bar, element, attrs) { + if (!animate) { + element.css({'transition': 'none'}); + } - this.bars.push(bar); + this.bars.push(bar); - bar.$watch('value', function( value ) { - bar.percent = +(100 * value / $scope.max).toFixed(2); - }); + bar.max = $scope.max; + bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar'; - bar.$on('$destroy', function() { - element = null; - self.removeBar(bar); - }); + bar.$watch('value', function(value) { + bar.recalculatePercentage(); + }); + + bar.recalculatePercentage = function() { + var totalPercentage = self.bars.reduce(function(total, bar) { + bar.percent = +(100 * bar.value / bar.max).toFixed(2); + return total + bar.percent; + }, 0); + + if (totalPercentage > 100) { + bar.percent -= totalPercentage - 100; + } }; - this.removeBar = function(bar) { - this.bars.splice(this.bars.indexOf(bar), 1); - }; + bar.$on('$destroy', function() { + element = null; + self.removeBar(bar); + }); + }; + + this.removeBar = function(bar) { + this.bars.splice(this.bars.indexOf(bar), 1); + this.bars.forEach(function (bar) { + bar.recalculatePercentage(); + }); + }; + + $scope.$watch('max', function(max) { + self.bars.forEach(function(bar) { + bar.max = $scope.max; + bar.recalculatePercentage(); + }); + }); }]) -.directive('progress', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - controller: 'ProgressController', - require: 'progress', - scope: {}, - templateUrl: 'template/progressbar/progress.html' - }; +.directive('uibProgress', function() { + return { + replace: true, + transclude: true, + controller: 'UibProgressController', + require: 'uibProgress', + scope: { + max: '=?' + }, + templateUrl: 'template/progressbar/progress.html' + }; }) -.directive('bar', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - require: '^progress', - scope: { - value: '=', - type: '@' - }, - templateUrl: 'template/progressbar/bar.html', - link: function(scope, element, attrs, progressCtrl) { - progressCtrl.addBar(scope, element); - } - }; +.directive('uibBar', function() { + return { + replace: true, + transclude: true, + require: '^uibProgress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, element, attrs); + } + }; }) -.directive('progressbar', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - controller: 'ProgressController', - scope: { - value: '=', - type: '@' - }, - templateUrl: 'template/progressbar/progressbar.html', - link: function(scope, element, attrs, progressCtrl) { - progressCtrl.addBar(scope, angular.element(element.children()[0])); - } - }; +.directive('uibProgressbar', function() { + return { + replace: true, + transclude: true, + controller: 'UibProgressController', + scope: { + value: '=', + max: '=?', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title}); + } + }; }); + +/* Deprecated progressbar below */ + +angular.module('ui.bootstrap.progressbar') + +.value('$progressSuppressWarning', false) + +.controller('ProgressController', ['$scope', '$attrs', 'uibProgressConfig', '$log', '$progressSuppressWarning', function($scope, $attrs, progressConfig, $log, $progressSuppressWarning) { + if (!$progressSuppressWarning) { + $log.warn('ProgressController is now deprecated. Use UibProgressController instead.'); + } + + var self = this, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; + + this.bars = []; + $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max; + + this.addBar = function(bar, element, attrs) { + if (!animate) { + element.css({'transition': 'none'}); + } + + this.bars.push(bar); + + bar.max = $scope.max; + bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar'; + + bar.$watch('value', function(value) { + bar.recalculatePercentage(); + }); + + bar.recalculatePercentage = function() { + bar.percent = +(100 * bar.value / bar.max).toFixed(2); + + var totalPercentage = self.bars.reduce(function(total, bar) { + return total + bar.percent; + }, 0); + + if (totalPercentage > 100) { + bar.percent -= totalPercentage - 100; + } + }; + + bar.$on('$destroy', function() { + element = null; + self.removeBar(bar); + }); + }; + + this.removeBar = function(bar) { + this.bars.splice(this.bars.indexOf(bar), 1); + }; + + $scope.$watch('max', function(max) { + self.bars.forEach(function(bar) { + bar.max = $scope.max; + bar.recalculatePercentage(); + }); + }); +}]) + +.directive('progress', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) { + return { + replace: true, + transclude: true, + controller: 'ProgressController', + require: 'progress', + scope: { + max: '=?', + title: '@?' + }, + templateUrl: 'template/progressbar/progress.html', + link: function() { + if (!$progressSuppressWarning) { + $log.warn('progress is now deprecated. Use uib-progress instead.'); + } + } + }; +}]) + +.directive('bar', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) { + return { + replace: true, + transclude: true, + require: '^progress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + if (!$progressSuppressWarning) { + $log.warn('bar is now deprecated. Use uib-bar instead.'); + } + progressCtrl.addBar(scope, element); + } + }; +}]) + +.directive('progressbar', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) { + return { + replace: true, + transclude: true, + controller: 'ProgressController', + scope: { + value: '=', + max: '=?', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + if (!$progressSuppressWarning) { + $log.warn('progressbar is now deprecated. Use uib-progressbar instead.'); + } + progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title}); + } + }; +}]); + angular.module('ui.bootstrap.rating', []) -.constant('ratingConfig', { +.constant('uibRatingConfig', { max: 5, stateOn: null, - stateOff: null + stateOff: null, + titles : ['one', 'two', 'three', 'four', 'five'] }) -.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) { +.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) { var ngModelCtrl = { $setViewValue: angular.noop }; this.init = function(ngModelCtrl_) { ngModelCtrl = ngModelCtrl_; ngModelCtrl.$render = this.render; + ngModelCtrl.$formatters.push(function(value) { + if (angular.isNumber(value) && value << 0 !== value) { + value = Math.round(value); + } + return value; + }); + this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; + var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ; + this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ? + tmpTitles : ratingConfig.titles; - var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : - new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max ); + var ratingStates = angular.isDefined($attrs.ratingStates) ? + $scope.$parent.$eval($attrs.ratingStates) : + new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max); $scope.range = this.buildTemplateObjects(ratingStates); }; this.buildTemplateObjects = function(states) { for (var i = 0, n = states.length; i < n; i++) { - states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]); + states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]); } return states; }; + this.getTitle = function(index) { + if (index >= this.titles.length) { + return index + 1; + } else { + return this.titles[index]; + } + }; + $scope.rate = function(value) { - if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) { - ngModelCtrl.$setViewValue(value); + if (!$scope.readonly && value >= 0 && value <= $scope.range.length) { + ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value); ngModelCtrl.$render(); } }; $scope.enter = function(value) { - if ( !$scope.readonly ) { + if (!$scope.readonly) { $scope.value = value; } $scope.onHover({value: value}); @@ -2935,7 +6131,7 @@ angular.module('ui.bootstrap.rating', []) if (/(37|38|39|40)/.test(evt.which)) { evt.preventDefault(); evt.stopPropagation(); - $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) ); + $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1)); } }; @@ -2944,9 +6140,43 @@ angular.module('ui.bootstrap.rating', []) }; }]) -.directive('rating', function() { +.directive('uibRating', function() { + return { + require: ['uibRating', 'ngModel'], + scope: { + readonly: '=?', + onHover: '&', + onLeave: '&' + }, + controller: 'UibRatingController', + templateUrl: 'template/rating/rating.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + ratingCtrl.init(ngModelCtrl); + } + }; +}); + +/* Deprecated rating below */ + +angular.module('ui.bootstrap.rating') + +.value('$ratingSuppressWarning', false) + +.controller('RatingController', ['$scope', '$attrs', '$controller', '$log', '$ratingSuppressWarning', function($scope, $attrs, $controller, $log, $ratingSuppressWarning) { + if (!$ratingSuppressWarning) { + $log.warn('RatingController is now deprecated. Use UibRatingController instead.'); + } + + angular.extend(this, $controller('UibRatingController', { + $scope: $scope, + $attrs: $attrs + })); +}]) + +.directive('rating', ['$log', '$ratingSuppressWarning', function($log, $ratingSuppressWarning) { return { - restrict: 'EA', require: ['rating', 'ngModel'], scope: { readonly: '=?', @@ -2957,14 +6187,15 @@ angular.module('ui.bootstrap.rating', []) templateUrl: 'template/rating/rating.html', replace: true, link: function(scope, element, attrs, ctrls) { - var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - if ( ngModelCtrl ) { - ratingCtrl.init( ngModelCtrl ); + if (!$ratingSuppressWarning) { + $log.warn('rating is now deprecated. Use uib-rating instead.'); } + var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + ratingCtrl.init(ngModelCtrl); } }; -}); +}]); + /** * @ngdoc overview @@ -2976,7 +6207,7 @@ angular.module('ui.bootstrap.rating', []) angular.module('ui.bootstrap.tabs', []) -.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { +.controller('UibTabsetController', ['$scope', function ($scope) { var ctrl = this, tabs = ctrl.tabs = $scope.tabs = []; @@ -2985,20 +6216,27 @@ angular.module('ui.bootstrap.tabs', []) if (tab.active && tab !== selectedTab) { tab.active = false; tab.onDeselect(); + selectedTab.selectCalled = false; } }); selectedTab.active = true; - selectedTab.onSelect(); + // only call select if it has not already been called + if (!selectedTab.selectCalled) { + selectedTab.onSelect(); + selectedTab.selectCalled = true; + } }; ctrl.addTab = function addTab(tab) { tabs.push(tab); // we can't run the select function on the first tab // since that would select it twice - if (tabs.length === 1) { + if (tabs.length === 1 && tab.active !== false) { tab.active = true; } else if (tab.active) { ctrl.select(tab); + } else { + tab.active = false; } }; @@ -3033,23 +6271,23 @@ angular.module('ui.bootstrap.tabs', []) * @example - - First Content! - Second Content! - + + First Content! + Second Content! +
    - - First Vertical Content! - Second Vertical Content! - - - First Justified Content! - Second Justified Content! - + + First Vertical Content! + Second Vertical Content! + + + First Justified Content! + Second Justified Content! +
    */ -.directive('tabset', function() { +.directive('uibTabset', function() { return { restrict: 'EA', transclude: true, @@ -3057,7 +6295,7 @@ angular.module('ui.bootstrap.tabs', []) scope: { type: '@' }, - controller: 'TabsetController', + controller: 'UibTabsetController', templateUrl: 'template/tabs/tabset.html', link: function(scope, element, attrs) { scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; @@ -3090,19 +6328,19 @@ angular.module('ui.bootstrap.tabs', []) Enable/disable item 2, using disabled binding
    - - First Tab - - Alert me! + + First Tab + + Alert me! Second Tab, with alert callback and html heading! - - + {{item.content}} - - + +
    @@ -3133,22 +6371,22 @@ angular.module('ui.bootstrap.tabs', []) * @example - - - HTML in my titles?! + + + HTML in my titles?! And some content, too! - - - Icon heading?!? + + + Icon heading?!? That's right. - - + + */ -.directive('tab', ['$parse', function($parse) { +.directive('uibTab', ['$parse', function($parse) { return { - require: '^tabset', + require: '^uibTabset', restrict: 'EA', replace: true, templateUrl: 'template/tabs/tab.html', @@ -3163,45 +6401,43 @@ angular.module('ui.bootstrap.tabs', []) controller: function() { //Empty controller so other directives can require being 'under' a tab }, - compile: function(elm, attrs, transclude) { - return function postLink(scope, elm, attrs, tabsetCtrl) { - scope.$watch('active', function(active) { - if (active) { - tabsetCtrl.select(scope); - } - }); - - scope.disabled = false; - if ( attrs.disabled ) { - scope.$parent.$watch($parse(attrs.disabled), function(value) { - scope.disabled = !! value; - }); + link: function(scope, elm, attrs, tabsetCtrl, transclude) { + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); } + }); - scope.select = function() { - if ( !scope.disabled ) { - scope.active = true; - } - }; - - tabsetCtrl.addTab(scope); - scope.$on('$destroy', function() { - tabsetCtrl.removeTab(scope); + scope.disabled = false; + if (attrs.disable) { + scope.$parent.$watch($parse(attrs.disable), function(value) { + scope.disabled = !! value; }); + } - //We need to transclude later, once the content container is ready. - //when this link happens, we're inside a tab heading. - scope.$transcludeFn = transclude; + scope.select = function() { + if (!scope.disabled) { + scope.active = true; + } }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; } }; }]) -.directive('tabHeadingTransclude', [function() { +.directive('uibTabHeadingTransclude', function() { return { restrict: 'A', - require: '^tab', - link: function(scope, elm, attrs, tabCtrl) { + require: ['?^uibTab', '?^tab'], // TODO: change to '^uibTab' after deprecation removal + link: function(scope, elm) { scope.$watch('headingElement', function updateHeadingElement(heading) { if (heading) { elm.html(''); @@ -3210,14 +6446,14 @@ angular.module('ui.bootstrap.tabs', []) }); } }; -}]) +}) -.directive('tabContentTransclude', function() { +.directive('uibTabContentTransclude', function() { return { restrict: 'A', - require: '^tabset', + require: ['?^uibTabset', '?^tabset'], // TODO: change to '^uibTabset' after deprecation removal link: function(scope, elm, attrs) { - var tab = scope.$eval(attrs.tabContentTransclude); + var tab = scope.$eval(attrs.uibTabContentTransclude); //Now our tab is ready to be transcluded: both the tab heading area //and the tab content area are loaded. Transclude 'em both. @@ -3233,48 +6469,217 @@ angular.module('ui.bootstrap.tabs', []) }); } }; + function isTabHeading(node) { - return node.tagName && ( - node.hasAttribute('tab-heading') || - node.hasAttribute('data-tab-heading') || - node.tagName.toLowerCase() === 'tab-heading' || - node.tagName.toLowerCase() === 'data-tab-heading' + return node.tagName && ( + node.hasAttribute('tab-heading') || // TODO: remove after deprecation removal + node.hasAttribute('data-tab-heading') || // TODO: remove after deprecation removal + node.hasAttribute('x-tab-heading') || // TODO: remove after deprecation removal + node.hasAttribute('uib-tab-heading') || + node.hasAttribute('data-uib-tab-heading') || + node.hasAttribute('x-uib-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || // TODO: remove after deprecation removal + node.tagName.toLowerCase() === 'data-tab-heading' || // TODO: remove after deprecation removal + node.tagName.toLowerCase() === 'x-tab-heading' || // TODO: remove after deprecation removal + node.tagName.toLowerCase() === 'uib-tab-heading' || + node.tagName.toLowerCase() === 'data-uib-tab-heading' || + node.tagName.toLowerCase() === 'x-uib-tab-heading' ); } -}) +}); -; +/* deprecated tabs below */ + +angular.module('ui.bootstrap.tabs') + + .value('$tabsSuppressWarning', false) + + .controller('TabsetController', ['$scope', '$controller', '$log', '$tabsSuppressWarning', function($scope, $controller, $log, $tabsSuppressWarning) { + if (!$tabsSuppressWarning) { + $log.warn('TabsetController is now deprecated. Use UibTabsetController instead.'); + } + + angular.extend(this, $controller('UibTabsetController', { + $scope: $scope + })); + }]) + + .directive('tabset', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: { + type: '@' + }, + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + + if (!$tabsSuppressWarning) { + $log.warn('tabset is now deprecated. Use uib-tabset instead.'); + } + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; + } + }; + }]) + + .directive('tab', ['$parse', '$log', '$tabsSuppressWarning', function($parse, $log, $tabsSuppressWarning) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + active: '=?', + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + link: function(scope, elm, attrs, tabsetCtrl, transclude) { + if (!$tabsSuppressWarning) { + $log.warn('tab is now deprecated. Use uib-tab instead.'); + } + + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); + } + }); + + scope.disabled = false; + if (attrs.disable) { + scope.$parent.$watch($parse(attrs.disable), function(value) { + scope.disabled = !!value; + }); + } + + scope.select = function() { + if (!scope.disabled) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + } + }; + }]) + + .directive('tabHeadingTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm) { + if (!$tabsSuppressWarning) { + $log.warn('tab-heading-transclude is now deprecated. Use uib-tab-heading-transclude instead.'); + } + + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; + }]) + + .directive('tabContentTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + if (!$tabsSuppressWarning) { + $log.warn('tab-content-transclude is now deprecated. Use uib-tab-content-transclude instead.'); + } + + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } + else { + elm.append(node); + } + }); + }); + } + }; + + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.hasAttribute('x-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' || + node.tagName.toLowerCase() === 'x-tab-heading' + ); + } + }]); angular.module('ui.bootstrap.timepicker', []) -.constant('timepickerConfig', { +.constant('uibTimepickerConfig', { hourStep: 1, minuteStep: 1, showMeridian: true, meridians: null, readonlyInput: false, - mousewheel: true + mousewheel: true, + arrowkeys: true, + showSpinners: true }) -.controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) { +.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) { var selected = new Date(), ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; - this.init = function( ngModelCtrl_, inputs ) { + $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0; + $element.removeAttr('tabindex'); + + this.init = function(ngModelCtrl_, inputs) { ngModelCtrl = ngModelCtrl_; ngModelCtrl.$render = this.render; + ngModelCtrl.$formatters.unshift(function(modelValue) { + return modelValue ? new Date(modelValue) : null; + }); + var hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1); var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; - if ( mousewheel ) { - this.setupMousewheelEvents( hoursInputEl, minutesInputEl ); + if (mousewheel) { + this.setupMousewheelEvents(hoursInputEl, minutesInputEl); + } + + var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys; + if (arrowkeys) { + this.setupArrowkeyEvents(hoursInputEl, minutesInputEl); } $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; - this.setupInputEvents( hoursInputEl, minutesInputEl ); + this.setupInputEvents(hoursInputEl, minutesInputEl); }; var hourStep = timepickerConfig.hourStep; @@ -3291,17 +6696,61 @@ angular.module('ui.bootstrap.timepicker', []) }); } + var min; + $scope.$parent.$watch($parse($attrs.min), function(value) { + var dt = new Date(value); + min = isNaN(dt) ? undefined : dt; + }); + + var max; + $scope.$parent.$watch($parse($attrs.max), function(value) { + var dt = new Date(value); + max = isNaN(dt) ? undefined : dt; + }); + + $scope.noIncrementHours = function() { + var incrementedSelected = addMinutes(selected, hourStep * 60); + return incrementedSelected > max || + (incrementedSelected < selected && incrementedSelected < min); + }; + + $scope.noDecrementHours = function() { + var decrementedSelected = addMinutes(selected, -hourStep * 60); + return decrementedSelected < min || + (decrementedSelected > selected && decrementedSelected > max); + }; + + $scope.noIncrementMinutes = function() { + var incrementedSelected = addMinutes(selected, minuteStep); + return incrementedSelected > max || + (incrementedSelected < selected && incrementedSelected < min); + }; + + $scope.noDecrementMinutes = function() { + var decrementedSelected = addMinutes(selected, -minuteStep); + return decrementedSelected < min || + (decrementedSelected > selected && decrementedSelected > max); + }; + + $scope.noToggleMeridian = function() { + if (selected.getHours() < 13) { + return addMinutes(selected, 12 * 60) > max; + } else { + return addMinutes(selected, -12 * 60) < min; + } + }; + // 12H / 24H mode $scope.showMeridian = timepickerConfig.showMeridian; if ($attrs.showMeridian) { $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { $scope.showMeridian = !!value; - if ( ngModelCtrl.$error.time ) { + if (ngModelCtrl.$error.time) { // Evaluate from template var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); - if (angular.isDefined( hours ) && angular.isDefined( minutes )) { - selected.setHours( hours ); + if (angular.isDefined(hours) && angular.isDefined(minutes)) { + selected.setHours(hours); refresh(); } } else { @@ -3311,18 +6760,18 @@ angular.module('ui.bootstrap.timepicker', []) } // Get $scope.hours in 24H mode if valid - function getHoursFromTemplate ( ) { - var hours = parseInt( $scope.hours, 10 ); - var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); - if ( !valid ) { + function getHoursFromTemplate() { + var hours = parseInt($scope.hours, 10); + var valid = $scope.showMeridian ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); + if (!valid) { return undefined; } - if ( $scope.showMeridian ) { - if ( hours === 12 ) { + if ($scope.showMeridian) { + if (hours === 12) { hours = 0; } - if ( $scope.meridian === meridians[1] ) { + if ($scope.meridian === meridians[1]) { hours = hours + 12; } } @@ -3331,15 +6780,15 @@ angular.module('ui.bootstrap.timepicker', []) function getMinutesFromTemplate() { var minutes = parseInt($scope.minutes, 10); - return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; + return (minutes >= 0 && minutes < 60) ? minutes : undefined; } - function pad( value ) { - return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; + function pad(value) { + return (angular.isDefined(value) && value.toString().length < 2) ? '0' + value : value.toString(); } // Respond on mousewheel spin - this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) { + this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl) { var isScrollingUp = function(e) { if (e.originalEvent) { e = e.originalEvent; @@ -3350,26 +6799,53 @@ angular.module('ui.bootstrap.timepicker', []) }; hoursInputEl.bind('mousewheel wheel', function(e) { - $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() ); + $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours()); e.preventDefault(); }); minutesInputEl.bind('mousewheel wheel', function(e) { - $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() ); + $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes()); e.preventDefault(); }); }; - this.setupInputEvents = function( hoursInputEl, minutesInputEl ) { - if ( $scope.readonlyInput ) { + // Respond on up/down arrowkeys + this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl) { + hoursInputEl.bind('keydown', function(e) { + if (e.which === 38) { // up + e.preventDefault(); + $scope.incrementHours(); + $scope.$apply(); + } else if (e.which === 40) { // down + e.preventDefault(); + $scope.decrementHours(); + $scope.$apply(); + } + }); + + minutesInputEl.bind('keydown', function(e) { + if (e.which === 38) { // up + e.preventDefault(); + $scope.incrementMinutes(); + $scope.$apply(); + } else if (e.which === 40) { // down + e.preventDefault(); + $scope.decrementMinutes(); + $scope.$apply(); + } + }); + }; + + this.setupInputEvents = function(hoursInputEl, minutesInputEl) { + if ($scope.readonlyInput) { $scope.updateHours = angular.noop; $scope.updateMinutes = angular.noop; return; } var invalidate = function(invalidHours, invalidMinutes) { - ngModelCtrl.$setViewValue( null ); + ngModelCtrl.$setViewValue(null); ngModelCtrl.$setValidity('time', false); if (angular.isDefined(invalidHours)) { $scope.invalidHours = invalidHours; @@ -3380,39 +6856,49 @@ angular.module('ui.bootstrap.timepicker', []) }; $scope.updateHours = function() { - var hours = getHoursFromTemplate(); + var hours = getHoursFromTemplate(), + minutes = getMinutesFromTemplate(); - if ( angular.isDefined(hours) ) { - selected.setHours( hours ); - refresh( 'h' ); + if (angular.isDefined(hours) && angular.isDefined(minutes)) { + selected.setHours(hours); + if (selected < min || selected > max) { + invalidate(true); + } else { + refresh('h'); + } } else { invalidate(true); } }; hoursInputEl.bind('blur', function(e) { - if ( !$scope.invalidHours && $scope.hours < 10) { - $scope.$apply( function() { - $scope.hours = pad( $scope.hours ); + if (!$scope.invalidHours && $scope.hours < 10) { + $scope.$apply(function() { + $scope.hours = pad($scope.hours); }); } }); $scope.updateMinutes = function() { - var minutes = getMinutesFromTemplate(); + var minutes = getMinutesFromTemplate(), + hours = getHoursFromTemplate(); - if ( angular.isDefined(minutes) ) { - selected.setMinutes( minutes ); - refresh( 'm' ); + if (angular.isDefined(minutes) && angular.isDefined(hours)) { + selected.setMinutes(minutes); + if (selected < min || selected > max) { + invalidate(undefined, true); + } else { + refresh('m'); + } } else { invalidate(undefined, true); } }; minutesInputEl.bind('blur', function(e) { - if ( !$scope.invalidMinutes && $scope.minutes < 10 ) { - $scope.$apply( function() { - $scope.minutes = pad( $scope.minutes ); + if (!$scope.invalidMinutes && $scope.minutes < 10) { + $scope.$apply(function() { + $scope.minutes = pad($scope.minutes); }); } }); @@ -3420,25 +6906,32 @@ angular.module('ui.bootstrap.timepicker', []) }; this.render = function() { - var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null; + var date = ngModelCtrl.$viewValue; - if ( isNaN(date) ) { + if (isNaN(date)) { ngModelCtrl.$setValidity('time', false); $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); } else { - if ( date ) { + if (date) { selected = date; } - makeValid(); + + if (selected < min || selected > max) { + ngModelCtrl.$setValidity('time', false); + $scope.invalidHours = true; + $scope.invalidMinutes = true; + } else { + makeValid(); + } updateTemplate(); } }; // Call internally when we know that model is valid. - function refresh( keyboardChange ) { + function refresh(keyboardChange) { makeValid(); - ngModelCtrl.$setViewValue( new Date(selected) ); - updateTemplate( keyboardChange ); + ngModelCtrl.$setViewValue(new Date(selected)); + updateTemplate(keyboardChange); } function makeValid() { @@ -3447,255 +6940,519 @@ angular.module('ui.bootstrap.timepicker', []) $scope.invalidMinutes = false; } - function updateTemplate( keyboardChange ) { + function updateTemplate(keyboardChange) { var hours = selected.getHours(), minutes = selected.getMinutes(); - if ( $scope.showMeridian ) { - hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system + if ($scope.showMeridian) { + hours = (hours === 0 || hours === 12) ? 12 : hours % 12; // Convert 24 to 12 hour system } $scope.hours = keyboardChange === 'h' ? hours : pad(hours); - $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); + if (keyboardChange !== 'm') { + $scope.minutes = pad(minutes); + } $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; } - function addMinutes( minutes ) { - var dt = new Date( selected.getTime() + minutes * 60000 ); - selected.setHours( dt.getHours(), dt.getMinutes() ); + function addMinutes(date, minutes) { + var dt = new Date(date.getTime() + minutes * 60000); + var newDate = new Date(date); + newDate.setHours(dt.getHours(), dt.getMinutes()); + return newDate; + } + + function addMinutesToSelected(minutes) { + selected = addMinutes(selected, minutes); refresh(); } + $scope.showSpinners = angular.isDefined($attrs.showSpinners) ? + $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners; + $scope.incrementHours = function() { - addMinutes( hourStep * 60 ); + if (!$scope.noIncrementHours()) { + addMinutesToSelected(hourStep * 60); + } }; + $scope.decrementHours = function() { - addMinutes( - hourStep * 60 ); + if (!$scope.noDecrementHours()) { + addMinutesToSelected(-hourStep * 60); + } }; + $scope.incrementMinutes = function() { - addMinutes( minuteStep ); + if (!$scope.noIncrementMinutes()) { + addMinutesToSelected(minuteStep); + } }; + $scope.decrementMinutes = function() { - addMinutes( - minuteStep ); + if (!$scope.noDecrementMinutes()) { + addMinutesToSelected(-minuteStep); + } }; + $scope.toggleMeridian = function() { - addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); + if (!$scope.noToggleMeridian()) { + addMinutesToSelected(12 * 60 * (selected.getHours() < 12 ? 1 : -1)); + } }; }]) -.directive('timepicker', function () { +.directive('uibTimepicker', function() { return { restrict: 'EA', - require: ['timepicker', '?^ngModel'], - controller:'TimepickerController', + require: ['uibTimepicker', '?^ngModel'], + controller: 'UibTimepickerController', + controllerAs: 'timepicker', replace: true, scope: {}, - templateUrl: 'template/timepicker/timepicker.html', + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/timepicker/timepicker.html'; + }, link: function(scope, element, attrs, ctrls) { var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - if ( ngModelCtrl ) { - timepickerCtrl.init( ngModelCtrl, element.find('input') ); + if (ngModelCtrl) { + timepickerCtrl.init(ngModelCtrl, element.find('input')); } } }; }); -angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) +/* Deprecated timepicker below */ + +angular.module('ui.bootstrap.timepicker') + +.value('$timepickerSuppressWarning', false) + +.controller('TimepickerController', ['$scope', '$element', '$attrs', '$controller', '$log', '$timepickerSuppressWarning', function($scope, $element, $attrs, $controller, $log, $timepickerSuppressWarning) { + if (!$timepickerSuppressWarning) { + $log.warn('TimepickerController is now deprecated. Use UibTimepickerController instead.'); + } + + angular.extend(this, $controller('UibTimepickerController', { + $scope: $scope, + $element: $element, + $attrs: $attrs + })); +}]) + +.directive('timepicker', ['$log', '$timepickerSuppressWarning', function($log, $timepickerSuppressWarning) { + return { + restrict: 'EA', + require: ['timepicker', '?^ngModel'], + controller: 'TimepickerController', + controllerAs: 'timepicker', + replace: true, + scope: {}, + templateUrl: function(element, attrs) { + return attrs.templateUrl || 'template/timepicker/timepicker.html'; + }, + link: function(scope, element, attrs, ctrls) { + if (!$timepickerSuppressWarning) { + $log.warn('timepicker is now deprecated. Use uib-timepicker instead.'); + } + var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (ngModelCtrl) { + timepickerCtrl.init(ngModelCtrl, element.find('input')); + } + } + }; +}]); + +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) /** * A helper service that can parse typeahead's syntax (string provided by users) * Extracted to a separate service for ease of unit testing */ - .factory('typeaheadParser', ['$parse', function ($parse) { - - // 00000111000000000000022200000000000000003333333333333330000000000044000 - var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; - - return { - parse:function (input) { - - var match = input.match(TYPEAHEAD_REGEXP); - if (!match) { - throw new Error( - 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + - ' but got "' + input + '".'); - } - - return { - itemName:match[3], - source:$parse(match[4]), - viewMapper:$parse(match[2] || match[1]), - modelMapper:$parse(match[1]) - }; - } - }; -}]) - - .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', - function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { - - var HOT_KEYS = [9, 13, 27, 38, 40]; - - return { - require:'ngModel', - link:function (originalScope, element, attrs, modelCtrl) { - - //SUPPORTED ATTRIBUTES (OPTIONS) - - //minimal no of characters that needs to be entered before typeahead kicks-in - var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; - - //minimal wait time after last character typed before typehead kicks-in - var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; - - //should it restrict model values to the ones selected from the popup only? - var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; - - //binding to a variable that indicates if matches are being retrieved asynchronously - var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; - - //a callback executed when a match is selected - var onSelectCallback = $parse(attrs.typeaheadOnSelect); - - var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; - - var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; - - var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false; - - //INTERNAL VARIABLES - - //model setter executed upon match selection - var $setModelValue = $parse(attrs.ngModel).assign; - - //expressions used by typeahead - var parserResult = typeaheadParser.parse(attrs.typeahead); - - var hasFocus; - - //create a child scope for the typeahead directive so we are not polluting original scope - //with typeahead-specific data (matches, query etc.) - var scope = originalScope.$new(); - originalScope.$on('$destroy', function(){ - scope.$destroy(); - }); - - // WAI-ARIA - var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); - element.attr({ - 'aria-autocomplete': 'list', - 'aria-expanded': false, - 'aria-owns': popupId - }); - - //pop-up element used to display matches - var popUpEl = angular.element('
    '); - popUpEl.attr({ - id: popupId, - matches: 'matches', - active: 'activeIdx', - select: 'select(activeIdx)', - query: 'query', - position: 'position' - }); - //custom item template - if (angular.isDefined(attrs.typeaheadTemplateUrl)) { - popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); - } - - var resetMatches = function() { - scope.matches = []; - scope.activeIdx = -1; - element.attr('aria-expanded', false); - }; - - var getMatchId = function(index) { - return popupId + '-option-' + index; - }; - - // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. - // This attribute is added or removed automatically when the `activeIdx` changes. - scope.$watch('activeIdx', function(index) { - if (index < 0) { - element.removeAttr('aria-activedescendant'); - } else { - element.attr('aria-activedescendant', getMatchId(index)); + .factory('uibTypeaheadParser', ['$parse', function($parse) { + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; + return { + parse: function(input) { + var match = input.match(TYPEAHEAD_REGEXP); + if (!match) { + throw new Error( + 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + + ' but got "' + input + '".'); } - }); - var getMatchesAsync = function(inputValue) { + return { + itemName: match[3], + source: $parse(match[4]), + viewMapper: $parse(match[2] || match[1]), + modelMapper: $parse(match[1]) + }; + } + }; + }]) - var locals = {$viewValue: inputValue}; - isLoadingSetter(originalScope, true); - $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$uibPosition', 'uibTypeaheadParser', + function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) { + var HOT_KEYS = [9, 13, 27, 38, 40]; + var eventDebounceTime = 200; + var modelCtrl, ngModelOptions; + //SUPPORTED ATTRIBUTES (OPTIONS) - //it might happen that several async queries were in progress if a user were typing fast - //but we are interested only in responses that correspond to the current view value - var onCurrentRequest = (inputValue === modelCtrl.$viewValue); - if (onCurrentRequest && hasFocus) { - if (matches.length > 0) { + //minimal no of characters that needs to be entered before typeahead kicks-in + var minLength = originalScope.$eval(attrs.typeaheadMinLength); + if (!minLength && minLength !== 0) { + minLength = 1; + } - scope.activeIdx = focusFirst ? 0 : -1; - scope.matches.length = 0; + //minimal wait time after last character typed before typeahead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; - //transform labels - for(var i=0; i
    '); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + 'move-in-progress': 'moveInProgress', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) { + popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl); + } + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; + + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + + var inputIsExactMatch = function(inputValue, index) { + if (scope.matches.length > index && inputValue) { + return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase(); + } + + return false; + }; + + var getMatchesAsync = function(inputValue) { + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + isNoResultsSetter(originalScope, false); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches && matches.length > 0) { + scope.activeIdx = focusFirst ? 0 : -1; + isNoResultsSetter(originalScope, false); + scope.matches.length = 0; + + //transform labels + for (var i = 0; i < matches.length; i++) { + locals[parserResult.itemName] = matches[i]; + scope.matches.push({ + id: getMatchId(i), + label: parserResult.viewMapper(scope, locals), + model: matches[i] + }); } + + scope.query = inputValue; + //position pop-up with matches - we need to re-calculate its position each time we are opening a window + //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page + //due to other elements being rendered + recalculatePosition(); + + element.attr('aria-expanded', true); + + //Select the single remaining option if user input matches + if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { + scope.select(0); + } + } else { + resetMatches(); + isNoResultsSetter(originalScope, true); } - if (onCurrentRequest) { - isLoadingSetter(originalScope, false); - } - }, function(){ - resetMatches(); + } + if (onCurrentRequest) { isLoadingSetter(originalScope, false); - }); - }; + } + }, function() { + resetMatches(); + isLoadingSetter(originalScope, false); + isNoResultsSetter(originalScope, true); + }); + }; + + // bind events only if appendToBody params exist - performance feature + if (appendToBody) { + angular.element($window).bind('resize', fireRecalculating); + $document.find('body').bind('scroll', fireRecalculating); + } + + // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later + var timeoutEventPromise; + + // Default progress type + scope.moveInProgress = false; + + function fireRecalculating() { + if (!scope.moveInProgress) { + scope.moveInProgress = true; + scope.$digest(); + } + + // Cancel previous timeout + if (timeoutEventPromise) { + $timeout.cancel(timeoutEventPromise); + } + + // Debounced executing recalculate after events fired + timeoutEventPromise = $timeout(function() { + // if popup is visible + if (scope.matches.length) { + recalculatePosition(); + } + + scope.moveInProgress = false; + }, eventDebounceTime); + } + + // recalculate actual position and set new values to scope + // after digest loop is popup in right position + function recalculatePosition() { + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top += element.prop('offsetHeight'); + } + + //we need to propagate user's query so we can higlight matches + scope.query = undefined; + + //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later + var timeoutPromise; + + var scheduleSearchWithTimeout = function(inputValue) { + timeoutPromise = $timeout(function() { + getMatchesAsync(inputValue); + }, waitTime); + }; + + var cancelPreviousTimeout = function() { + if (timeoutPromise) { + $timeout.cancel(timeoutPromise); + } + }; + + resetMatches(); + + scope.select = function(activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + selected = true; + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + modelCtrl.$setValidity('parse', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); resetMatches(); - //we need to propagate user's query so we can higlight matches - scope.query = undefined; + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) { + $timeout(function() { element[0].focus(); }, 0, false); + } + }; - //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later - var timeoutPromise; + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function(evt) { + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } - var scheduleSearchWithTimeout = function(inputValue) { - timeoutPromise = $timeout(function () { - getMatchesAsync(inputValue); - }, waitTime); - }; + // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results + if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) { + resetMatches(); + scope.$digest(); + return; + } - var cancelPreviousTimeout = function() { - if (timeoutPromise) { - $timeout.cancel(timeoutPromise); + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function() { + if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) { + selected = true; + scope.$apply(function() { + scope.select(scope.activeIdx); + }); + } + hasFocus = false; + selected = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function(evt) { + // Issue #3973 + // Firefox treats right click as a click on document + if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) { + resetMatches(); + if (!$rootScope.$$phase) { + scope.$digest(); } - }; + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function() { + $document.unbind('click', dismissClickHandler); + if (appendToBody || appendToElementId) { + $popup.remove(); + } + + if (appendToBody) { + angular.element($window).unbind('resize', fireRecalculating); + $document.find('body').unbind('scroll', fireRecalculating); + } + // Prevent jQuery cache memory leak + popUpEl.remove(); + }); + + var $popup = $compile(popUpEl)(scope); + + if (appendToBody) { + $document.find('body').append($popup); + } else if (appendToElementId !== false) { + angular.element($document[0].getElementById(appendToElementId)).append($popup); + } else { + element.after($popup); + } + + this.init = function(_modelCtrl, _ngModelOptions) { + modelCtrl = _modelCtrl; + ngModelOptions = _ngModelOptions; //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue - modelCtrl.$parsers.unshift(function (inputValue) { - + modelCtrl.$parsers.unshift(function(inputValue) { hasFocus = true; - if (inputValue && inputValue.length >= minSearch) { + if (minLength === 0 || inputValue && inputValue.length >= minLength) { if (waitTime > 0) { cancelPreviousTimeout(); scheduleSearchWithTimeout(inputValue); @@ -3714,7 +7471,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap if (!inputValue) { // Reset in case user had typed something previously. modelCtrl.$setValidity('editable', true); - return inputValue; + return null; } else { modelCtrl.$setValidity('editable', false); return undefined; @@ -3722,18 +7479,21 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap } }); - modelCtrl.$formatters.push(function (modelValue) { - + modelCtrl.$formatters.push(function(modelValue) { var candidateViewValue, emptyViewValue; var locals = {}; - if (inputFormatter) { + // The validity may be set to false via $parsers (see above) if + // the model is restricted to selected values. If the model + // is set manually it is considered to be valid. + if (!isEditable) { + modelCtrl.$setValidity('editable', true); + } + if (inputFormatter) { locals.$model = modelValue; return inputFormatter(originalScope, locals); - } else { - //it might happen that we don't have enough info to properly render input value //we need to check for this situation and simply return model value if we can't apply custom formatting locals[parserResult.itemName] = modelValue; @@ -3741,160 +7501,626 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap locals[parserResult.itemName] = undefined; emptyViewValue = parserResult.viewMapper(originalScope, locals); - return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue; } }); + }; + }]) - scope.select = function (activeIdx) { - //called from within the $digest() cycle - var locals = {}; - var model, item; - - locals[parserResult.itemName] = item = scope.matches[activeIdx].model; - model = parserResult.modelMapper(originalScope, locals); - $setModelValue(originalScope, model); - modelCtrl.$setValidity('editable', true); - - onSelectCallback(originalScope, { - $item: item, - $model: model, - $label: parserResult.viewMapper(originalScope, locals) - }); - - resetMatches(); - - //return focus to the input element if a match was selected via a mouse click event - // use timeout to avoid $rootScope:inprog error - $timeout(function() { element[0].focus(); }, 0, false); - }; - - //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) - element.bind('keydown', function (evt) { - - //typeahead is open and an "interesting" key was pressed - if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { - return; - } - - // if there's nothing selected (i.e. focusFirst) and enter is hit, don't do anything - if (scope.activeIdx == -1 && (evt.which === 13 || evt.which === 9)) { - return; - } - - evt.preventDefault(); - - if (evt.which === 40) { - scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; - scope.$digest(); - - } else if (evt.which === 38) { - scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; - scope.$digest(); - - } else if (evt.which === 13 || evt.which === 9) { - scope.$apply(function () { - scope.select(scope.activeIdx); - }); - - } else if (evt.which === 27) { - evt.stopPropagation(); - - resetMatches(); - scope.$digest(); - } - }); - - element.bind('blur', function (evt) { - hasFocus = false; - }); - - // Keep reference to click handler to unbind it. - var dismissClickHandler = function (evt) { - if (element[0] !== evt.target) { - resetMatches(); - scope.$digest(); - } - }; - - $document.bind('click', dismissClickHandler); - - originalScope.$on('$destroy', function(){ - $document.unbind('click', dismissClickHandler); - if (appendToBody) { - $popup.remove(); - } - }); - - var $popup = $compile(popUpEl)(scope); - if (appendToBody) { - $document.find('body').append($popup); - } else { - element.after($popup); - } - } - }; - -}]) - - .directive('typeaheadPopup', function () { + .directive('uibTypeahead', function() { return { - restrict:'EA', - scope:{ - matches:'=', - query:'=', - active:'=', - position:'=', - select:'&' - }, - replace:true, - templateUrl:'template/typeahead/typeahead-popup.html', - link:function (scope, element, attrs) { + controller: 'UibTypeaheadController', + require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'], + link: function(originalScope, element, attrs, ctrls) { + ctrls[2].init(ctrls[0], ctrls[1]); + } + }; + }) + .directive('uibTypeaheadPopup', function() { + return { + scope: { + matches: '=', + query: '=', + active: '=', + position: '&', + moveInProgress: '=', + select: '&' + }, + replace: true, + templateUrl: function(element, attrs) { + return attrs.popupTemplateUrl || 'template/typeahead/typeahead-popup.html'; + }, + link: function(scope, element, attrs) { scope.templateUrl = attrs.templateUrl; - scope.isOpen = function () { + scope.isOpen = function() { return scope.matches.length > 0; }; - scope.isActive = function (matchIdx) { + scope.isActive = function(matchIdx) { return scope.active == matchIdx; }; - scope.selectActive = function (matchIdx) { + scope.selectActive = function(matchIdx) { scope.active = matchIdx; }; - scope.selectMatch = function (activeIdx) { + scope.selectMatch = function(activeIdx) { scope.select({activeIdx:activeIdx}); }; } }; }) - .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { + .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) { return { - restrict:'EA', - scope:{ - index:'=', - match:'=', - query:'=' + scope: { + index: '=', + match: '=', + query: '=' }, - link:function (scope, element, attrs) { + link:function(scope, element, attrs) { var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; - $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ - element.replaceWith($compile(tplContent.trim())(scope)); + $templateRequest(tplUrl).then(function(tplContent) { + $compile(tplContent.trim())(scope, function(clonedElement) { + element.replaceWith(clonedElement); + }); }); } }; }]) - .filter('typeaheadHighlight', function() { + .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) { + var isSanitizePresent; + isSanitizePresent = $injector.has('$sanitize'); function escapeRegexp(queryToEscape) { + // Regex: capture the whole query string and replace it with the string that will be used to match + // the results, for example if the capture is "a" the result will be \a return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); } + function containsHtml(matchItem) { + return /<.*>/g.test(matchItem); + } + return function(matchItem, query) { - return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + if (!isSanitizePresent && containsHtml(matchItem)) { + $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger + } + matchItem = query? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag + if (!isSanitizePresent) { + matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive + } + return matchItem; }; - }); + }]); + +/* Deprecated typeahead below */ + +angular.module('ui.bootstrap.typeahead') + .value('$typeaheadSuppressWarning', false) + .service('typeaheadParser', ['$parse', 'uibTypeaheadParser', '$log', '$typeaheadSuppressWarning', function($parse, uibTypeaheadParser, $log, $typeaheadSuppressWarning) { + if (!$typeaheadSuppressWarning) { + $log.warn('typeaheadParser is now deprecated. Use uibTypeaheadParser instead.'); + } + + return uibTypeaheadParser; + }]) + + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$uibPosition', 'typeaheadParser', '$log', '$typeaheadSuppressWarning', + function($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser, $log, $typeaheadSuppressWarning) { + var HOT_KEYS = [9, 13, 27, 38, 40]; + var eventDebounceTime = 200; + return { + require: ['ngModel', '^?ngModelOptions'], + link: function(originalScope, element, attrs, ctrls) { + if (!$typeaheadSuppressWarning) { + $log.warn('typeahead is now deprecated. Use uib-typeahead instead.'); + } + var modelCtrl = ctrls[0]; + var ngModelOptions = ctrls[1]; + //SUPPORTED ATTRIBUTES (OPTIONS) + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minLength = originalScope.$eval(attrs.typeaheadMinLength); + if (!minLength && minLength !== 0) { + minLength = 1; + } + + //minimal wait time after last character typed before typeahead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); + + //should it select highlighted popup value when losing focus? + var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false; + + //binding to a variable that indicates if there were no results after the query is completed + var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop; + + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + + var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; + + var appendToElementId = attrs.typeaheadAppendToElementId || false; + + var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false; + + //If input matches an item of the list exactly, select it automatically + var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false; + + //INTERNAL VARIABLES + + //model setter executed upon match selection + var parsedModel = $parse(attrs.ngModel); + var invokeModelSetter = $parse(attrs.ngModel + '($$$p)'); + var $setModelValue = function(scope, newValue) { + if (angular.isFunction(parsedModel(originalScope)) && + ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) { + return invokeModelSetter(scope, {$$$p: newValue}); + } else { + return parsedModel.assign(scope, newValue); + } + }; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + var hasFocus; + + //Used to avoid bug in iOS webview where iOS keyboard does not fire + //mousedown & mouseup events + //Issue #3699 + var selected; + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + var offDestroy = originalScope.$on('$destroy', function() { + scope.$destroy(); + }); + scope.$on('$destroy', offDestroy); + + // WAI-ARIA + var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + element.attr({ + 'aria-autocomplete': 'list', + 'aria-expanded': false, + 'aria-owns': popupId + }); + + //pop-up element used to display matches + var popUpEl = angular.element('
    '); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + 'move-in-progress': 'moveInProgress', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) { + popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl); + } + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; + + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + + var inputIsExactMatch = function(inputValue, index) { + if (scope.matches.length > index && inputValue) { + return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase(); + } + + return false; + }; + + var getMatchesAsync = function(inputValue) { + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + isNoResultsSetter(originalScope, false); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches && matches.length > 0) { + scope.activeIdx = focusFirst ? 0 : -1; + isNoResultsSetter(originalScope, false); + scope.matches.length = 0; + + //transform labels + for (var i = 0; i < matches.length; i++) { + locals[parserResult.itemName] = matches[i]; + scope.matches.push({ + id: getMatchId(i), + label: parserResult.viewMapper(scope, locals), + model: matches[i] + }); + } + + scope.query = inputValue; + //position pop-up with matches - we need to re-calculate its position each time we are opening a window + //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page + //due to other elements being rendered + recalculatePosition(); + + element.attr('aria-expanded', true); + + //Select the single remaining option if user input matches + if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { + scope.select(0); + } + } else { + resetMatches(); + isNoResultsSetter(originalScope, true); + } + } + if (onCurrentRequest) { + isLoadingSetter(originalScope, false); + } + }, function() { + resetMatches(); + isLoadingSetter(originalScope, false); + isNoResultsSetter(originalScope, true); + }); + }; + + // bind events only if appendToBody params exist - performance feature + if (appendToBody) { + angular.element($window).bind('resize', fireRecalculating); + $document.find('body').bind('scroll', fireRecalculating); + } + + // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later + var timeoutEventPromise; + + // Default progress type + scope.moveInProgress = false; + + function fireRecalculating() { + if (!scope.moveInProgress) { + scope.moveInProgress = true; + scope.$digest(); + } + + // Cancel previous timeout + if (timeoutEventPromise) { + $timeout.cancel(timeoutEventPromise); + } + + // Debounced executing recalculate after events fired + timeoutEventPromise = $timeout(function() { + // if popup is visible + if (scope.matches.length) { + recalculatePosition(); + } + + scope.moveInProgress = false; + }, eventDebounceTime); + } + + // recalculate actual position and set new values to scope + // after digest loop is popup in right position + function recalculatePosition() { + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top += element.prop('offsetHeight'); + } + + resetMatches(); + + //we need to propagate user's query so we can higlight matches + scope.query = undefined; + + //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later + var timeoutPromise; + + var scheduleSearchWithTimeout = function(inputValue) { + timeoutPromise = $timeout(function() { + getMatchesAsync(inputValue); + }, waitTime); + }; + + var cancelPreviousTimeout = function() { + if (timeoutPromise) { + $timeout.cancel(timeoutPromise); + } + }; + + //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM + //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue + modelCtrl.$parsers.unshift(function(inputValue) { + hasFocus = true; + + if (minLength === 0 || inputValue && inputValue.length >= minLength) { + if (waitTime > 0) { + cancelPreviousTimeout(); + scheduleSearchWithTimeout(inputValue); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); + cancelPreviousTimeout(); + resetMatches(); + } + + if (isEditable) { + return inputValue; + } else { + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return null; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } + } + }); + + modelCtrl.$formatters.push(function(modelValue) { + var candidateViewValue, emptyViewValue; + var locals = {}; + + // The validity may be set to false via $parsers (see above) if + // the model is restricted to selected values. If the model + // is set manually it is considered to be valid. + if (!isEditable) { + modelCtrl.$setValidity('editable', true); + } + + if (inputFormatter) { + locals.$model = modelValue; + return inputFormatter(originalScope, locals); + } else { + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue; + } + }); + + scope.select = function(activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + selected = true; + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + modelCtrl.$setValidity('parse', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); + + resetMatches(); + + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) { + $timeout(function() { element[0].focus(); }, 0, false); + } + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function(evt) { + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results + if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) { + resetMatches(); + scope.$digest(); + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function() { + if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) { + selected = true; + scope.$apply(function() { + scope.select(scope.activeIdx); + }); + } + hasFocus = false; + selected = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function(evt) { + // Issue #3973 + // Firefox treats right click as a click on document + if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) { + resetMatches(); + if (!$rootScope.$$phase) { + scope.$digest(); + } + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function() { + $document.unbind('click', dismissClickHandler); + if (appendToBody || appendToElementId) { + $popup.remove(); + } + + if (appendToBody) { + angular.element($window).unbind('resize', fireRecalculating); + $document.find('body').unbind('scroll', fireRecalculating); + } + // Prevent jQuery cache memory leak + popUpEl.remove(); + }); + + var $popup = $compile(popUpEl)(scope); + + if (appendToBody) { + $document.find('body').append($popup); + } else if (appendToElementId !== false) { + angular.element($document[0].getElementById(appendToElementId)).append($popup); + } else { + element.after($popup); + } + } + }; + }]) + + .directive('typeaheadPopup', ['$typeaheadSuppressWarning', '$log', function($typeaheadSuppressWarning, $log) { + return { + scope: { + matches: '=', + query: '=', + active: '=', + position: '&', + moveInProgress: '=', + select: '&' + }, + replace: true, + templateUrl: function(element, attrs) { + return attrs.popupTemplateUrl || 'template/typeahead/typeahead-popup.html'; + }, + link: function(scope, element, attrs) { + + if (!$typeaheadSuppressWarning) { + $log.warn('typeahead-popup is now deprecated. Use uib-typeahead-popup instead.'); + } + scope.templateUrl = attrs.templateUrl; + + scope.isOpen = function() { + return scope.matches.length > 0; + }; + + scope.isActive = function(matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function(matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function(activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }]) + + .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', '$typeaheadSuppressWarning', '$log', function($templateRequest, $compile, $parse, $typeaheadSuppressWarning, $log) { + return { + restrict: 'EA', + scope: { + index: '=', + match: '=', + query: '=' + }, + link:function(scope, element, attrs) { + if (!$typeaheadSuppressWarning) { + $log.warn('typeahead-match is now deprecated. Use uib-typeahead-match instead.'); + } + + var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; + $templateRequest(tplUrl).then(function(tplContent) { + $compile(tplContent.trim())(scope, function(clonedElement) { + element.replaceWith(clonedElement); + }); + }); + } + }; + }]) + + .filter('typeaheadHighlight', ['$sce', '$injector', '$log', '$typeaheadSuppressWarning', function($sce, $injector, $log, $typeaheadSuppressWarning) { + var isSanitizePresent; + isSanitizePresent = $injector.has('$sanitize'); + + function escapeRegexp(queryToEscape) { + // Regex: capture the whole query string and replace it with the string that will be used to match + // the results, for example if the capture is "a" the result will be \a + return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + } + + function containsHtml(matchItem) { + return /<.*>/g.test(matchItem); + } + + return function(matchItem, query) { + if (!$typeaheadSuppressWarning) { + $log.warn('typeaheadHighlight is now deprecated. Use uibTypeaheadHighlight instead.'); + } + + if (!isSanitizePresent && containsHtml(matchItem)) { + $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger + } + + matchItem = query? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag + if (!isSanitizePresent) { + matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive + } + + return matchItem; + }; + }]); +!angular.$$csp() && angular.element(document).find('head').prepend(''); \ No newline at end of file diff --git a/vendor/assets/components/angular-bootstrap/ui-bootstrap.min.js b/vendor/assets/components/angular-bootstrap/ui-bootstrap.min.js index a6383c443..cbabc3283 100644 --- a/vendor/assets/components/angular-bootstrap/ui-bootstrap.min.js +++ b/vendor/assets/components/angular-bootstrap/ui-bootstrap.min.js @@ -2,8 +2,10 @@ * angular-ui-bootstrap * http://angular-ui.github.io/bootstrap/ - * Version: 0.12.1 - 2015-02-20 + * Version: 0.14.3 - 2015-10-23 * License: MIT */ -angular.module("ui.bootstrap",["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(a){return{link:function(b,c,d){function e(b){function d(){j===e&&(j=void 0)}var e=a(c,b);return j&&j.cancel(),j=e,e.then(d,d),e}function f(){k?(k=!1,g()):(c.removeClass("collapse").addClass("collapsing"),e({height:c[0].scrollHeight+"px"}).then(g))}function g(){c.removeClass("collapsing"),c.addClass("collapse in"),c.css({height:"auto"})}function h(){if(k)k=!1,i(),c.css({height:0});else{c.css({height:c[0].scrollHeight+"px"});{c[0].offsetWidth}c.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(i)}}function i(){c.removeClass("collapsing"),c.addClass("collapse")}var j,k=!0;b.$watch(d.collapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.$watch("isOpen",function(b){b&&d.closeOthers(a)}),a.toggleOpen=function(){a.isDisabled||(a.isOpen=!a.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b,this.close=a.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}).directive("dismissOnTimeout",["$timeout",function(a){return{require:"alert",link:function(b,c,d,e){a(function(){e.close()},parseInt(d.dismissOnTimeout,10))}}}]),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$interval","$transition",function(a,b,c,d){function e(){f();var b=+a.interval;!isNaN(b)&&b>0&&(h=c(g,b))}function f(){h&&(c.cancel(h),h=null)}function g(){var b=+a.interval;i&&!isNaN(b)&&b>0?a.next():a.pause()}var h,i,j=this,k=j.slides=a.slides=[],l=-1;j.currentSlide=null;var m=!1;j.select=a.select=function(c,f){function g(){if(!m){if(j.currentSlide&&angular.isString(f)&&!a.noTransition&&c.$element){c.$element.addClass(f);{c.$element[0].offsetWidth}angular.forEach(k,function(a){angular.extend(a,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(c,{direction:f,active:!0,entering:!0}),angular.extend(j.currentSlide||{},{direction:f,leaving:!0}),a.$currentTransition=d(c.$element,{}),function(b,c){a.$currentTransition.then(function(){h(b,c)},function(){h(b,c)})}(c,j.currentSlide)}else h(c,j.currentSlide);j.currentSlide=c,l=i,e()}}function h(b,c){angular.extend(b,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(c||{},{direction:"",active:!1,leaving:!1,entering:!1}),a.$currentTransition=null}var i=k.indexOf(c);void 0===f&&(f=i>l?"next":"prev"),c&&c!==j.currentSlide&&(a.$currentTransition?(a.$currentTransition.cancel(),b(g)):g())},a.$on("$destroy",function(){m=!0}),j.indexOfSlide=function(a){return k.indexOf(a)},a.next=function(){var b=(l+1)%k.length;return a.$currentTransition?void 0:j.select(k[b],"next")},a.prev=function(){var b=0>l-1?k.length-1:l-1;return a.$currentTransition?void 0:j.select(k[b],"prev")},a.isActive=function(a){return j.currentSlide===a},a.$watch("interval",e),a.$on("$destroy",f),a.play=function(){i||(i=!0,e())},a.pause=function(){a.noPause||(i=!1,f())},j.addSlide=function(b,c){b.$element=c,k.push(b),1===k.length||b.active?(j.select(k[k.length-1]),1==k.length&&a.play()):b.active=!1},j.removeSlide=function(a){var b=k.indexOf(a);k.splice(b,1),k.length>0&&a.active?j.select(b>=k.length?k[b-1]:k[b]):l>b&&l--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$locale","orderByFilter",function(a,b){function c(a){var c=[],d=a.split("");return angular.forEach(e,function(b,e){var f=a.indexOf(e);if(f>-1){a=a.split(""),d[f]="("+b.regex+")",a[f]="$";for(var g=f+1,h=f+e.length;h>g;g++)d[g]="",a[g]="$";a=a.join(""),c.push({index:f,apply:b.apply})}}),{regex:new RegExp("^"+d.join("")+"$"),map:b(c,"index")}}function d(a,b,c){return 1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}this.parsers={};var e={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:a.DATETIME_FORMATS.MONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.MONTH.indexOf(b)}},MMM:{regex:a.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.SHORTMONTH.indexOf(b)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:a.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:a.DATETIME_FORMATS.SHORTDAY.join("|")}};this.parse=function(b,e){if(!angular.isString(b)||!e)return b;e=a.DATETIME_FORMATS[e]||e,this.parsers[e]||(this.parsers[e]=c(e));var f=this.parsers[e],g=f.regex,h=f.map,i=b.match(g);if(i&&i.length){for(var j,k={year:1900,month:0,date:1,hours:0},l=1,m=i.length;m>l;l++){var n=h[l-1];n.apply&&n.apply.call(k,i[l])}return d(k.year,k.month,k.date)&&(j=new Date(k.year,k.month,k.date,k.hours)),j}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$timeout","$log","dateFilter","datepickerConfig",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","minMode","maxMode","showWeeks","startingDay","yearRange"],function(c,e){i[c]=angular.isDefined(b[c])?8>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):h[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=h[d]?new Date(h[d]):null}),a.datepickerMode=a.datepickerMode||h.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),this.activeDate=angular.isDefined(b.initDate)?a.$parent.$eval(b.initDate):new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$modelValue){var a=new Date(j.$modelValue),b=!isNaN(a);b?this.activeDate=a:f.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'),j.$setValidity("date",b)}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$modelValue?new Date(j.$modelValue):null;j.$setValidity("date-disabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$modelValue?new Date(j.$modelValue):null;return{date:a,label:g(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$modelValue?new Date(j.$modelValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){e(function(){i.element[0].focus()},0,!1)};a.$on("datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate),k()}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):(a.toggleMode("up"===c?1:-1),k())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{datepickerMode:"=?",dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}).directive("daypicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(b,c,d,e){function f(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?i[b]:29}function g(a,b){var c=new Array(b),d=new Date(a),e=0;for(d.setHours(12);b>e;)c[e++]=new Date(d),d.setDate(d.getDate()+1);return c}function h(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}b.showWeeks=e.showWeeks,e.step={months:1},e.element=c;var i=[31,28,31,30,31,30,31,31,30,31,30,31];e._refreshView=function(){var c=e.activeDate.getFullYear(),d=e.activeDate.getMonth(),f=new Date(c,d,1),i=e.startingDay-f.getDay(),j=i>0?7-i:-i,k=new Date(f);j>0&&k.setDate(-j+1);for(var l=g(k,42),m=0;42>m;m++)l[m]=angular.extend(e.createDateObject(l[m],e.formatDay),{secondary:l[m].getMonth()!==d,uid:b.uniqueId+"-"+m});b.labels=new Array(7);for(var n=0;7>n;n++)b.labels[n]={abbr:a(l[n].date,e.formatDayHeader),full:a(l[n].date,"EEEE")};if(b.title=a(e.activeDate,e.formatDayTitle),b.rows=e.split(l,7),b.showWeeks){b.weekNumbers=[];for(var o=h(b.rows[0][0].date),p=b.rows.length;b.weekNumbers.push(o++)f;f++)c[f]=angular.extend(e.createDateObject(new Date(d,f,1),e.formatMonth),{uid:b.uniqueId+"-"+f});b.title=a(e.activeDate,e.formatMonthTitle),b.rows=e.split(c,3)},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},e.handleKeyDown=function(a){var b=e.activeDate.getMonth();if("left"===a)b-=1;else if("up"===a)b-=3;else if("right"===a)b+=1;else if("down"===a)b+=3;else if("pageup"===a||"pagedown"===a){var c=e.activeDate.getFullYear()+("pageup"===a?-1:1);e.activeDate.setFullYear(c)}else"home"===a?b=0:"end"===a&&(b=11);e.activeDate.setMonth(b)},e.refreshView()}}}]).directive("yearpicker",["dateFilter",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(a,b,c,d){function e(a){return parseInt((a-1)/f,10)*f+1}var f=d.yearRange;d.step={years:f},d.element=b,d._refreshView=function(){for(var b=new Array(f),c=0,g=e(d.activeDate.getFullYear());f>c;c++)b[c]=angular.extend(d.createDateObject(new Date(g+c,0,1),d.formatYear),{uid:a.uniqueId+"-"+c});a.title=[b[0].label,b[f-1].label].join(" - "),a.rows=d.split(b,5)},d.compare=function(a,b){return a.getFullYear()-b.getFullYear()},d.handleKeyDown=function(a){var b=d.activeDate.getFullYear();"left"===a?b-=1:"up"===a?b-=5:"right"===a?b+=1:"down"===a?b+=5:"pageup"===a||"pagedown"===a?b+=("pageup"===a?-1:1)*d.step.years:"home"===a?b=e(d.activeDate.getFullYear()):"end"===a&&(b=e(d.activeDate.getFullYear())+f-1),d.activeDate.setFullYear(b)},d.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","dateParser","datepickerPopupConfig",function(a,b,c,d,e,f,g){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&"},link:function(h,i,j,k){function l(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function m(a){if(a){if(angular.isDate(a)&&!isNaN(a))return k.$setValidity("date",!0),a;if(angular.isString(a)){var b=f.parse(a,n)||new Date(a);return isNaN(b)?void k.$setValidity("date",!1):(k.$setValidity("date",!0),b)}return void k.$setValidity("date",!1)}return k.$setValidity("date",!0),null}var n,o=angular.isDefined(j.closeOnDateSelection)?h.$parent.$eval(j.closeOnDateSelection):g.closeOnDateSelection,p=angular.isDefined(j.datepickerAppendToBody)?h.$parent.$eval(j.datepickerAppendToBody):g.appendToBody;h.showButtonBar=angular.isDefined(j.showButtonBar)?h.$parent.$eval(j.showButtonBar):g.showButtonBar,h.getText=function(a){return h[a+"Text"]||g[a+"Text"]},j.$observe("datepickerPopup",function(a){n=a||g.datepickerPopup,k.$render()});var q=angular.element("
    ");q.attr({"ng-model":"date","ng-change":"dateSelection()"});var r=angular.element(q.children()[0]);j.datepickerOptions&&angular.forEach(h.$parent.$eval(j.datepickerOptions),function(a,b){r.attr(l(b),a)}),h.watchData={},angular.forEach(["minDate","maxDate","datepickerMode"],function(a){if(j[a]){var c=b(j[a]);if(h.$parent.$watch(c,function(b){h.watchData[a]=b}),r.attr(l(a),"watchData."+a),"datepickerMode"===a){var d=c.assign;h.$watch("watchData."+a,function(a,b){a!==b&&d(h.$parent,a)})}}}),j.dateDisabled&&r.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),k.$parsers.unshift(m),h.dateSelection=function(a){angular.isDefined(a)&&(h.date=a),k.$setViewValue(h.date),k.$render(),o&&(h.isOpen=!1,i[0].focus())},i.bind("input change keyup",function(){h.$apply(function(){h.date=k.$modelValue})}),k.$render=function(){var a=k.$viewValue?e(k.$viewValue,n):"";i.val(a),h.date=m(k.$modelValue)};var s=function(a){h.isOpen&&a.target!==i[0]&&h.$apply(function(){h.isOpen=!1})},t=function(a){h.keydown(a)};i.bind("keydown",t),h.keydown=function(a){27===a.which?(a.preventDefault(),a.stopPropagation(),h.close()):40!==a.which||h.isOpen||(h.isOpen=!0)},h.$watch("isOpen",function(a){a?(h.$broadcast("datepicker.focus"),h.position=p?d.offset(i):d.position(i),h.position.top=h.position.top+i.prop("offsetHeight"),c.bind("click",s)):c.unbind("click",s)}),h.select=function(a){if("today"===a){var b=new Date;angular.isDate(k.$modelValue)?(a=new Date(k.$modelValue),a.setFullYear(b.getFullYear(),b.getMonth(),b.getDate())):a=new Date(b.setHours(0,0,0,0))}h.dateSelection(a)},h.close=function(){h.isOpen=!1,i[0].focus()};var u=a(q)(h);q.remove(),p?c.find("body").append(u):i.after(u),h.$on("$destroy",function(){u.remove(),i.unbind("keydown",t),c.unbind("click",s)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(a,b){b.bind("click",function(a){a.preventDefault(),a.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function(a){var b=null;this.open=function(e){b||(a.bind("click",c),a.bind("keydown",d)),b&&b!==e&&(b.isOpen=!1),b=e},this.close=function(e){b===e&&(b=null,a.unbind("click",c),a.unbind("keydown",d))};var c=function(a){if(b){var c=b.getToggleElement();a&&c&&c[0].contains(a.target)||b.$apply(function(){b.isOpen=!1})}},d=function(a){27===a.which&&(b.focusToggleElement(),c())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function(a,b,c,d,e,f){var g,h=this,i=a.$new(),j=d.openClass,k=angular.noop,l=b.onToggle?c(b.onToggle):angular.noop;this.init=function(d){h.$element=d,b.isOpen&&(g=c(b.isOpen),k=g.assign,a.$watch(g,function(a){i.isOpen=!!a}))},this.toggle=function(a){return i.isOpen=arguments.length?!!a:!i.isOpen},this.isOpen=function(){return i.isOpen},i.getToggleElement=function(){return h.toggleElement},i.focusToggleElement=function(){h.toggleElement&&h.toggleElement[0].focus()},i.$watch("isOpen",function(b,c){f[b?"addClass":"removeClass"](h.$element,j),b?(i.focusToggleElement(),e.open(i)):e.close(i),k(a,b),angular.isDefined(b)&&b!==c&&l(a,{open:!!b})}),a.$on("$locationChangeSuccess",function(){i.isOpen=!1}),a.$on("$destroy",function(){i.$destroy()})}]).directive("dropdown",function(){return{controller:"DropdownController",link:function(a,b,c,d){d.init(b)}}}).directive("dropdownToggle",function(){return{require:"?^dropdown",link:function(a,b,c,d){if(d){d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0),i()})}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&(a.preventDefault(),e.$apply(function(){o.dismiss(b.key,"escape key press")})))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();if(h>=0&&!k){l=e.$new(!0),l.index=h;var i=angular.element("
    ");i.attr("backdrop-class",b.backdropClass),k=d(i)(l),f.append(k)}var j=angular.element("
    ");j.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,size:b.size,index:n.length()-1,animate:"animate"}).html(b.content);var o=d(j)(b.scope);n.top().value.modalDomEl=o,f.append(o),f.addClass(m)},o.close=function(a,b){var c=n.get(a);c&&(c.value.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a);c&&(c.value.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,backdropClass:b.backdropClass,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(f,g){e=f,this.config=g,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=g.itemsPerPage},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b){a.page!==b&&b>0&&b<=a.totalPages&&(e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages},a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render()});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@"},require:["pager","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase() -})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$document","$position","$interpolate",function(e,f,g,h,i,j){return function(e,k,l){function m(a){var b=a||n.trigger||l,d=c[b]||b;return{show:b,hide:d}}var n=angular.extend({},b,d),o=a(e),p=j.startSymbol(),q=j.endSymbol(),r="
    ';return{restrict:"EA",compile:function(){var a=f(r);return function(b,c,d){function f(){D.isOpen?l():j()}function j(){(!C||b.$eval(d[k+"Enable"]))&&(s(),D.popupDelay?z||(z=g(o,D.popupDelay,!1),z.then(function(a){a()})):o()())}function l(){b.$apply(function(){p()})}function o(){return z=null,y&&(g.cancel(y),y=null),D.content?(q(),w.css({top:0,left:0,display:"block"}),D.$digest(),E(),D.isOpen=!0,D.$digest(),E):angular.noop}function p(){D.isOpen=!1,g.cancel(z),z=null,D.animation?y||(y=g(r,500)):r()}function q(){w&&r(),x=D.$new(),w=a(x,function(a){A?h.find("body").append(a):c.after(a)})}function r(){y=null,w&&(w.remove(),w=null),x&&(x.$destroy(),x=null)}function s(){t(),u()}function t(){var a=d[k+"Placement"];D.placement=angular.isDefined(a)?a:n.placement}function u(){var a=d[k+"PopupDelay"],b=parseInt(a,10);D.popupDelay=isNaN(b)?n.popupDelay:b}function v(){var a=d[k+"Trigger"];F(),B=m(a),B.show===B.hide?c.bind(B.show,f):(c.bind(B.show,j),c.bind(B.hide,l))}var w,x,y,z,A=angular.isDefined(n.appendToBody)?n.appendToBody:!1,B=m(void 0),C=angular.isDefined(d[k+"Enable"]),D=b.$new(!0),E=function(){var a=i.positionElements(c,w,D.placement,A);a.top+="px",a.left+="px",w.css(a)};D.isOpen=!1,d.$observe(e,function(a){D.content=a,!a&&D.isOpen&&p()}),d.$observe(k+"Title",function(a){D.title=a});var F=function(){c.unbind(B.show,j),c.unbind(B.hide,l)};v();var G=b.$eval(d[k+"Animation"]);D.animation=angular.isDefined(G)?!!G:n.animation;var H=b.$eval(d[k+"AppendToBody"]);A=angular.isDefined(H)?H:A,A&&b.$on("$locationChangeSuccess",function(){D.isOpen&&p()}),b.$on("$destroy",function(){g.cancel(y),g.cancel(z),F(),r(),D=null})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,this.addBar=function(b,c){e||c.css({transition:"none"}),this.bars.push(b),b.$watch("value",function(c){b.percent=+(100*c/a.max).toFixed(2)}),b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(f)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff},a[b]);return a},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect())}),a.active=!0,a.onSelect()},b.addTab=function(a){c.push(a),1===c.length?a.active=!0:a.active&&b.select(a)},b.removeTab=function(a){var e=c.indexOf(a);if(a.active&&c.length>1&&!d){var f=e==c.length-1?e-1:e+1;b.select(c[f])}c.splice(e,1)};var d;a.$on("$destroy",function(){d=!0})}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){b.$watch("active",function(a){a&&f.select(b)}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function(a,b,c,d,e,f){function g(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===p[1]&&(b+=12)),b):void 0}function h(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function i(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a}function j(a){k(),o.$setViewValue(new Date(n)),l(a)}function k(){o.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function l(b){var c=n.getHours(),d=n.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:i(c),a.minutes="m"===b?d:i(d),a.meridian=n.getHours()<12?p[0]:p[1]}function m(a){var b=new Date(n.getTime()+6e4*a);n.setHours(b.getHours(),b.getMinutes()),j()}var n=new Date,o={$setViewValue:angular.noop},p=angular.isDefined(b.meridians)?a.$parent.$eval(b.meridians):f.meridians||e.DATETIME_FORMATS.AMPMS;this.init=function(c,d){o=c,o.$render=this.render;var e=d.eq(0),g=d.eq(1),h=angular.isDefined(b.mousewheel)?a.$parent.$eval(b.mousewheel):f.mousewheel;h&&this.setupMousewheelEvents(e,g),a.readonlyInput=angular.isDefined(b.readonlyInput)?a.$parent.$eval(b.readonlyInput):f.readonlyInput,this.setupInputEvents(e,g)};var q=f.hourStep;b.hourStep&&a.$parent.$watch(c(b.hourStep),function(a){q=parseInt(a,10)});var r=f.minuteStep;b.minuteStep&&a.$parent.$watch(c(b.minuteStep),function(a){r=parseInt(a,10)}),a.showMeridian=f.showMeridian,b.showMeridian&&a.$parent.$watch(c(b.showMeridian),function(b){if(a.showMeridian=!!b,o.$error.time){var c=g(),d=h();angular.isDefined(c)&&angular.isDefined(d)&&(n.setHours(c),j())}else l()}),this.setupMousewheelEvents=function(b,c){var d=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){o.$setViewValue(null),o.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=g();angular.isDefined(a)?(n.setHours(a),j("h")):d(!0)},b.bind("blur",function(){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=i(a.hours)})}),a.updateMinutes=function(){var a=h();angular.isDefined(a)?(n.setMinutes(a),j("m")):d(void 0,!0)},c.bind("blur",function(){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=i(a.minutes)})})},this.render=function(){var a=o.$modelValue?new Date(o.$modelValue):null;isNaN(a)?(o.$setValidity("time",!1),d.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(a&&(n=a),k(),l())},a.incrementHours=function(){m(60*q)},a.decrementHours=function(){m(60*-q)},a.incrementMinutes=function(){m(r)},a.decrementMinutes=function(){m(-r)},a.toggleMeridian=function(){m(720*(n.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",replace:!0,scope:{},templateUrl:"template/timepicker/timepicker.html",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?i.$eval(k.typeaheadAppendToBody):!1,u=i.$eval(k.typeaheadFocusFirst)!==!1,v=b(k.ngModel).assign,w=g.parse(k.typeahead),x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y="typeahead-"+x.$id+"-"+Math.floor(1e4*Math.random());j.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":y});var z=angular.element("
    ");z.attr({id:y,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&z.attr("template-url",k.typeaheadTemplateUrl);var A=function(){x.matches=[],x.activeIdx=-1,j.attr("aria-expanded",!1)},B=function(a){return y+"-option-"+a};x.$watch("activeIdx",function(a){0>a?j.removeAttr("aria-activedescendant"):j.attr("aria-activedescendant",B(a))});var C=function(a){var b={$viewValue:a};q(i,!0),c.when(w.source(i,b)).then(function(c){var d=a===l.$viewValue;if(d&&m)if(c.length>0){x.activeIdx=u?0:-1,x.matches.length=0;for(var e=0;e=n?o>0?(F(),E(a)):C(a):(q(i,!1),F(),A()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[w.itemName]=a,b=w.viewMapper(i,d),d[w.itemName]=void 0,c=w.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,e={};e[w.itemName]=c=x.matches[a].model,b=w.modelMapper(i,e),v(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:w.viewMapper(i,e)}),A(),d(function(){j[0].focus()},0,!1)},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(-1!=x.activeIdx||13!==a.which&&9!==a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx>0?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),A(),x.$digest()))}),j.bind("blur",function(){m=!1});var G=function(a){j[0]!==a.target&&(A(),x.$digest())};e.bind("click",G),i.$on("$destroy",function(){e.unbind("click",G),t&&H.remove()});var H=a(z)(x);t?e.find("body").append(H):j.after(H)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?(""+b).replace(new RegExp(a(c),"gi"),"$&"):b}}); \ No newline at end of file +angular.module("ui.bootstrap",["ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.collapse",[]).directive("uibCollapse",["$animate","$injector",function(a,b){var c=b.has("$animateCss")?b.get("$animateCss"):null;return{link:function(b,d,e){function f(){d.removeClass("collapse").addClass("collapsing").attr("aria-expanded",!0).attr("aria-hidden",!1),c?c(d,{addClass:"in",easing:"ease",to:{height:d[0].scrollHeight+"px"}}).start()["finally"](g):a.addClass(d,"in",{to:{height:d[0].scrollHeight+"px"}}).then(g)}function g(){d.removeClass("collapsing").addClass("collapse").css({height:"auto"})}function h(){return d.hasClass("collapse")||d.hasClass("in")?(d.css({height:d[0].scrollHeight+"px"}).removeClass("collapse").addClass("collapsing").attr("aria-expanded",!1).attr("aria-hidden",!0),void(c?c(d,{removeClass:"in",to:{height:"0"}}).start()["finally"](i):a.removeClass(d,"in",{to:{height:"0"}}).then(i))):i()}function i(){d.css({height:"0"}),d.removeClass("collapsing").addClass("collapse")}b.$watch(e.uibCollapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.collapse").value("$collapseSuppressWarning",!1).directive("collapse",["$animate","$injector","$log","$collapseSuppressWarning",function(a,b,c,d){var e=b.has("$animateCss")?b.get("$animateCss"):null;return{link:function(b,f,g){function h(){f.removeClass("collapse").addClass("collapsing").attr("aria-expanded",!0).attr("aria-hidden",!1),e?e(f,{easing:"ease",to:{height:f[0].scrollHeight+"px"}}).start().done(i):a.animate(f,{},{height:f[0].scrollHeight+"px"}).then(i)}function i(){f.removeClass("collapsing").addClass("collapse in").css({height:"auto"})}function j(){return f.hasClass("collapse")||f.hasClass("in")?(f.css({height:f[0].scrollHeight+"px"}).removeClass("collapse in").addClass("collapsing").attr("aria-expanded",!1).attr("aria-hidden",!0),void(e?e(f,{to:{height:"0"}}).start().done(k):a.animate(f,{},{height:"0"}).then(k))):k()}function k(){f.css({height:"0"}),f.removeClass("collapsing").addClass("collapse")}d||c.warn("collapse is now deprecated. Use uib-collapse instead."),b.$watch(g.collapse,function(a){a?j():h()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("uibAccordionConfig",{closeOthers:!0}).controller("UibAccordionController",["$scope","$attrs","uibAccordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("uibAccordion",function(){return{controller:"UibAccordionController",controllerAs:"accordion",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"template/accordion/accordion.html"}}}).directive("uibAccordionGroup",function(){return{require:"^uibAccordion",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/accordion/accordion-group.html"},scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.openClass=c.openClass||"panel-open",a.panelClass=c.panelClass,a.$watch("isOpen",function(c){b.toggleClass(a.openClass,!!c),c&&d.closeOthers(a)}),a.toggleOpen=function(b){a.isDisabled||b&&32!==b.which||(a.isOpen=!a.isOpen)}}}}).directive("uibAccordionHeading",function(){return{transclude:!0,template:"",replace:!0,require:"^uibAccordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,angular.noop))}}}).directive("uibAccordionTransclude",function(){return{require:["?^uibAccordionGroup","?^accordionGroup"],link:function(a,b,c,d){d=d[0]?d[0]:d[1],a.$watch(function(){return d[c.uibAccordionTransclude]},function(a){a&&(b.find("span").html(""),b.find("span").append(a))})}}}),angular.module("ui.bootstrap.accordion").value("$accordionSuppressWarning",!1).controller("AccordionController",["$scope","$attrs","$controller","$log","$accordionSuppressWarning",function(a,b,c,d,e){e||d.warn("AccordionController is now deprecated. Use UibAccordionController instead."),angular.extend(this,c("UibAccordionController",{$scope:a,$attrs:b}))}]).directive("accordion",["$log","$accordionSuppressWarning",function(a,b){return{restrict:"EA",controller:"AccordionController",controllerAs:"accordion",transclude:!0,replace:!1,templateUrl:function(a,b){return b.templateUrl||"template/accordion/accordion.html"},link:function(){b||a.warn("accordion is now deprecated. Use uib-accordion instead.")}}}]).directive("accordionGroup",["$log","$accordionSuppressWarning",function(a,b){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/accordion/accordion-group.html"},scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(c,d,e,f){b||a.warn("accordion-group is now deprecated. Use uib-accordion-group instead."),f.addGroup(c),c.openClass=e.openClass||"panel-open",c.panelClass=e.panelClass,c.$watch("isOpen",function(a){d.toggleClass(c.openClass,!!a),a&&f.closeOthers(c)}),c.toggleOpen=function(a){c.isDisabled||a&&32!==a.which||(c.isOpen=!c.isOpen)}}}}]).directive("accordionHeading",["$log","$accordionSuppressWarning",function(a,b){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(c,d,e,f,g){b||a.warn("accordion-heading is now deprecated. Use uib-accordion-heading instead."),f.setHeading(g(c,angular.noop))}}}]).directive("accordionTransclude",["$log","$accordionSuppressWarning",function(a,b){return{require:"^accordionGroup",link:function(c,d,e,f){b||a.warn("accordion-transclude is now deprecated. Use uib-accordion-transclude instead."),c.$watch(function(){return f[e.accordionTransclude]},function(a){a&&(d.find("span").html(""),d.find("span").append(a))})}}}]),angular.module("ui.bootstrap.alert",[]).controller("UibAlertController",["$scope","$attrs","$interpolate","$timeout",function(a,b,c,d){a.closeable=!!b.close;var e=angular.isDefined(b.dismissOnTimeout)?c(b.dismissOnTimeout)(a.$parent):null;e&&d(function(){a.close()},parseInt(e,10))}]).directive("uibAlert",function(){return{controller:"UibAlertController",controllerAs:"alert",templateUrl:function(a,b){return b.templateUrl||"template/alert/alert.html"},transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}),angular.module("ui.bootstrap.alert").value("$alertSuppressWarning",!1).controller("AlertController",["$scope","$attrs","$controller","$log","$alertSuppressWarning",function(a,b,c,d,e){e||d.warn("AlertController is now deprecated. Use UibAlertController instead."),angular.extend(this,c("UibAlertController",{$scope:a,$attrs:b}))}]).directive("alert",["$log","$alertSuppressWarning",function(a,b){return{controller:"AlertController",controllerAs:"alert",templateUrl:function(a,b){return b.templateUrl||"template/alert/alert.html"},transclude:!0,replace:!0,scope:{type:"@",close:"&"},link:function(){b||a.warn("alert is now deprecated. Use uib-alert instead.")}}}]),angular.module("ui.bootstrap.buttons",[]).constant("uibButtonConfig",{activeClass:"active",toggleEvent:"click"}).controller("UibButtonsController",["uibButtonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("uibBtnRadio",function(){return{require:["uibBtnRadio","ngModel"],controller:"UibButtonsController",controllerAs:"buttons",link:function(a,b,c,d){var e=d[0],f=d[1];b.find("input").css({display:"none"}),f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.uibBtnRadio)))},b.on(e.toggleEvent,function(){if(!c.disabled){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.uibBtnRadio)),f.$render()})}})}}}).directive("uibBtnCheckbox",function(){return{require:["uibBtnCheckbox","ngModel"],controller:"UibButtonsController",controllerAs:"button",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){return angular.isDefined(b)?a.$eval(b):c}var h=d[0],i=d[1];b.find("input").css({display:"none"}),i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.on(h.toggleEvent,function(){c.disabled||a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.buttons").value("$buttonsSuppressWarning",!1).controller("ButtonsController",["$controller","$log","$buttonsSuppressWarning",function(a,b,c){c||b.warn("ButtonsController is now deprecated. Use UibButtonsController instead."),angular.extend(this,a("UibButtonsController"))}]).directive("btnRadio",["$log","$buttonsSuppressWarning",function(a,b){return{require:["btnRadio","ngModel"],controller:"ButtonsController",controllerAs:"buttons",link:function(c,d,e,f){b||a.warn("btn-radio is now deprecated. Use uib-btn-radio instead.");var g=f[0],h=f[1];d.find("input").css({display:"none"}),h.$render=function(){d.toggleClass(g.activeClass,angular.equals(h.$modelValue,c.$eval(e.btnRadio)))},d.bind(g.toggleEvent,function(){if(!e.disabled){var a=d.hasClass(g.activeClass);(!a||angular.isDefined(e.uncheckable))&&c.$apply(function(){h.$setViewValue(a?null:c.$eval(e.btnRadio)),h.$render()})}})}}}]).directive("btnCheckbox",["$document","$log","$buttonsSuppressWarning",function(a,b,c){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",controllerAs:"button",link:function(d,e,f,g){function h(){return j(f.btnCheckboxTrue,!0)}function i(){return j(f.btnCheckboxFalse,!1)}function j(a,b){var c=d.$eval(a);return angular.isDefined(c)?c:b}c||b.warn("btn-checkbox is now deprecated. Use uib-btn-checkbox instead.");var k=g[0],l=g[1];e.find("input").css({display:"none"}),l.$render=function(){e.toggleClass(k.activeClass,angular.equals(l.$modelValue,h()))},e.bind(k.toggleEvent,function(){f.disabled||d.$apply(function(){l.$setViewValue(e.hasClass(k.activeClass)?i():h()),l.$render()})}),e.on("keypress",function(b){f.disabled||32!==b.which||a[0].activeElement!==e[0]||d.$apply(function(){l.$setViewValue(e.hasClass(k.activeClass)?i():h()),l.$render()})})}}}]),angular.module("ui.bootstrap.carousel",[]).controller("UibCarouselController",["$scope","$element","$interval","$animate",function(a,b,c,d){function e(b,c,e){s||(angular.extend(b,{direction:e,active:!0}),angular.extend(m.currentSlide||{},{direction:e,active:!1}),d.enabled()&&!a.noTransition&&!a.$currentTransition&&b.$element&&m.slides.length>1&&(b.$element.data(q,b.direction),m.currentSlide&&m.currentSlide.$element&&m.currentSlide.$element.data(q,b.direction),a.$currentTransition=!0,o?d.on("addClass",b.$element,function(b,c){"close"===c&&(a.$currentTransition=null,d.off("addClass",b))}):b.$element.one("$animate:close",function(){a.$currentTransition=null})),m.currentSlide=b,r=c,g())}function f(a){if(angular.isUndefined(n[a].index))return n[a];var b;n.length;for(b=0;b0&&(k=c(i,b))}function h(){k&&(c.cancel(k),k=null)}function i(){var b=+a.interval;l&&!isNaN(b)&&b>0&&n.length?a.next():a.pause()}function j(b){b.length||(a.$currentTransition=null)}var k,l,m=this,n=m.slides=a.slides=[],o=angular.version.minor>=4,p="uib-noTransition",q="uib-slideDirection",r=-1;m.currentSlide=null;var s=!1;m.select=a.select=function(b,c){var d=a.indexOfSlide(b);void 0===c&&(c=d>m.getCurrentIndex()?"next":"prev"),b&&b!==m.currentSlide&&!a.$currentTransition&&e(b,d,c)},a.$on("$destroy",function(){s=!0}),m.getCurrentIndex=function(){return m.currentSlide&&angular.isDefined(m.currentSlide.index)?+m.currentSlide.index:r},a.indexOfSlide=function(a){return angular.isDefined(a.index)?+a.index:n.indexOf(a)},a.next=function(){var b=(m.getCurrentIndex()+1)%n.length;return 0===b&&a.noWrap()?void a.pause():m.select(f(b),"next")},a.prev=function(){var b=m.getCurrentIndex()-1<0?n.length-1:m.getCurrentIndex()-1;return a.noWrap()&&b===n.length-1?void a.pause():m.select(f(b),"prev")},a.isActive=function(a){return m.currentSlide===a},a.$watch("interval",g),a.$watchCollection("slides",j),a.$on("$destroy",h),a.play=function(){l||(l=!0,g())},a.pause=function(){a.noPause||(l=!1,h())},m.addSlide=function(b,c){b.$element=c,n.push(b),1===n.length||b.active?(m.select(n[n.length-1]),1===n.length&&a.play()):b.active=!1},m.removeSlide=function(a){angular.isDefined(a.index)&&n.sort(function(a,b){return+a.index>+b.index});var b=n.indexOf(a);n.splice(b,1),n.length>0&&a.active?b>=n.length?m.select(n[b-1]):m.select(n[b]):r>b&&r--,0===n.length&&(m.currentSlide=null)},a.$watch("noTransition",function(a){b.data(p,a)})}]).directive("uibCarousel",[function(){return{transclude:!0,replace:!0,controller:"UibCarouselController",controllerAs:"carousel",require:"carousel",templateUrl:function(a,b){return b.templateUrl||"template/carousel/carousel.html"},scope:{interval:"=",noTransition:"=",noPause:"=",noWrap:"&"}}}]).directive("uibSlide",function(){return{require:"^uibCarousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/carousel/slide.html"},scope:{active:"=?",actual:"=?",index:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}).animation(".item",["$injector","$animate",function(a,b){function c(a,b,c){a.removeClass(b),c&&c()}var d="uib-noTransition",e="uib-slideDirection",f=null;return a.has("$animateCss")&&(f=a.get("$animateCss")),{beforeAddClass:function(a,g,h){if("active"==g&&a.parent()&&a.parent().parent()&&!a.parent().parent().data(d)){var i=!1,j=a.data(e),k="next"==j?"left":"right",l=c.bind(this,a,k+" "+j,h);return a.addClass(j),f?f(a,{addClass:k}).start().done(l):b.addClass(a,k).then(function(){i||l(),h()}),function(){i=!0}}h()},beforeRemoveClass:function(a,g,h){if("active"===g&&a.parent()&&a.parent().parent()&&!a.parent().parent().data(d)){var i=!1,j=a.data(e),k="next"==j?"left":"right",l=c.bind(this,a,k,h);return f?f(a,{addClass:k}).start().done(l):b.addClass(a,k).then(function(){i||l(),h()}),function(){i=!0}}h()}}}]),angular.module("ui.bootstrap.carousel").value("$carouselSuppressWarning",!1).controller("CarouselController",["$scope","$element","$controller","$log","$carouselSuppressWarning",function(a,b,c,d,e){e||d.warn("CarouselController is now deprecated. Use UibCarouselController instead."),angular.extend(this,c("UibCarouselController",{$scope:a,$element:b}))}]).directive("carousel",["$log","$carouselSuppressWarning",function(a,b){return{transclude:!0,replace:!0,controller:"CarouselController",controllerAs:"carousel",require:"carousel",templateUrl:function(a,b){return b.templateUrl||"template/carousel/carousel.html"},scope:{interval:"=",noTransition:"=",noPause:"=",noWrap:"&"},link:function(){b||a.warn("carousel is now deprecated. Use uib-carousel instead.")}}}]).directive("slide",["$log","$carouselSuppressWarning",function(a,b){return{require:"^carousel",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/carousel/slide.html"},scope:{active:"=?",actual:"=?",index:"=?"},link:function(c,d,e,f){b||a.warn("slide is now deprecated. Use uib-slide instead."),f.addSlide(c,d),c.$on("$destroy",function(){f.removeSlide(c)}),c.$watch("active",function(a){a&&f.select(c)})}}}]),angular.module("ui.bootstrap.dateparser",[]).service("uibDateParser",["$log","$locale","orderByFilter",function(a,b,c){function d(a){var b=[],d=a.split("");return angular.forEach(g,function(c,e){var f=a.indexOf(e);if(f>-1){a=a.split(""),d[f]="("+c.regex+")",a[f]="$";for(var g=f+1,h=f+e.length;h>g;g++)d[g]="",a[g]="$";a=a.join(""),b.push({index:f,apply:c.apply})}}),{regex:new RegExp("^"+d.join("")+"$"),map:c(b,"index")}}function e(a,b,c){return 1>c?!1:1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}var f,g,h=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;this.init=function(){f=b.id,this.parsers={},g={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:b.DATETIME_FORMATS.MONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.MONTH.indexOf(a)}},MMM:{regex:b.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.SHORTMONTH.indexOf(a)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:b.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:b.DATETIME_FORMATS.SHORTDAY.join("|")},HH:{regex:"(?:0|1)[0-9]|2[0-3]",apply:function(a){this.hours=+a}},hh:{regex:"0[0-9]|1[0-2]",apply:function(a){this.hours=+a}},H:{regex:"1?[0-9]|2[0-3]",apply:function(a){this.hours=+a}},h:{regex:"[0-9]|1[0-2]",apply:function(a){this.hours=+a}},mm:{regex:"[0-5][0-9]",apply:function(a){this.minutes=+a}},m:{regex:"[0-9]|[1-5][0-9]",apply:function(a){this.minutes=+a}},sss:{regex:"[0-9][0-9][0-9]",apply:function(a){this.milliseconds=+a}},ss:{regex:"[0-5][0-9]",apply:function(a){this.seconds=+a}},s:{regex:"[0-9]|[1-5][0-9]",apply:function(a){this.seconds=+a}},a:{regex:b.DATETIME_FORMATS.AMPMS.join("|"),apply:function(a){12===this.hours&&(this.hours=0),"PM"===a&&(this.hours+=12)}}}},this.init(),this.parse=function(c,g,i){if(!angular.isString(c)||!g)return c;g=b.DATETIME_FORMATS[g]||g,g=g.replace(h,"\\$&"),b.id!==f&&this.init(),this.parsers[g]||(this.parsers[g]=d(g));var j=this.parsers[g],k=j.regex,l=j.map,m=c.match(k);if(m&&m.length){var n,o;angular.isDate(i)&&!isNaN(i.getTime())?n={year:i.getFullYear(),month:i.getMonth(),date:i.getDate(),hours:i.getHours(),minutes:i.getMinutes(),seconds:i.getSeconds(),milliseconds:i.getMilliseconds()}:(i&&a.warn("dateparser:","baseDate is not a valid date"),n={year:1900,month:0,date:1,hours:0,minutes:0,seconds:0,milliseconds:0});for(var p=1,q=m.length;q>p;p++){var r=l[p-1];r.apply&&r.apply.call(n,m[p])}return e(n.year,n.month,n.date)&&(angular.isDate(i)&&!isNaN(i.getTime())?(o=new Date(i),o.setFullYear(n.year,n.month,n.date,n.hours,n.minutes,n.seconds,n.milliseconds||0)):o=new Date(n.year,n.month,n.date,n.hours,n.minutes,n.seconds,n.milliseconds||0)),o}}}]),angular.module("ui.bootstrap.dateparser").value("$dateParserSuppressWarning",!1).service("dateParser",["$log","$dateParserSuppressWarning","uibDateParser",function(a,b,c){b||a.warn("dateParser is now deprecated. Use uibDateParser instead."),angular.extend(this,c)}]),angular.module("ui.bootstrap.position",[]).factory("$uibPosition",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.position").value("$positionSuppressWarning",!1).service("$position",["$log","$positionSuppressWarning","$uibPosition",function(a,b,c){b||a.warn("$position is now deprecated. Use $uibPosition instead."),angular.extend(this,c)}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).value("$datepickerSuppressError",!1).constant("uibDatepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null,shortcutPropagation:!1}).controller("UibDatepickerController",["$scope","$attrs","$parse","$interpolate","$log","dateFilter","uibDatepickerConfig","$datepickerSuppressError",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","showWeeks","startingDay","yearRange","shortcutPropagation"],function(c,e){i[c]=angular.isDefined(b[c])?6>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):g[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=g[d]?new Date(g[d]):null}),angular.forEach(["minMode","maxMode"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(c){i[d]=angular.isDefined(c)?c:b[d],a[d]=i[d],("minMode"==d&&i.modes.indexOf(a.datepickerMode)i.modes.indexOf(i[d]))&&(a.datepickerMode=i[d])}):(i[d]=g[d]||null,a[d]=i[d])}),a.datepickerMode=a.datepickerMode||g.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),angular.isDefined(b.initDate)?(this.activeDate=a.$parent.$eval(b.initDate)||new Date,a.$parent.$watch(b.initDate,function(a){a&&(j.$isEmpty(j.$modelValue)||j.$invalid)&&(i.activeDate=a,i.refreshView())})):this.activeDate=new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$viewValue){var a=new Date(j.$viewValue),b=!isNaN(a);b?this.activeDate=a:h||e.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$viewValue?new Date(j.$viewValue):null;j.$setValidity("dateDisabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$viewValue?new Date(j.$viewValue):null;return{date:a,label:f(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date),customClass:this.customClass(a)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.customClass=function(b){return a.customClass({date:b,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$viewValue?new Date(j.$viewValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){i.element[0].focus()};a.$on("uib:datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),i.shortcutPropagation||b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate)}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):a.toggleMode("up"===c?1:-1)}}]).controller("UibDaypickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?f[b]:29}function e(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}var f=[31,28,31,30,31,30,31,31,30,31,30,31];this.step={months:1},this.element=b,this.init=function(b){angular.extend(b,this),a.showWeeks=b.showWeeks,b.refreshView()},this.getDates=function(a,b){for(var c,d=new Array(b),e=new Date(a),f=0;b>f;)c=new Date(e),d[f++]=c,e.setDate(e.getDate()+1);return d},this._refreshView=function(){var b=this.activeDate.getFullYear(),d=this.activeDate.getMonth(),f=new Date(this.activeDate);f.setFullYear(b,d,1);var g=this.startingDay-f.getDay(),h=g>0?7-g:-g,i=new Date(f);h>0&&i.setDate(-h+1);for(var j=this.getDates(i,42),k=0;42>k;k++)j[k]=angular.extend(this.createDateObject(j[k],this.formatDay),{secondary:j[k].getMonth()!==d,uid:a.uniqueId+"-"+k});a.labels=new Array(7);for(var l=0;7>l;l++)a.labels[l]={abbr:c(j[l].date,this.formatDayHeader),full:c(j[l].date,"EEEE")};if(a.title=c(this.activeDate,this.formatDayTitle),a.rows=this.split(j,7),a.showWeeks){a.weekNumbers=[];for(var m=(11-this.startingDay)%7,n=a.rows.length,o=0;n>o;o++)a.weekNumbers.push(e(a.rows[o][m].date))}},this.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())},this.handleKeyDown=function(a,b){var c=this.activeDate.getDate();if("left"===a)c-=1;else if("up"===a)c-=7;else if("right"===a)c+=1;else if("down"===a)c+=7;else if("pageup"===a||"pagedown"===a){var e=this.activeDate.getMonth()+("pageup"===a?-1:1);this.activeDate.setMonth(e,1),c=Math.min(d(this.activeDate.getFullYear(),this.activeDate.getMonth()),c)}else"home"===a?c=1:"end"===a&&(c=d(this.activeDate.getFullYear(),this.activeDate.getMonth()));this.activeDate.setDate(c)}}]).controller("UibMonthpickerController",["$scope","$element","dateFilter",function(a,b,c){this.step={years:1},this.element=b,this.init=function(a){angular.extend(a,this),a.refreshView()},this._refreshView=function(){for(var b,d=new Array(12),e=this.activeDate.getFullYear(),f=0;12>f;f++)b=new Date(this.activeDate),b.setFullYear(e,f,1),d[f]=angular.extend(this.createDateObject(b,this.formatMonth),{uid:a.uniqueId+"-"+f});a.title=c(this.activeDate,this.formatMonthTitle),a.rows=this.split(d,3)},this.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},this.handleKeyDown=function(a,b){var c=this.activeDate.getMonth();if("left"===a)c-=1;else if("up"===a)c-=3;else if("right"===a)c+=1;else if("down"===a)c+=3;else if("pageup"===a||"pagedown"===a){var d=this.activeDate.getFullYear()+("pageup"===a?-1:1);this.activeDate.setFullYear(d)}else"home"===a?c=0:"end"===a&&(c=11);this.activeDate.setMonth(c)}}]).controller("UibYearpickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a){return parseInt((a-1)/e,10)*e+1}var e;this.element=b,this.yearpickerInit=function(){e=this.yearRange,this.step={years:e}},this._refreshView=function(){for(var b,c=new Array(e),f=0,g=d(this.activeDate.getFullYear());e>f;f++)b=new Date(this.activeDate),b.setFullYear(g+f,0,1),c[f]=angular.extend(this.createDateObject(b,this.formatYear),{uid:a.uniqueId+"-"+f});a.title=[c[0].label,c[e-1].label].join(" - "),a.rows=this.split(c,5)},this.compare=function(a,b){return a.getFullYear()-b.getFullYear()},this.handleKeyDown=function(a,b){var c=this.activeDate.getFullYear();"left"===a?c-=1:"up"===a?c-=5:"right"===a?c+=1:"down"===a?c+=5:"pageup"===a||"pagedown"===a?c+=("pageup"===a?-1:1)*this.step.years:"home"===a?c=d(this.activeDate.getFullYear()):"end"===a&&(c=d(this.activeDate.getFullYear())+e-1),this.activeDate.setFullYear(c)}}]).directive("uibDatepicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/datepicker.html"},scope:{datepickerMode:"=?",dateDisabled:"&",customClass:"&",shortcutPropagation:"&?"},require:["uibDatepicker","^ngModel"],controller:"UibDatepickerController",controllerAs:"datepicker",link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}).directive("uibDaypicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/day.html"},require:["^?uibDatepicker","uibDaypicker","^?datepicker"],controller:"UibDaypickerController",link:function(a,b,c,d){var e=d[0]||d[2],f=d[1];f.init(e)}}}).directive("uibMonthpicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/month.html"},require:["^?uibDatepicker","uibMonthpicker","^?datepicker"],controller:"UibMonthpickerController",link:function(a,b,c,d){var e=d[0]||d[2],f=d[1];f.init(e)}}}).directive("uibYearpicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/year.html"},require:["^?uibDatepicker","uibYearpicker","^?datepicker"],controller:"UibYearpickerController",link:function(a,b,c,d){var e=d[0]||d[2];angular.extend(e,d[1]),e.yearpickerInit(),e.refreshView()}}}).constant("uibDatepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",datepickerPopupTemplateUrl:"template/datepicker/popup.html",datepickerTemplateUrl:"template/datepicker/datepicker.html",html5Types:{date:"yyyy-MM-dd","datetime-local":"yyyy-MM-ddTHH:mm:ss.sss",month:"yyyy-MM"},currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0,onOpenFocus:!0}).controller("UibDatepickerPopupController",["$scope","$element","$attrs","$compile","$parse","$document","$rootScope","$uibPosition","dateFilter","uibDateParser","uibDatepickerPopupConfig","$timeout",function(a,b,c,d,e,f,g,h,i,j,k,l){function m(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function n(b){if(angular.isNumber(b)&&(b=new Date(b)),b){if(angular.isDate(b)&&!isNaN(b))return b;if(angular.isString(b)){var c=j.parse(b,r,a.date);return isNaN(c)?void 0:c}return void 0}return null}function o(a,b){var d=a||b;if(!c.ngRequired&&!d)return!0;if(angular.isNumber(d)&&(d=new Date(d)),d){if(angular.isDate(d)&&!isNaN(d))return!0;if(angular.isString(d)){var e=j.parse(d,r);return!isNaN(e)}return!1}return!0}function p(c){var d=A[0],e=b[0].contains(c.target),f=void 0!==d.contains&&d.contains(c.target);!a.isOpen||e||f||a.$apply(function(){a.isOpen=!1})}function q(c){27===c.which&&a.isOpen?(c.preventDefault(),c.stopPropagation(),a.$apply(function(){a.isOpen=!1}),b[0].focus()):40!==c.which||a.isOpen||(c.preventDefault(),c.stopPropagation(),a.$apply(function(){a.isOpen=!0}))}var r,s,t,u,v,w,x,y,z,A,B={},C=!1;a.watchData={},this.init=function(h){if(z=h,s=angular.isDefined(c.closeOnDateSelection)?a.$parent.$eval(c.closeOnDateSelection):k.closeOnDateSelection, +t=angular.isDefined(c.datepickerAppendToBody)?a.$parent.$eval(c.datepickerAppendToBody):k.appendToBody,u=angular.isDefined(c.onOpenFocus)?a.$parent.$eval(c.onOpenFocus):k.onOpenFocus,v=angular.isDefined(c.datepickerPopupTemplateUrl)?c.datepickerPopupTemplateUrl:k.datepickerPopupTemplateUrl,w=angular.isDefined(c.datepickerTemplateUrl)?c.datepickerTemplateUrl:k.datepickerTemplateUrl,a.showButtonBar=angular.isDefined(c.showButtonBar)?a.$parent.$eval(c.showButtonBar):k.showButtonBar,k.html5Types[c.type]?(r=k.html5Types[c.type],C=!0):(r=c.datepickerPopup||c.uibDatepickerPopup||k.datepickerPopup,c.$observe("uibDatepickerPopup",function(a,b){var c=a||k.datepickerPopup;if(c!==r&&(r=c,z.$modelValue=null,!r))throw new Error("uibDatepickerPopup must have a date format specified.")})),!r)throw new Error("uibDatepickerPopup must have a date format specified.");if(C&&c.datepickerPopup)throw new Error("HTML5 date input types do not support custom formats.");if(x=angular.element("
    "),x.attr({"ng-model":"date","ng-change":"dateSelection(date)","template-url":v}),y=angular.element(x.children()[0]),y.attr("template-url",w),C&&"month"===c.type&&(y.attr("datepicker-mode",'"month"'),y.attr("min-mode","month")),c.datepickerOptions){var l=a.$parent.$eval(c.datepickerOptions);l&&l.initDate&&(a.initDate=l.initDate,y.attr("init-date","initDate"),delete l.initDate),angular.forEach(l,function(a,b){y.attr(m(b),a)})}angular.forEach(["minMode","maxMode","minDate","maxDate","datepickerMode","initDate","shortcutPropagation"],function(b){if(c[b]){var d=e(c[b]);if(a.$parent.$watch(d,function(c){a.watchData[b]=c,("minDate"===b||"maxDate"===b)&&(B[b]=new Date(c))}),y.attr(m(b),"watchData."+b),"datepickerMode"===b){var f=d.assign;a.$watch("watchData."+b,function(b,c){angular.isFunction(f)&&b!==c&&f(a.$parent,b)})}}}),c.dateDisabled&&y.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),c.showWeeks&&y.attr("show-weeks",c.showWeeks),c.customClass&&y.attr("custom-class","customClass({ date: date, mode: mode })"),C?z.$formatters.push(function(b){return a.date=b,b}):(z.$$parserName="date",z.$validators.date=o,z.$parsers.unshift(n),z.$formatters.push(function(b){return a.date=b,z.$isEmpty(b)?b:i(b,r)})),z.$viewChangeListeners.push(function(){a.date=j.parse(z.$viewValue,r,a.date)}),b.bind("keydown",q),A=d(x)(a),x.remove(),t?f.find("body").append(A):b.after(A),a.$on("$destroy",function(){a.isOpen===!0&&(g.$$phase||a.$apply(function(){a.isOpen=!1})),A.remove(),b.unbind("keydown",q),f.unbind("click",p)})},a.getText=function(b){return a[b+"Text"]||k[b+"Text"]},a.isDisabled=function(b){return"today"===b&&(b=new Date),a.watchData.minDate&&a.compare(b,B.minDate)<0||a.watchData.maxDate&&a.compare(b,B.maxDate)>0},a.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())},a.dateSelection=function(c){angular.isDefined(c)&&(a.date=c);var d=a.date?i(a.date,r):null;b.val(d),z.$setViewValue(d),s&&(a.isOpen=!1,b[0].focus())},a.keydown=function(c){27===c.which&&(a.isOpen=!1,b[0].focus())},a.select=function(b){if("today"===b){var c=new Date;angular.isDate(a.date)?(b=new Date(a.date),b.setFullYear(c.getFullYear(),c.getMonth(),c.getDate())):b=new Date(c.setHours(0,0,0,0))}a.dateSelection(b)},a.close=function(){a.isOpen=!1,b[0].focus()},a.$watch("isOpen",function(c){c?(a.position=t?h.offset(b):h.position(b),a.position.top=a.position.top+b.prop("offsetHeight"),l(function(){u&&a.$broadcast("uib:datepicker.focus"),f.bind("click",p)},0,!1)):f.unbind("click",p)})}]).directive("uibDatepickerPopup",function(){return{require:["ngModel","uibDatepickerPopup"],controller:"UibDatepickerPopupController",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&",customClass:"&"},link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibDatepickerPopupWrap",function(){return{replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/popup.html"}}}),angular.module("ui.bootstrap.datepicker").value("$datepickerSuppressWarning",!1).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$log","dateFilter","uibDatepickerConfig","$datepickerSuppressError","$datepickerSuppressWarning",function(a,b,c,d,e,f,g,h,i){i||e.warn("DatepickerController is now deprecated. Use UibDatepickerController instead.");var j=this,k={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","showWeeks","startingDay","yearRange","shortcutPropagation"],function(c,e){j[c]=angular.isDefined(b[c])?6>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):g[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){j[d]=a?new Date(a):null,j.refreshView()}):j[d]=g[d]?new Date(g[d]):null}),angular.forEach(["minMode","maxMode"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(c){j[d]=angular.isDefined(c)?c:b[d],a[d]=j[d],("minMode"==d&&j.modes.indexOf(a.datepickerMode)j.modes.indexOf(j[d]))&&(a.datepickerMode=j[d])}):(j[d]=g[d]||null,a[d]=j[d])}),a.datepickerMode=a.datepickerMode||g.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),angular.isDefined(b.initDate)?(this.activeDate=a.$parent.$eval(b.initDate)||new Date,a.$parent.$watch(b.initDate,function(a){a&&(k.$isEmpty(k.$modelValue)||k.$invalid)&&(j.activeDate=a,j.refreshView())})):this.activeDate=new Date,a.isActive=function(b){return 0===j.compare(b.date,j.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){k=a,k.$render=function(){j.render()}},this.render=function(){if(k.$viewValue){var a=new Date(k.$viewValue),b=!isNaN(a);b?this.activeDate=a:h||e.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=k.$viewValue?new Date(k.$viewValue):null;k.$setValidity("dateDisabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=k.$viewValue?new Date(k.$viewValue):null;return{date:a,label:f(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date),customClass:this.customClass(a)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.customClass=function(b){return a.customClass({date:b,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},this.fixTimeZone=function(a){var b=a.getHours();a.setHours(23===b?b+2:0)},a.select=function(b){if(a.datepickerMode===j.minMode){var c=k.$viewValue?new Date(k.$viewValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),k.$setViewValue(c),k.$render()}else j.activeDate=b,a.datepickerMode=j.modes[j.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=j.activeDate.getFullYear()+a*(j.step.years||0),c=j.activeDate.getMonth()+a*(j.step.months||0);j.activeDate.setFullYear(b,c,1),j.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===j.maxMode&&1===b||a.datepickerMode===j.minMode&&-1===b||(a.datepickerMode=j.modes[j.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var l=function(){j.element[0].focus()};a.$on("uib:datepicker.focus",l),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),j.shortcutPropagation||b.stopPropagation(),"enter"===c||"space"===c){if(j.isDisabled(j.activeDate))return;a.select(j.activeDate)}else!b.ctrlKey||"up"!==c&&"down"!==c?(j.handleKeyDown(c,b),j.refreshView()):a.toggleMode("up"===c?1:-1)}}]).directive("datepicker",["$log","$datepickerSuppressWarning",function(a,b){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/datepicker.html"},scope:{datepickerMode:"=?",dateDisabled:"&",customClass:"&",shortcutPropagation:"&?"},require:["datepicker","^ngModel"],controller:"DatepickerController",controllerAs:"datepicker",link:function(c,d,e,f){b||a.warn("datepicker is now deprecated. Use uib-datepicker instead.");var g=f[0],h=f[1];g.init(h)}}}]).directive("daypicker",["$log","$datepickerSuppressWarning",function(a,b){return{replace:!0,templateUrl:"template/datepicker/day.html",require:["^datepicker","daypicker"],controller:"UibDaypickerController",link:function(c,d,e,f){b||a.warn("daypicker is now deprecated. Use uib-daypicker instead.");var g=f[0],h=f[1];h.init(g)}}}]).directive("monthpicker",["$log","$datepickerSuppressWarning",function(a,b){return{replace:!0,templateUrl:"template/datepicker/month.html",require:["^datepicker","monthpicker"],controller:"UibMonthpickerController",link:function(c,d,e,f){b||a.warn("monthpicker is now deprecated. Use uib-monthpicker instead.");var g=f[0],h=f[1];h.init(g)}}}]).directive("yearpicker",["$log","$datepickerSuppressWarning",function(a,b){return{replace:!0,templateUrl:"template/datepicker/year.html",require:["^datepicker","yearpicker"],controller:"UibYearpickerController",link:function(c,d,e,f){b||a.warn("yearpicker is now deprecated. Use uib-yearpicker instead.");var g=f[0];angular.extend(g,f[1]),g.yearpickerInit(),g.refreshView()}}}]).directive("datepickerPopup",["$log","$datepickerSuppressWarning",function(a,b){return{require:["ngModel","datepickerPopup"],controller:"UibDatepickerPopupController",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&",customClass:"&"},link:function(c,d,e,f){b||a.warn("datepicker-popup is now deprecated. Use uib-datepicker-popup instead.");var g=f[0],h=f[1];h.init(g)}}}]).directive("datepickerPopupWrap",["$log","$datepickerSuppressWarning",function(a,b){return{replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/popup.html"},link:function(){b||a.warn("datepicker-popup-wrap is now deprecated. Use uib-datepicker-popup-wrap instead.")}}}]),angular.module("ui.bootstrap.dropdown",["ui.bootstrap.position"]).constant("uibDropdownConfig",{openClass:"open"}).service("uibDropdownService",["$document","$rootScope",function(a,b){var c=null;this.open=function(b){c||(a.bind("click",d),a.bind("keydown",e)),c&&c!==b&&(c.isOpen=!1),c=b},this.close=function(b){c===b&&(c=null,a.unbind("click",d),a.unbind("keydown",e))};var d=function(a){if(c&&(!a||"disabled"!==c.getAutoClose())){var d=c.getToggleElement();if(!(a&&d&&d[0].contains(a.target))){var e=c.getDropdownElement();a&&"outsideClick"===c.getAutoClose()&&e&&e[0].contains(a.target)||(c.isOpen=!1,b.$$phase||c.$apply())}}},e=function(a){27===a.which?(c.focusToggleElement(),d()):c.isKeynavEnabled()&&/(38|40)/.test(a.which)&&c.isOpen&&(a.preventDefault(),a.stopPropagation(),c.focusDropdownEntry(a.which))}}]).controller("UibDropdownController",["$scope","$element","$attrs","$parse","uibDropdownConfig","uibDropdownService","$animate","$uibPosition","$document","$compile","$templateRequest",function(a,b,c,d,e,f,g,h,i,j,k){var l,m,n=this,o=a.$new(),p=e.openClass,q=angular.noop,r=c.onToggle?d(c.onToggle):angular.noop,s=!1,t=!1;b.addClass("dropdown"),this.init=function(){c.isOpen&&(m=d(c.isOpen),q=m.assign,a.$watch(m,function(a){o.isOpen=!!a})),s=angular.isDefined(c.dropdownAppendToBody),t=angular.isDefined(c.uibKeyboardNav),s&&n.dropdownMenu&&(i.find("body").append(n.dropdownMenu),b.on("$destroy",function(){n.dropdownMenu.remove()}))},this.toggle=function(a){return o.isOpen=arguments.length?!!a:!o.isOpen},this.isOpen=function(){return o.isOpen},o.getToggleElement=function(){return n.toggleElement},o.getAutoClose=function(){return c.autoClose||"always"},o.getElement=function(){return b},o.isKeynavEnabled=function(){return t},o.focusDropdownEntry=function(a){var c=n.dropdownMenu?angular.element(n.dropdownMenu).find("a"):angular.element(b).find("ul").eq(0).find("a");switch(a){case 40:angular.isNumber(n.selectedOption)?n.selectedOption=n.selectedOption===c.length-1?n.selectedOption:n.selectedOption+1:n.selectedOption=0;break;case 38:angular.isNumber(n.selectedOption)?n.selectedOption=0===n.selectedOption?0:n.selectedOption-1:n.selectedOption=c.length-1}c[n.selectedOption].focus()},o.getDropdownElement=function(){return n.dropdownMenu},o.focusToggleElement=function(){n.toggleElement&&n.toggleElement[0].focus()},o.$watch("isOpen",function(c,d){if(s&&n.dropdownMenu){var e=h.positionElements(b,n.dropdownMenu,"bottom-left",!0),i={top:e.top+"px",display:c?"block":"none"},m=n.dropdownMenu.hasClass("dropdown-menu-right");m?(i.left="auto",i.right=window.innerWidth-(e.left+b.prop("offsetWidth"))+"px"):(i.left=e.left+"px",i.right="auto"),n.dropdownMenu.css(i)}if(g[c?"addClass":"removeClass"](b,p).then(function(){angular.isDefined(c)&&c!==d&&r(a,{open:!!c})}),c)n.dropdownMenuTemplateUrl&&k(n.dropdownMenuTemplateUrl).then(function(a){l=o.$new(),j(a.trim())(l,function(a){var b=a;n.dropdownMenu.replaceWith(b),n.dropdownMenu=b})}),o.focusToggleElement(),f.open(o);else{if(n.dropdownMenuTemplateUrl){l&&l.$destroy();var t=angular.element('');n.dropdownMenu.replaceWith(t),n.dropdownMenu=t}f.close(o),n.selectedOption=null}angular.isFunction(q)&&q(a,c)}),a.$on("$locationChangeSuccess",function(){"disabled"!==o.getAutoClose()&&(o.isOpen=!1)});var u=a.$on("$destroy",function(){o.$destroy()});o.$on("$destroy",u)}]).directive("uibDropdown",function(){return{controller:"UibDropdownController",link:function(a,b,c,d){d.init()}}}).directive("uibDropdownMenu",function(){return{restrict:"AC",require:"?^uibDropdown",link:function(a,b,c,d){if(d&&!angular.isDefined(c.dropdownNested)){b.addClass("dropdown-menu");var e=c.templateUrl;e&&(d.dropdownMenuTemplateUrl=e),d.dropdownMenu||(d.dropdownMenu=b)}}}}).directive("uibKeyboardNav",function(){return{restrict:"A",require:"?^uibDropdown",link:function(a,b,c,d){b.bind("keydown",function(a){if(-1!==[38,40].indexOf(a.which)){a.preventDefault(),a.stopPropagation();var b=d.dropdownMenu.find("a");switch(a.which){case 40:angular.isNumber(d.selectedOption)?d.selectedOption=d.selectedOption===b.length-1?d.selectedOption:d.selectedOption+1:d.selectedOption=0;break;case 38:angular.isNumber(d.selectedOption)?d.selectedOption=0===d.selectedOption?0:d.selectedOption-1:d.selectedOption=b.length-1}b[d.selectedOption].focus()}})}}}).directive("uibDropdownToggle",function(){return{require:"?^uibDropdown",link:function(a,b,c,d){if(d){b.addClass("dropdown-toggle"),d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.dropdown").value("$dropdownSuppressWarning",!1).service("dropdownService",["$log","$dropdownSuppressWarning","uibDropdownService",function(a,b,c){b||a.warn("dropdownService is now deprecated. Use uibDropdownService instead."),angular.extend(this,c)}]).controller("DropdownController",["$scope","$element","$attrs","$parse","uibDropdownConfig","uibDropdownService","$animate","$uibPosition","$document","$compile","$templateRequest","$log","$dropdownSuppressWarning",function(a,b,c,d,e,f,g,h,i,j,k,l,m){m||l.warn("DropdownController is now deprecated. Use UibDropdownController instead.");var n,o,p=this,q=a.$new(),r=e.openClass,s=angular.noop,t=c.onToggle?d(c.onToggle):angular.noop,u=!1,v=!1;b.addClass("dropdown"),this.init=function(){c.isOpen&&(o=d(c.isOpen),s=o.assign,a.$watch(o,function(a){q.isOpen=!!a})),u=angular.isDefined(c.dropdownAppendToBody),v=angular.isDefined(c.uibKeyboardNav),u&&p.dropdownMenu&&(i.find("body").append(p.dropdownMenu),b.on("$destroy",function(){p.dropdownMenu.remove()}))},this.toggle=function(a){return q.isOpen=arguments.length?!!a:!q.isOpen},this.isOpen=function(){return q.isOpen},q.getToggleElement=function(){return p.toggleElement},q.getAutoClose=function(){return c.autoClose||"always"},q.getElement=function(){return b},q.isKeynavEnabled=function(){return v},q.focusDropdownEntry=function(a){var c=p.dropdownMenu?angular.element(p.dropdownMenu).find("a"):angular.element(b).find("ul").eq(0).find("a");switch(a){case 40:angular.isNumber(p.selectedOption)?p.selectedOption=p.selectedOption===c.length-1?p.selectedOption:p.selectedOption+1:p.selectedOption=0;break;case 38:angular.isNumber(p.selectedOption)?p.selectedOption=0===p.selectedOption?0:p.selectedOption-1:p.selectedOption=c.length-1}c[p.selectedOption].focus()},q.getDropdownElement=function(){return p.dropdownMenu},q.focusToggleElement=function(){p.toggleElement&&p.toggleElement[0].focus()},q.$watch("isOpen",function(c,d){if(u&&p.dropdownMenu){var e=h.positionElements(b,p.dropdownMenu,"bottom-left",!0),i={top:e.top+"px",display:c?"block":"none"},l=p.dropdownMenu.hasClass("dropdown-menu-right");l?(i.left="auto",i.right=window.innerWidth-(e.left+b.prop("offsetWidth"))+"px"):(i.left=e.left+"px",i.right="auto"),p.dropdownMenu.css(i)}if(g[c?"addClass":"removeClass"](b,r).then(function(){angular.isDefined(c)&&c!==d&&t(a,{open:!!c})}),c)p.dropdownMenuTemplateUrl&&k(p.dropdownMenuTemplateUrl).then(function(a){n=q.$new(),j(a.trim())(n,function(a){var b=a;p.dropdownMenu.replaceWith(b),p.dropdownMenu=b})}),q.focusToggleElement(),f.open(q);else{if(p.dropdownMenuTemplateUrl){n&&n.$destroy();var m=angular.element('');p.dropdownMenu.replaceWith(m),p.dropdownMenu=m}f.close(q),p.selectedOption=null}angular.isFunction(s)&&s(a,c)}),a.$on("$locationChangeSuccess",function(){"disabled"!==q.getAutoClose()&&(q.isOpen=!1)});var w=a.$on("$destroy",function(){q.$destroy()});q.$on("$destroy",w)}]).directive("dropdown",["$log","$dropdownSuppressWarning",function(a,b){return{controller:"DropdownController",link:function(c,d,e,f){b||a.warn("dropdown is now deprecated. Use uib-dropdown instead."),f.init()}}}]).directive("dropdownMenu",["$log","$dropdownSuppressWarning",function(a,b){return{restrict:"AC",require:"?^dropdown",link:function(c,d,e,f){if(f&&!angular.isDefined(e.dropdownNested)){b||a.warn("dropdown-menu is now deprecated. Use uib-dropdown-menu instead."),d.addClass("dropdown-menu");var g=e.templateUrl;g&&(f.dropdownMenuTemplateUrl=g),f.dropdownMenu||(f.dropdownMenu=d)}}}}]).directive("keyboardNav",["$log","$dropdownSuppressWarning",function(a,b){return{restrict:"A",require:"?^dropdown",link:function(c,d,e,f){b||a.warn("keyboard-nav is now deprecated. Use uib-keyboard-nav instead."),d.bind("keydown",function(a){if(-1!==[38,40].indexOf(a.which)){a.preventDefault(),a.stopPropagation();var b=f.dropdownMenu.find("a");switch(a.which){case 40:angular.isNumber(f.selectedOption)?f.selectedOption=f.selectedOption===b.length-1?f.selectedOption:f.selectedOption+1:f.selectedOption=0;break;case 38:angular.isNumber(f.selectedOption)?f.selectedOption=0===f.selectedOption?0:f.selectedOption-1:f.selectedOption=b.length-1}b[f.selectedOption].focus()}})}}}]).directive("dropdownToggle",["$log","$dropdownSuppressWarning",function(a,b){return{require:"?^dropdown",link:function(c,d,e,f){if(b||a.warn("dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead."),f){d.addClass("dropdown-toggle"),f.toggleElement=d;var g=function(a){a.preventDefault(),d.hasClass("disabled")||e.disabled||c.$apply(function(){f.toggle()})};d.bind("click",g),d.attr({"aria-haspopup":!0,"aria-expanded":!1}),c.$watch(f.isOpen,function(a){d.attr("aria-expanded",!!a)}),c.$on("$destroy",function(){d.unbind("click",g)})}}}}]),angular.module("ui.bootstrap.stackedMap",[]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0&&(b=u.top().value,b.modalDomEl.toggleClass(b.windowTopClass||"",a))}function m(){if(q&&-1==j()){var a=r;n(q,r,function(){a=null}),q=void 0,r=void 0}}function n(b,c,d){function e(){e.done||(e.done=!0,p?p(b,{event:"leave"}).start().then(function(){b.remove()}):a.leave(b),c.$destroy(),d&&d())}var g,h=null,i=function(){return g||(g=f.defer(),h=g.promise),function(){g.resolve()}};return c.$broadcast(w.NOW_CLOSING_EVENT,i),f.when(h).then(e)}function o(a,b,c){return!a.value.modalScope.$broadcast("modal.closing",b,c).defaultPrevented}var p=null;g.has("$animateCss")&&(p=g.get("$animateCss"));var q,r,s,t="modal-open",u=i.createNew(),v=h.createNew(),w={NOW_CLOSING_EVENT:"modal.stack.now-closing"},x=0,y="a[href], area[href], input:not([disabled]), button:not([disabled]),select:not([disabled]), textarea:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable=true]";return e.$watch(j,function(a){r&&(r.index=a)}),c.bind("keydown",function(a){if(a.isDefaultPrevented())return a;var b=u.top();if(b&&b.value.keyboard)switch(a.which){case 27:a.preventDefault(),e.$apply(function(){w.dismiss(b.key,"escape key press")});break;case 9:w.loadFocusElementList(b);var c=!1;a.shiftKey?w.isFocusInFirstItem(a)&&(c=w.focusLastFocusableElement()):w.isFocusInLastItem(a)&&(c=w.focusFirstFocusableElement()),c&&(a.preventDefault(),a.stopPropagation())}}),w.open=function(a,b){var f=c[0].activeElement,g=b.openedClass||t;l(!1),u.add(a,{deferred:b.deferred,renderDeferred:b.renderDeferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard,openedClass:b.openedClass,windowTopClass:b.windowTopClass}),v.put(g,a);var h=c.find("body").eq(0),i=j();if(i>=0&&!q){r=e.$new(!0),r.index=i;var k=angular.element('
    ');k.attr("backdrop-class",b.backdropClass),b.animation&&k.attr("modal-animation","true"),q=d(k)(r),h.append(q)}var m=angular.element('
    ');m.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,"window-top-class":b.windowTopClass,size:b.size,index:u.length()-1,animate:"animate"}).html(b.content),b.animation&&m.attr("modal-animation","true");var n=d(m)(b.scope);u.top().value.modalDomEl=n,u.top().value.modalOpener=f,h.append(n),h.addClass(g),w.clearFocusListCache()},w.close=function(a,b){var c=u.get(a);return c&&o(c,b,!0)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.resolve(b),k(a,c.value.modalOpener),!0):!c},w.dismiss=function(a,b){var c=u.get(a);return c&&o(c,b,!1)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.reject(b),k(a,c.value.modalOpener),!0):!c},w.dismissAll=function(a){for(var b=this.getTop();b&&this.dismiss(b.key,a);)b=this.getTop()},w.getTop=function(){return u.top()},w.modalRendered=function(a){var b=u.get(a);b&&b.value.renderDeferred.resolve()},w.focusFirstFocusableElement=function(){return s.length>0?(s[0].focus(),!0):!1},w.focusLastFocusableElement=function(){return s.length>0?(s[s.length-1].focus(),!0):!1},w.isFocusInFirstItem=function(a){return s.length>0?(a.target||a.srcElement)==s[0]:!1},w.isFocusInLastItem=function(a){return s.length>0?(a.target||a.srcElement)==s[s.length-1]:!1},w.clearFocusListCache=function(){s=[],x=0},w.loadFocusElementList=function(a){if((void 0===s||!s.length)&&a){var b=a.value.modalDomEl;b&&b.length&&(s=b[0].querySelectorAll(y))}},w}]).provider("$uibModal",function(){var a={options:{animation:!0,backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$templateRequest","$controller","$uibModalStack","$modalSuppressWarning","$log",function(b,c,d,e,f,g,h,i){function j(a){return a.template?d.when(a.template):e(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl)}function k(a){var c=[];return angular.forEach(a,function(a){angular.isFunction(a)||angular.isArray(a)?c.push(d.when(b.invoke(a))):angular.isString(a)?c.push(d.when(b.get(a))):c.push(d.when(a))}),c}var l={},m=null;return l.getPromiseChain=function(){return m},l.open=function(b){function e(){return r}var l=d.defer(),n=d.defer(),o=d.defer(),p={result:l.promise,opened:n.promise,rendered:o.promise,close:function(a){return g.close(p,a)},dismiss:function(a){return g.dismiss(p,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var q,r=d.all([j(b)].concat(k(b.resolve)));return q=m=d.all([m]).then(e,e).then(function(a){var d=(b.scope||c).$new();d.$close=p.close,d.$dismiss=p.dismiss,d.$on("$destroy",function(){d.$$uibDestructionScheduled||d.$dismiss("$uibUnscheduledDestruction")});var e,j={},k=1;b.controller&&(j.$scope=d,j.$uibModalInstance=p,Object.defineProperty(j,"$modalInstance",{get:function(){return h||i.warn("$modalInstance is now deprecated. Use $uibModalInstance instead."),p}}),angular.forEach(b.resolve,function(b,c){j[c]=a[k++]}),e=f(b.controller,j),b.controllerAs&&(b.bindToController&&angular.extend(e,d),d[b.controllerAs]=e)),g.open(p,{scope:d,deferred:l,renderDeferred:o,content:a[0],animation:b.animation,backdrop:b.backdrop,keyboard:b.keyboard,backdropClass:b.backdropClass,windowTopClass:b.windowTopClass,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size,openedClass:b.openedClass}),n.resolve(!0)},function(a){n.reject(a),l.reject(a)})["finally"](function(){m===q&&(m=null)}),p},l}]};return a}),angular.module("ui.bootstrap.modal").value("$modalSuppressWarning",!1).directive("modalBackdrop",["$animate","$injector","$modalStack","$log","$modalSuppressWarning",function(a,b,c,d,e){function f(b,f,h){e||d.warn("modal-backdrop is now deprecated. Use uib-modal-backdrop instead."),f.addClass("modal-backdrop"),h.modalInClass&&(g?g(f,{addClass:h.modalInClass}).start():a.addClass(f,h.modalInClass),b.$on(c.NOW_CLOSING_EVENT,function(b,c){var d=c();g?g(f,{removeClass:h.modalInClass}).start().then(d):a.removeClass(f,h.modalInClass).then(d)}))}var g=null;return b.has("$animateCss")&&(g=b.get("$animateCss")),{replace:!0,templateUrl:"template/modal/backdrop.html",compile:function(a,b){return a.addClass(b.backdropClass),f}}}]).directive("modalWindow",["$modalStack","$q","$animate","$injector","$log","$modalSuppressWarning",function(a,b,c,d,e,f){var g=null;return d.has("$animateCss")&&(g=d.get("$animateCss")),{scope:{index:"@"},replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"template/modal/window.html"},link:function(d,h,i){f||e.warn("modal-window is now deprecated. Use uib-modal-window instead."),h.addClass(i.windowClass||""),h.addClass(i.windowTopClass||""),d.size=i.size,d.close=function(b){var c=a.getTop();c&&c.value.backdrop&&"static"!==c.value.backdrop&&b.target===b.currentTarget&&(b.preventDefault(),b.stopPropagation(),a.dismiss(c.key,"backdrop click"))},h.on("click",d.close),d.$isRendered=!0;var j=b.defer();i.$observe("modalRender",function(a){"true"==a&&j.resolve()}),j.promise.then(function(){var e=null;i.modalInClass&&(e=g?g(h,{addClass:i.modalInClass}).start():c.addClass(h,i.modalInClass),d.$on(a.NOW_CLOSING_EVENT,function(a,b){var d=b();g?g(h,{removeClass:i.modalInClass}).start().then(d):c.removeClass(h,i.modalInClass).then(d)})),b.when(e).then(function(){var a=h[0].querySelector("[autofocus]");a?a.focus():h[0].focus()});var f=a.getTop();f&&a.modalRendered(f.key)})}}}]).directive("modalAnimationClass",["$log","$modalSuppressWarning",function(a,b){return{compile:function(c,d){b||a.warn("modal-animation-class is now deprecated. Use uib-modal-animation-class instead."),d.modalAnimation&&c.addClass(d.modalAnimationClass)}}}]).directive("modalTransclude",["$log","$modalSuppressWarning",function(a,b){return{link:function(c,d,e,f,g){b||a.warn("modal-transclude is now deprecated. Use uib-modal-transclude instead."),g(c.$parent,function(a){d.empty(),d.append(a)})}}}]).service("$modalStack",["$animate","$timeout","$document","$compile","$rootScope","$q","$injector","$$multiMap","$$stackedMap","$uibModalStack","$log","$modalSuppressWarning",function(a,b,c,d,e,f,g,h,i,j,k,l){l||k.warn("$modalStack is now deprecated. Use $uibModalStack instead."),angular.extend(this,j)}]).provider("$modal",["$uibModalProvider",function(a){angular.extend(this,a),this.$get=["$injector","$log","$modalSuppressWarning",function(b,c,d){return d||c.warn("$modal is now deprecated. Use $uibModal instead."),b.invoke(a.$get)}]}]),angular.module("ui.bootstrap.pagination",[]).controller("UibPaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(g,h){e=g,this.config=h,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10), +a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=h.itemsPerPage,a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b,c){c&&c.preventDefault();var d=!a.ngDisabled||!c;d&&a.page!==b&&b>0&&b<=a.totalPages&&(c&&c.target&&c.target.blur(),e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages}}]).constant("uibPaginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("uibPagination",["$parse","uibPaginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@",ngDisabled:"="},require:["uibPagination","?ngModel"],controller:"UibPaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"template/pagination/pagination.html"},replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render()});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("uibPagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("uibPager",["uibPagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@",ngDisabled:"="},require:["uibPager","?ngModel"],controller:"UibPaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"template/pagination/pager.html"},replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.pagination").value("$paginationSuppressWarning",!1).controller("PaginationController",["$scope","$attrs","$parse","$log","$paginationSuppressWarning",function(a,b,c,d,e){e||d.warn("PaginationController is now deprecated. Use UibPaginationController instead.");var f=this,g={$setViewValue:angular.noop},h=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d,e){g=d,this.config=e,g.$render=function(){f.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){f.itemsPerPage=parseInt(b,10),a.totalPages=f.calculateTotalPages()}):this.itemsPerPage=e.itemsPerPage,a.$watch("totalItems",function(){a.totalPages=f.calculateTotalPages()}),a.$watch("totalPages",function(b){h(a.$parent,b),a.page>b?a.selectPage(b):g.$render()})},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(g.$viewValue,10)||1},a.selectPage=function(b,c){c&&c.preventDefault();var d=!a.ngDisabled||!c;d&&a.page!==b&&b>0&&b<=a.totalPages&&(c&&c.target&&c.target.blur(),g.$setViewValue(b),g.$render())},a.getText=function(b){return a[b+"Text"]||f.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages}}]).directive("pagination",["$parse","uibPaginationConfig","$log","$paginationSuppressWarning",function(a,b,c,d){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@",ngDisabled:"="},require:["pagination","?ngModel"],controller:"PaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"template/pagination/pagination.html"},replace:!0,link:function(e,f,g,h){function i(a,b,c){return{number:a,text:b,active:c}}function j(a,b){var c=[],d=1,e=b,f=angular.isDefined(m)&&b>m;f&&(n?(d=Math.max(a-Math.floor(m/2),1),e=d+m-1,e>b&&(e=b,d=e-m+1)):(d=(Math.ceil(a/m)-1)*m+1,e=Math.min(d+m-1,b)));for(var g=d;e>=g;g++){var h=i(g,g,g===a);c.push(h)}if(f&&!n){if(d>1){var j=i(d-1,"...",!1);c.unshift(j)}if(b>e){var k=i(e+1,"...",!1);c.push(k)}}return c}d||c.warn("pagination is now deprecated. Use uib-pagination instead.");var k=h[0],l=h[1];if(l){var m=angular.isDefined(g.maxSize)?e.$parent.$eval(g.maxSize):b.maxSize,n=angular.isDefined(g.rotate)?e.$parent.$eval(g.rotate):b.rotate;e.boundaryLinks=angular.isDefined(g.boundaryLinks)?e.$parent.$eval(g.boundaryLinks):b.boundaryLinks,e.directionLinks=angular.isDefined(g.directionLinks)?e.$parent.$eval(g.directionLinks):b.directionLinks,k.init(l,b),g.maxSize&&e.$parent.$watch(a(g.maxSize),function(a){m=parseInt(a,10),k.render()});var o=k.render;k.render=function(){o(),e.page>0&&e.page<=e.totalPages&&(e.pages=j(e.page,e.totalPages))}}}}}]).directive("pager",["uibPagerConfig","$log","$paginationSuppressWarning",function(a,b,c){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@",ngDisabled:"="},require:["pager","?ngModel"],controller:"PaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"template/pagination/pager.html"},replace:!0,link:function(d,e,f,g){c||b.warn("pager is now deprecated. Use uib-pager instead.");var h=g[0],i=g[1];i&&(d.align=angular.isDefined(f.align)?d.$parent.$eval(f.align):a.align,h.init(i,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.stackedMap"]).provider("$uibTooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0,popupCloseDelay:0,useContentExp:!1},c={mouseenter:"mouseleave",click:"click",focus:"blur",none:""},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$document","$uibPosition","$interpolate","$rootScope","$parse","$$stackedMap",function(e,f,g,h,i,j,k,l,m){var n=m.createNew();return h.on("keypress",function(a){if(27===a.which){var b=n.top();b&&(b.value.close(),n.removeTop(),b=null)}}),function(e,k,m,o){function p(a){var b=(a||o.trigger||m).split(" "),d=b.map(function(a){return c[a]||a});return{show:b,hide:d}}o=angular.extend({},b,d,o);var q=a(e),r=j.startSymbol(),s=j.endSymbol(),t="
    ';return{compile:function(a,b){var c=f(t);return function(a,b,d,f){function j(){L.isOpen?q():m()}function m(){(!K||a.$eval(d[k+"Enable"]))&&(u(),x(),L.popupDelay?F||(F=g(r,L.popupDelay,!1)):r())}function q(){s(),L.popupCloseDelay?G||(G=g(t,L.popupCloseDelay,!1)):t()}function r(){return s(),u(),L.content?(v(),void L.$evalAsync(function(){L.isOpen=!0,y(!0),Q()})):angular.noop}function s(){F&&(g.cancel(F),F=null),H&&(g.cancel(H),H=null)}function t(){s(),u(),L&&L.$evalAsync(function(){L.isOpen=!1,y(!1),L.animation?E||(E=g(w,150,!1)):w()})}function u(){G&&(g.cancel(G),G=null),E&&(g.cancel(E),E=null)}function v(){C||(D=L.$new(),C=c(D,function(a){I?h.find("body").append(a):b.after(a)}),z())}function w(){A(),E=null,C&&(C.remove(),C=null),D&&(D.$destroy(),D=null)}function x(){L.title=d[k+"Title"],O?L.content=O(a):L.content=d[e],L.popupClass=d[k+"Class"],L.placement=angular.isDefined(d[k+"Placement"])?d[k+"Placement"]:o.placement;var b=parseInt(d[k+"PopupDelay"],10),c=parseInt(d[k+"PopupCloseDelay"],10);L.popupDelay=isNaN(b)?o.popupDelay:b,L.popupCloseDelay=isNaN(c)?o.popupCloseDelay:c}function y(b){N&&angular.isFunction(N.assign)&&N.assign(a,b)}function z(){P.length=0,O?(P.push(a.$watch(O,function(a){L.content=a,!a&&L.isOpen&&t()})),P.push(D.$watch(function(){M||(M=!0,D.$$postDigest(function(){M=!1,L&&L.isOpen&&Q()}))}))):P.push(d.$observe(e,function(a){L.content=a,!a&&L.isOpen?t():Q()})),P.push(d.$observe(k+"Title",function(a){L.title=a,L.isOpen&&Q()})),P.push(d.$observe(k+"Placement",function(a){L.placement=a?a:o.placement,L.isOpen&&Q()}))}function A(){P.length&&(angular.forEach(P,function(a){a()}),P.length=0)}function B(){var a=d[k+"Trigger"];R(),J=p(a),"none"!==J.show&&J.show.forEach(function(a,c){a===J.hide[c]?b[0].addEventListener(a,j):a&&(b[0].addEventListener(a,m),J.hide[c].split(" ").forEach(function(a){b[0].addEventListener(a,q)})),b.on("keypress",function(a){27===a.which&&q()})})}var C,D,E,F,G,H,I=angular.isDefined(o.appendToBody)?o.appendToBody:!1,J=p(void 0),K=angular.isDefined(d[k+"Enable"]),L=a.$new(!0),M=!1,N=angular.isDefined(d[k+"IsOpen"])?l(d[k+"IsOpen"]):!1,O=o.useContentExp?l(d[e]):!1,P=[],Q=function(){C&&C.html()&&(H||(H=g(function(){C.css({top:0,left:0});var a=i.positionElements(b,C,L.placement,I);a.top+="px",a.left+="px",a.visibility="visible",C.css(a),H=null},0,!1)))};L.origScope=a,L.isOpen=!1,n.add(L,{close:t}),L.contentExp=function(){return L.content},d.$observe("disabled",function(a){a&&s(),a&&L.isOpen&&t()}),N&&a.$watch(N,function(a){L&&!a===L.isOpen&&j()});var R=function(){J.show.forEach(function(a){b.unbind(a,m)}),J.hide.forEach(function(a){a.split(" ").forEach(function(a){b[0].removeEventListener(a,q)})})};B();var S=a.$eval(d[k+"Animation"]);L.animation=angular.isDefined(S)?!!S:o.animation;var T=a.$eval(d[k+"AppendToBody"]);I=angular.isDefined(T)?T:I,I&&a.$on("$locationChangeSuccess",function(){L.isOpen&&t()}),a.$on("$destroy",function(){s(),u(),R(),w(),n.remove(L),L=null})}}}}}]}).directive("uibTooltipTemplateTransclude",["$animate","$sce","$compile","$templateRequest",function(a,b,c,d){return{link:function(e,f,g){var h,i,j,k=e.$eval(g.tooltipTemplateTranscludeScope),l=0,m=function(){i&&(i.remove(),i=null),h&&(h.$destroy(),h=null),j&&(a.leave(j).then(function(){i=null}),i=j,j=null)};e.$watch(b.parseAsResourceUrl(g.uibTooltipTemplateTransclude),function(b){var g=++l;b?(d(b,!0).then(function(d){if(g===l){var e=k.$new(),i=d,n=c(i)(e,function(b){m(),a.enter(b,f)});h=e,j=n,h.$emit("$includeContentLoaded",b)}},function(){g===l&&(m(),e.$emit("$includeContentError",b))}),e.$emit("$includeContentRequested",b)):m()}),e.$on("$destroy",m)}}}]).directive("uibTooltipClasses",function(){return{restrict:"A",link:function(a,b,c){a.placement&&b.addClass(a.placement),a.popupClass&&b.addClass(a.popupClass),a.animation()&&b.addClass(c.tooltipAnimationClass)}}}).directive("uibTooltipPopup",function(){return{replace:!0,scope:{content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html",link:function(a,b){b.addClass("tooltip")}}}).directive("uibTooltip",["$uibTooltip",function(a){return a("uibTooltip","tooltip","mouseenter")}]).directive("uibTooltipTemplatePopup",function(){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"template/tooltip/tooltip-template-popup.html",link:function(a,b){b.addClass("tooltip")}}}).directive("uibTooltipTemplate",["$uibTooltip",function(a){return a("uibTooltipTemplate","tooltip","mouseenter",{useContentExp:!0})}]).directive("uibTooltipHtmlPopup",function(){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-popup.html",link:function(a,b){b.addClass("tooltip")}}}).directive("uibTooltipHtml",["$uibTooltip",function(a){return a("uibTooltipHtml","tooltip","mouseenter",{useContentExp:!0})}]),angular.module("ui.bootstrap.tooltip").value("$tooltipSuppressWarning",!1).provider("$tooltip",["$uibTooltipProvider",function(a){angular.extend(this,a),this.$get=["$log","$tooltipSuppressWarning","$injector",function(b,c,d){return c||b.warn("$tooltip is now deprecated. Use $uibTooltip instead."),d.invoke(a.$get)}]}]).directive("tooltipTemplateTransclude",["$animate","$sce","$compile","$templateRequest","$log","$tooltipSuppressWarning",function(a,b,c,d,e,f){return{link:function(g,h,i){f||e.warn("tooltip-template-transclude is now deprecated. Use uib-tooltip-template-transclude instead.");var j,k,l,m=g.$eval(i.tooltipTemplateTranscludeScope),n=0,o=function(){k&&(k.remove(),k=null),j&&(j.$destroy(),j=null),l&&(a.leave(l).then(function(){k=null}),k=l,l=null)};g.$watch(b.parseAsResourceUrl(i.tooltipTemplateTransclude),function(b){var e=++n;b?(d(b,!0).then(function(d){if(e===n){var f=m.$new(),g=d,i=c(g)(f,function(b){o(),a.enter(b,h)});j=f,l=i,j.$emit("$includeContentLoaded",b)}},function(){e===n&&(o(),g.$emit("$includeContentError",b))}),g.$emit("$includeContentRequested",b)):o()}),g.$on("$destroy",o)}}}]).directive("tooltipClasses",["$log","$tooltipSuppressWarning",function(a,b){return{restrict:"A",link:function(c,d,e){b||a.warn("tooltip-classes is now deprecated. Use uib-tooltip-classes instead."),c.placement&&d.addClass(c.placement),c.popupClass&&d.addClass(c.popupClass),c.animation()&&d.addClass(e.tooltipAnimationClass)}}}]).directive("tooltipPopup",["$log","$tooltipSuppressWarning",function(a,b){return{replace:!0,scope:{content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html",link:function(c,d){b||a.warn("tooltip-popup is now deprecated. Use uib-tooltip-popup instead."),d.addClass("tooltip")}}}]).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipTemplatePopup",["$log","$tooltipSuppressWarning",function(a,b){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"template/tooltip/tooltip-template-popup.html",link:function(c,d){b||a.warn("tooltip-template-popup is now deprecated. Use uib-tooltip-template-popup instead."),d.addClass("tooltip")}}}]).directive("tooltipTemplate",["$tooltip",function(a){return a("tooltipTemplate","tooltip","mouseenter",{useContentExp:!0})}]).directive("tooltipHtmlPopup",["$log","$tooltipSuppressWarning",function(a,b){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-popup.html",link:function(c,d){b||a.warn("tooltip-html-popup is now deprecated. Use uib-tooltip-html-popup instead."),d.addClass("tooltip")}}}]).directive("tooltipHtml",["$tooltip",function(a){return a("tooltipHtml","tooltip","mouseenter",{useContentExp:!0})}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("uibPopoverTemplatePopup",function(){return{replace:!0,scope:{title:"@",contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"template/popover/popover-template.html",link:function(a,b){b.addClass("popover")}}}).directive("uibPopoverTemplate",["$uibTooltip",function(a){return a("uibPopoverTemplate","popover","click",{useContentExp:!0})}]).directive("uibPopoverHtmlPopup",function(){return{replace:!0,scope:{contentExp:"&",title:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover-html.html",link:function(a,b){b.addClass("popover")}}}).directive("uibPopoverHtml",["$uibTooltip",function(a){return a("uibPopoverHtml","popover","click",{useContentExp:!0})}]).directive("uibPopoverPopup",function(){return{replace:!0,scope:{title:"@",content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html",link:function(a,b){b.addClass("popover")}}}).directive("uibPopover",["$uibTooltip",function(a){return a("uibPopover","popover","click")}]),angular.module("ui.bootstrap.popover").value("$popoverSuppressWarning",!1).directive("popoverTemplatePopup",["$log","$popoverSuppressWarning",function(a,b){return{replace:!0,scope:{title:"@",contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"template/popover/popover-template.html",link:function(c,d){b||a.warn("popover-template-popup is now deprecated. Use uib-popover-template-popup instead."),d.addClass("popover")}}}]).directive("popoverTemplate",["$tooltip",function(a){return a("popoverTemplate","popover","click",{useContentExp:!0})}]).directive("popoverHtmlPopup",["$log","$popoverSuppressWarning",function(a,b){return{replace:!0,scope:{contentExp:"&",title:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover-html.html",link:function(c,d){b||a.warn("popover-html-popup is now deprecated. Use uib-popover-html-popup instead."),d.addClass("popover")}}}]).directive("popoverHtml",["$tooltip",function(a){return a("popoverHtml","popover","click",{useContentExp:!0})}]).directive("popoverPopup",["$log","$popoverSuppressWarning",function(a,b){return{replace:!0,scope:{title:"@",content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html",link:function(c,d){b||a.warn("popover-popup is now deprecated. Use uib-popover-popup instead."),d.addClass("popover")}}}]).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("uibProgressConfig",{animate:!0,max:100}).controller("UibProgressController",["$scope","$attrs","uibProgressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(a.max)?a.max:c.max,this.addBar=function(b,c,f){e||c.css({transition:"none"}),this.bars.push(b),b.max=a.max,b.title=f&&angular.isDefined(f.title)?f.title:"progressbar",b.$watch("value",function(a){b.recalculatePercentage()}),b.recalculatePercentage=function(){var a=d.bars.reduce(function(a,b){return b.percent=+(100*b.value/b.max).toFixed(2),a+b.percent},0);a>100&&(b.percent-=a-100)},b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1),this.bars.forEach(function(a){a.recalculatePercentage()})},a.$watch("max",function(b){d.bars.forEach(function(b){b.max=a.max,b.recalculatePercentage()})})}]).directive("uibProgress",function(){return{replace:!0,transclude:!0,controller:"UibProgressController",require:"uibProgress",scope:{max:"=?"},templateUrl:"template/progressbar/progress.html"}}).directive("uibBar",function(){return{replace:!0,transclude:!0,require:"^uibProgress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b,c)}}}).directive("uibProgressbar",function(){return{replace:!0,transclude:!0,controller:"UibProgressController",scope:{value:"=",max:"=?",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]),{title:c.title})}}}),angular.module("ui.bootstrap.progressbar").value("$progressSuppressWarning",!1).controller("ProgressController",["$scope","$attrs","uibProgressConfig","$log","$progressSuppressWarning",function(a,b,c,d,e){e||d.warn("ProgressController is now deprecated. Use UibProgressController instead.");var f=this,g=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(a.max)?a.max:c.max,this.addBar=function(b,c,d){g||c.css({transition:"none"}),this.bars.push(b),b.max=a.max,b.title=d&&angular.isDefined(d.title)?d.title:"progressbar",b.$watch("value",function(a){b.recalculatePercentage()}),b.recalculatePercentage=function(){b.percent=+(100*b.value/b.max).toFixed(2);var a=f.bars.reduce(function(a,b){return a+b.percent},0);a>100&&(b.percent-=a-100)},b.$on("$destroy",function(){c=null,f.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)},a.$watch("max",function(b){f.bars.forEach(function(b){b.max=a.max,b.recalculatePercentage()})})}]).directive("progress",["$log","$progressSuppressWarning",function(a,b){return{replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{max:"=?",title:"@?"},templateUrl:"template/progressbar/progress.html",link:function(){b||a.warn("progress is now deprecated. Use uib-progress instead.")}}}]).directive("bar",["$log","$progressSuppressWarning",function(a,b){return{replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(c,d,e,f){b||a.warn("bar is now deprecated. Use uib-bar instead."),f.addBar(c,d)}}}]).directive("progressbar",["$log","$progressSuppressWarning",function(a,b){return{replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",max:"=?",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(c,d,e,f){b||a.warn("progressbar is now deprecated. Use uib-progressbar instead."),f.addBar(c,angular.element(d.children()[0]),{title:e.title})}}}]),angular.module("ui.bootstrap.rating",[]).constant("uibRatingConfig",{max:5,stateOn:null,stateOff:null,titles:["one","two","three","four","five"]}).controller("UibRatingController",["$scope","$attrs","uibRatingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,d.$formatters.push(function(a){return angular.isNumber(a)&&a<<0!==a&&(a=Math.round(a)),a}),this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.titles)?a.$parent.$eval(b.titles):c.titles;this.titles=angular.isArray(f)&&f.length>0?f:c.titles;var g=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(g)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff,title:this.getTitle(b)},a[b]);return a},this.getTitle=function(a){return a>=this.titles.length?a+1:this.titles[a]},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(d.$viewValue===b?0:b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("uibRating",function(){return{require:["uibRating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"UibRatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}),angular.module("ui.bootstrap.rating").value("$ratingSuppressWarning",!1).controller("RatingController",["$scope","$attrs","$controller","$log","$ratingSuppressWarning",function(a,b,c,d,e){e||d.warn("RatingController is now deprecated. Use UibRatingController instead."),angular.extend(this,c("UibRatingController",{$scope:a,$attrs:b}))}]).directive("rating",["$log","$ratingSuppressWarning",function(a,b){return{require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(c,d,e,f){b||a.warn("rating is now deprecated. Use uib-rating instead.");var g=f[0],h=f[1];g.init(h)}}}]),angular.module("ui.bootstrap.tabs",[]).controller("UibTabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect(),a.selectCalled=!1)}),a.active=!0,a.selectCalled||(a.onSelect(),a.selectCalled=!0)},b.addTab=function(a){c.push(a),1===c.length&&a.active!==!1?a.active=!0:a.active?b.select(a):a.active=!1},b.removeTab=function(a){var e=c.indexOf(a);if(a.active&&c.length>1&&!d){var f=e==c.length-1?e-1:e+1;b.select(c[f])}c.splice(e,1)};var d;a.$on("$destroy",function(){d=!0})}]).directive("uibTabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"UibTabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("uibTab",["$parse",function(a){return{require:"^uibTabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},link:function(b,c,d,e,f){b.$watch("active",function(a){a&&e.select(b)}),b.disabled=!1,d.disable&&b.$parent.$watch(a(d.disable),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},e.addTab(b),b.$on("$destroy",function(){e.removeTab(b)}),b.$transcludeFn=f}}}]).directive("uibTabHeadingTransclude",function(){return{restrict:"A",require:["?^uibTab","?^tab"],link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}).directive("uibTabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||a.hasAttribute("x-tab-heading")||a.hasAttribute("uib-tab-heading")||a.hasAttribute("data-uib-tab-heading")||a.hasAttribute("x-uib-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase()||"x-tab-heading"===a.tagName.toLowerCase()||"uib-tab-heading"===a.tagName.toLowerCase()||"data-uib-tab-heading"===a.tagName.toLowerCase()||"x-uib-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:["?^uibTabset","?^tabset"],link:function(b,c,d){var e=b.$eval(d.uibTabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.tabs").value("$tabsSuppressWarning",!1).controller("TabsetController",["$scope","$controller","$log","$tabsSuppressWarning",function(a,b,c,d){d||c.warn("TabsetController is now deprecated. Use UibTabsetController instead."),angular.extend(this,b("UibTabsetController",{$scope:a}))}]).directive("tabset",["$log","$tabsSuppressWarning",function(a,b){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(c,d,e){b||a.warn("tabset is now deprecated. Use uib-tabset instead."),c.vertical=angular.isDefined(e.vertical)?c.$parent.$eval(e.vertical):!1,c.justified=angular.isDefined(e.justified)?c.$parent.$eval(e.justified):!1}}}]).directive("tab",["$parse","$log","$tabsSuppressWarning",function(a,b,c){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},link:function(d,e,f,g,h){c||b.warn("tab is now deprecated. Use uib-tab instead."),d.$watch("active",function(a){a&&g.select(d)}),d.disabled=!1,f.disable&&d.$parent.$watch(a(f.disable),function(a){d.disabled=!!a}),d.select=function(){d.disabled||(d.active=!0)},g.addTab(d),d.$on("$destroy",function(){g.removeTab(d)}),d.$transcludeFn=h}}}]).directive("tabHeadingTransclude",["$log","$tabsSuppressWarning",function(a,b){return{restrict:"A",require:"^tab",link:function(c,d){b||a.warn("tab-heading-transclude is now deprecated. Use uib-tab-heading-transclude instead."),c.$watch("headingElement",function(a){a&&(d.html(""),d.append(a))})}}}]).directive("tabContentTransclude",["$log","$tabsSuppressWarning",function(a,b){function c(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||a.hasAttribute("x-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase()||"x-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(d,e,f){b||a.warn("tab-content-transclude is now deprecated. Use uib-tab-content-transclude instead.");var g=d.$eval(f.tabContentTransclude);g.$transcludeFn(g.$parent,function(a){angular.forEach(a,function(a){c(a)?g.headingElement=a:e.append(a)})})}}}]),angular.module("ui.bootstrap.timepicker",[]).constant("uibTimepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0,arrowkeys:!0,showSpinners:!0}).controller("UibTimepickerController",["$scope","$element","$attrs","$parse","$log","$locale","uibTimepickerConfig",function(a,b,c,d,e,f,g){function h(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===r[1]&&(b+=12)),b):void 0}function i(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function j(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a.toString()}function k(a){l(),q.$setViewValue(new Date(p)),m(a)}function l(){q.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function m(b){var c=p.getHours(),d=p.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:j(c),"m"!==b&&(a.minutes=j(d)),a.meridian=p.getHours()<12?r[0]:r[1]}function n(a,b){var c=new Date(a.getTime()+6e4*b),d=new Date(a);return d.setHours(c.getHours(),c.getMinutes()),d}function o(a){p=n(p,a),k()}var p=new Date,q={$setViewValue:angular.noop},r=angular.isDefined(c.meridians)?a.$parent.$eval(c.meridians):g.meridians||f.DATETIME_FORMATS.AMPMS;a.tabindex=angular.isDefined(c.tabindex)?c.tabindex:0,b.removeAttr("tabindex"),this.init=function(b,d){q=b,q.$render=this.render,q.$formatters.unshift(function(a){return a?new Date(a):null});var e=d.eq(0),f=d.eq(1),h=angular.isDefined(c.mousewheel)?a.$parent.$eval(c.mousewheel):g.mousewheel;h&&this.setupMousewheelEvents(e,f);var i=angular.isDefined(c.arrowkeys)?a.$parent.$eval(c.arrowkeys):g.arrowkeys;i&&this.setupArrowkeyEvents(e,f),a.readonlyInput=angular.isDefined(c.readonlyInput)?a.$parent.$eval(c.readonlyInput):g.readonlyInput,this.setupInputEvents(e,f)};var s=g.hourStep;c.hourStep&&a.$parent.$watch(d(c.hourStep),function(a){s=parseInt(a,10)});var t=g.minuteStep;c.minuteStep&&a.$parent.$watch(d(c.minuteStep),function(a){t=parseInt(a,10)});var u;a.$parent.$watch(d(c.min),function(a){var b=new Date(a);u=isNaN(b)?void 0:b});var v;a.$parent.$watch(d(c.max),function(a){var b=new Date(a);v=isNaN(b)?void 0:b}),a.noIncrementHours=function(){var a=n(p,60*s);return a>v||p>a&&u>a},a.noDecrementHours=function(){var a=n(p,60*-s);return u>a||a>p&&a>v},a.noIncrementMinutes=function(){var a=n(p,t);return a>v||p>a&&u>a},a.noDecrementMinutes=function(){var a=n(p,-t);return u>a||a>p&&a>v},a.noToggleMeridian=function(){return p.getHours()<13?n(p,720)>v:n(p,-720)0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupArrowkeyEvents=function(b,c){b.bind("keydown",function(b){38===b.which?(b.preventDefault(),a.incrementHours(),a.$apply()):40===b.which&&(b.preventDefault(), +a.decrementHours(),a.$apply())}),c.bind("keydown",function(b){38===b.which?(b.preventDefault(),a.incrementMinutes(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementMinutes(),a.$apply())})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){q.$setViewValue(null),q.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=h(),b=i();angular.isDefined(a)&&angular.isDefined(b)?(p.setHours(a),u>p||p>v?d(!0):k("h")):d(!0)},b.bind("blur",function(b){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=j(a.hours)})}),a.updateMinutes=function(){var a=i(),b=h();angular.isDefined(a)&&angular.isDefined(b)?(p.setMinutes(a),u>p||p>v?d(void 0,!0):k("m")):d(void 0,!0)},c.bind("blur",function(b){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=j(a.minutes)})})},this.render=function(){var b=q.$viewValue;isNaN(b)?(q.$setValidity("time",!1),e.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(b&&(p=b),u>p||p>v?(q.$setValidity("time",!1),a.invalidHours=!0,a.invalidMinutes=!0):l(),m())},a.showSpinners=angular.isDefined(c.showSpinners)?a.$parent.$eval(c.showSpinners):g.showSpinners,a.incrementHours=function(){a.noIncrementHours()||o(60*s)},a.decrementHours=function(){a.noDecrementHours()||o(60*-s)},a.incrementMinutes=function(){a.noIncrementMinutes()||o(t)},a.decrementMinutes=function(){a.noDecrementMinutes()||o(-t)},a.toggleMeridian=function(){a.noToggleMeridian()||o(720*(p.getHours()<12?1:-1))}}]).directive("uibTimepicker",function(){return{restrict:"EA",require:["uibTimepicker","?^ngModel"],controller:"UibTimepickerController",controllerAs:"timepicker",replace:!0,scope:{},templateUrl:function(a,b){return b.templateUrl||"template/timepicker/timepicker.html"},link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.timepicker").value("$timepickerSuppressWarning",!1).controller("TimepickerController",["$scope","$element","$attrs","$controller","$log","$timepickerSuppressWarning",function(a,b,c,d,e,f){f||e.warn("TimepickerController is now deprecated. Use UibTimepickerController instead."),angular.extend(this,d("UibTimepickerController",{$scope:a,$element:b,$attrs:c}))}]).directive("timepicker",["$log","$timepickerSuppressWarning",function(a,b){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",controllerAs:"timepicker",replace:!0,scope:{},templateUrl:function(a,b){return b.templateUrl||"template/timepicker/timepicker.html"},link:function(c,d,e,f){b||a.warn("timepicker is now deprecated. Use uib-timepicker instead.");var g=f[0],h=f[1];h&&g.init(h,d.find("input"))}}}]),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position"]).factory("uibTypeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).controller("UibTypeaheadController",["$scope","$element","$attrs","$compile","$parse","$q","$timeout","$document","$window","$rootScope","$uibPosition","uibTypeaheadParser",function(a,b,c,d,e,f,g,h,i,j,k,l){function m(){K.moveInProgress||(K.moveInProgress=!0,K.$digest()),S&&g.cancel(S),S=g(function(){K.matches.length&&n(),K.moveInProgress=!1},r)}function n(){K.position=C?k.offset(b):k.position(b),K.position.top+=b.prop("offsetHeight")}var o,p,q=[9,13,27,38,40],r=200,s=a.$eval(c.typeaheadMinLength);s||0===s||(s=1);var t,u,v=a.$eval(c.typeaheadWaitMs)||0,w=a.$eval(c.typeaheadEditable)!==!1,x=e(c.typeaheadLoading).assign||angular.noop,y=e(c.typeaheadOnSelect),z=angular.isDefined(c.typeaheadSelectOnBlur)?a.$eval(c.typeaheadSelectOnBlur):!1,A=e(c.typeaheadNoResults).assign||angular.noop,B=c.typeaheadInputFormatter?e(c.typeaheadInputFormatter):void 0,C=c.typeaheadAppendToBody?a.$eval(c.typeaheadAppendToBody):!1,D=c.typeaheadAppendToElementId||!1,E=a.$eval(c.typeaheadFocusFirst)!==!1,F=c.typeaheadSelectOnExact?a.$eval(c.typeaheadSelectOnExact):!1,G=e(c.ngModel),H=e(c.ngModel+"($$$p)"),I=function(b,c){return angular.isFunction(G(a))&&p&&p.$options&&p.$options.getterSetter?H(b,{$$$p:c}):G.assign(b,c)},J=l.parse(c.uibTypeahead),K=a.$new(),L=a.$on("$destroy",function(){K.$destroy()});K.$on("$destroy",L);var M="typeahead-"+K.$id+"-"+Math.floor(1e4*Math.random());b.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":M});var N=angular.element("
    ");N.attr({id:M,matches:"matches",active:"activeIdx",select:"select(activeIdx)","move-in-progress":"moveInProgress",query:"query",position:"position"}),angular.isDefined(c.typeaheadTemplateUrl)&&N.attr("template-url",c.typeaheadTemplateUrl),angular.isDefined(c.typeaheadPopupTemplateUrl)&&N.attr("popup-template-url",c.typeaheadPopupTemplateUrl);var O=function(){K.matches=[],K.activeIdx=-1,b.attr("aria-expanded",!1)},P=function(a){return M+"-option-"+a};K.$watch("activeIdx",function(a){0>a?b.removeAttr("aria-activedescendant"):b.attr("aria-activedescendant",P(a))});var Q=function(a,b){return K.matches.length>b&&a?a.toUpperCase()===K.matches[b].label.toUpperCase():!1},R=function(c){var d={$viewValue:c};x(a,!0),A(a,!1),f.when(J.source(a,d)).then(function(e){var f=c===o.$viewValue;if(f&&t)if(e&&e.length>0){K.activeIdx=E?0:-1,A(a,!1),K.matches.length=0;for(var g=0;g0?K.activeIdx:K.matches.length)-1,K.$digest()):13===a.which||9===a.which?K.$apply(function(){K.select(K.activeIdx)}):27===a.which&&(a.stopPropagation(),O(),K.$digest())}}),b.bind("blur",function(){z&&K.matches.length&&-1!==K.activeIdx&&!u&&(u=!0,K.$apply(function(){K.select(K.activeIdx)})),t=!1,u=!1});var W=function(a){b[0]!==a.target&&3!==a.which&&0!==K.matches.length&&(O(),j.$$phase||K.$digest())};h.bind("click",W),a.$on("$destroy",function(){h.unbind("click",W),(C||D)&&X.remove(),C&&(angular.element(i).unbind("resize",m),h.find("body").unbind("scroll",m)),N.remove()});var X=d(N)(K);C?h.find("body").append(X):D!==!1?angular.element(h[0].getElementById(D)).append(X):b.after(X),this.init=function(b,c){o=b,p=c,o.$parsers.unshift(function(b){return t=!0,0===s||b&&b.length>=s?v>0?(V(),U(b)):R(b):(x(a,!1),V(),O()),w?b:b?void o.$setValidity("editable",!1):(o.$setValidity("editable",!0),null)}),o.$formatters.push(function(b){var c,d,e={};return w||o.$setValidity("editable",!0),B?(e.$model=b,B(a,e)):(e[J.itemName]=b,c=J.viewMapper(a,e),e[J.itemName]=void 0,d=J.viewMapper(a,e),c!==d?c:b)})}}]).directive("uibTypeahead",function(){return{controller:"UibTypeaheadController",require:["ngModel","^?ngModelOptions","uibTypeahead"],link:function(a,b,c,d){d[2].init(d[0],d[1])}}}).directive("uibTypeaheadPopup",function(){return{scope:{matches:"=",query:"=",active:"=",position:"&",moveInProgress:"=",select:"&"},replace:!0,templateUrl:function(a,b){return b.popupTemplateUrl||"template/typeahead/typeahead-popup.html"},link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("uibTypeaheadMatch",["$templateRequest","$compile","$parse",function(a,b,c){return{scope:{index:"=",match:"=",query:"="},link:function(d,e,f){var g=c(f.templateUrl)(d.$parent)||"template/typeahead/typeahead-match.html";a(g).then(function(a){b(a.trim())(d,function(a){e.replaceWith(a)})})}}}]).filter("uibTypeaheadHighlight",["$sce","$injector","$log",function(a,b,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}function e(a){return/<.*>/g.test(a)}var f;return f=b.has("$sanitize"),function(b,g){return!f&&e(b)&&c.warn("Unsafe use of typeahead please use ngSanitize"),b=g?(""+b).replace(new RegExp(d(g),"gi"),"$&"):b,f||(b=a.trustAsHtml(b)),b}}]),angular.module("ui.bootstrap.typeahead").value("$typeaheadSuppressWarning",!1).service("typeaheadParser",["$parse","uibTypeaheadParser","$log","$typeaheadSuppressWarning",function(a,b,c,d){return d||c.warn("typeaheadParser is now deprecated. Use uibTypeaheadParser instead."),b}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$window","$rootScope","$uibPosition","typeaheadParser","$log","$typeaheadSuppressWarning",function(a,b,c,d,e,f,g,h,i,j,k){var l=[9,13,27,38,40],m=200;return{require:["ngModel","^?ngModelOptions"],link:function(n,o,p,q){function r(){N.moveInProgress||(N.moveInProgress=!0,N.$digest()),V&&d.cancel(V),V=d(function(){N.matches.length&&s(),N.moveInProgress=!1},m)}function s(){N.position=F?h.offset(o):h.position(o),N.position.top+=o.prop("offsetHeight")}k||j.warn("typeahead is now deprecated. Use uib-typeahead instead.");var t=q[0],u=q[1],v=n.$eval(p.typeaheadMinLength);v||0===v||(v=1);var w,x,y=n.$eval(p.typeaheadWaitMs)||0,z=n.$eval(p.typeaheadEditable)!==!1,A=b(p.typeaheadLoading).assign||angular.noop,B=b(p.typeaheadOnSelect),C=angular.isDefined(p.typeaheadSelectOnBlur)?n.$eval(p.typeaheadSelectOnBlur):!1,D=b(p.typeaheadNoResults).assign||angular.noop,E=p.typeaheadInputFormatter?b(p.typeaheadInputFormatter):void 0,F=p.typeaheadAppendToBody?n.$eval(p.typeaheadAppendToBody):!1,G=p.typeaheadAppendToElementId||!1,H=n.$eval(p.typeaheadFocusFirst)!==!1,I=p.typeaheadSelectOnExact?n.$eval(p.typeaheadSelectOnExact):!1,J=b(p.ngModel),K=b(p.ngModel+"($$$p)"),L=function(a,b){return angular.isFunction(J(n))&&u&&u.$options&&u.$options.getterSetter?K(a,{$$$p:b}):J.assign(a,b)},M=i.parse(p.typeahead),N=n.$new(),O=n.$on("$destroy",function(){N.$destroy()});N.$on("$destroy",O);var P="typeahead-"+N.$id+"-"+Math.floor(1e4*Math.random());o.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":P});var Q=angular.element("
    ");Q.attr({id:P,matches:"matches",active:"activeIdx",select:"select(activeIdx)","move-in-progress":"moveInProgress",query:"query",position:"position"}),angular.isDefined(p.typeaheadTemplateUrl)&&Q.attr("template-url",p.typeaheadTemplateUrl),angular.isDefined(p.typeaheadPopupTemplateUrl)&&Q.attr("popup-template-url",p.typeaheadPopupTemplateUrl);var R=function(){N.matches=[],N.activeIdx=-1,o.attr("aria-expanded",!1)},S=function(a){return P+"-option-"+a};N.$watch("activeIdx",function(a){0>a?o.removeAttr("aria-activedescendant"):o.attr("aria-activedescendant",S(a))});var T=function(a,b){return N.matches.length>b&&a?a.toUpperCase()===N.matches[b].label.toUpperCase():!1},U=function(a){var b={$viewValue:a};A(n,!0),D(n,!1),c.when(M.source(n,b)).then(function(c){var d=a===t.$viewValue;if(d&&w)if(c&&c.length>0){N.activeIdx=H?0:-1,D(n,!1),N.matches.length=0;for(var e=0;e=v?y>0?(Y(),X(a)):U(a):(A(n,!1),Y(),R()),z?a:a?void t.$setValidity("editable",!1):(t.$setValidity("editable",!0),null)}),t.$formatters.push(function(a){var b,c,d={};return z||t.$setValidity("editable",!0),E?(d.$model=a,E(n,d)):(d[M.itemName]=a,b=M.viewMapper(n,d),d[M.itemName]=void 0,c=M.viewMapper(n,d),b!==c?b:a)}),N.select=function(a){var b,c,e={};x=!0,e[M.itemName]=c=N.matches[a].model,b=M.modelMapper(n,e),L(n,b),t.$setValidity("editable",!0),t.$setValidity("parse",!0),B(n,{$item:c,$model:b,$label:M.viewMapper(n,e)}),R(),N.$eval(p.typeaheadFocusOnSelect)!==!1&&d(function(){o[0].focus()},0,!1)},o.bind("keydown",function(a){if(0!==N.matches.length&&-1!==l.indexOf(a.which)){if(-1===N.activeIdx&&(9===a.which||13===a.which))return R(),void N.$digest();a.preventDefault(),40===a.which?(N.activeIdx=(N.activeIdx+1)%N.matches.length,N.$digest()):38===a.which?(N.activeIdx=(N.activeIdx>0?N.activeIdx:N.matches.length)-1,N.$digest()):13===a.which||9===a.which?N.$apply(function(){N.select(N.activeIdx)}):27===a.which&&(a.stopPropagation(),R(),N.$digest())}}),o.bind("blur",function(){C&&N.matches.length&&-1!==N.activeIdx&&!x&&(x=!0,N.$apply(function(){N.select(N.activeIdx)})),w=!1,x=!1});var Z=function(a){o[0]!==a.target&&3!==a.which&&0!==N.matches.length&&(R(),g.$$phase||N.$digest())};e.bind("click",Z),n.$on("$destroy",function(){e.unbind("click",Z),(F||G)&&$.remove(),F&&(angular.element(f).unbind("resize",r),e.find("body").unbind("scroll",r)),Q.remove()});var $=a(Q)(N);F?e.find("body").append($):G!==!1?angular.element(e[0].getElementById(G)).append($):o.after($)}}}]).directive("typeaheadPopup",["$typeaheadSuppressWarning","$log",function(a,b){return{scope:{matches:"=",query:"=",active:"=",position:"&",moveInProgress:"=",select:"&"},replace:!0,templateUrl:function(a,b){return b.popupTemplateUrl||"template/typeahead/typeahead-popup.html"},link:function(c,d,e){a||b.warn("typeahead-popup is now deprecated. Use uib-typeahead-popup instead."),c.templateUrl=e.templateUrl,c.isOpen=function(){return c.matches.length>0},c.isActive=function(a){return c.active==a},c.selectActive=function(a){c.active=a},c.selectMatch=function(a){c.select({activeIdx:a})}}}}]).directive("typeaheadMatch",["$templateRequest","$compile","$parse","$typeaheadSuppressWarning","$log",function(a,b,c,d,e){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(f,g,h){d||e.warn("typeahead-match is now deprecated. Use uib-typeahead-match instead.");var i=c(h.templateUrl)(f.$parent)||"template/typeahead/typeahead-match.html";a(i).then(function(a){b(a.trim())(f,function(a){g.replaceWith(a)})})}}}]).filter("typeaheadHighlight",["$sce","$injector","$log","$typeaheadSuppressWarning",function(a,b,c,d){function e(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}function f(a){return/<.*>/g.test(a)}var g;return g=b.has("$sanitize"),function(b,h){return d||c.warn("typeaheadHighlight is now deprecated. Use uibTypeaheadHighlight instead."),!g&&f(b)&&c.warn("Unsafe use of typeahead please use ngSanitize"),b=h?(""+b).replace(new RegExp(e(h),"gi"),"$&"):b,g||(b=a.trustAsHtml(b)),b}}]),!angular.$$csp()&&angular.element(document).find("head").prepend(''); \ No newline at end of file diff --git a/vendor/assets/components/angular-cookies/.bower.json b/vendor/assets/components/angular-cookies/.bower.json new file mode 100644 index 000000000..8fa27aba1 --- /dev/null +++ b/vendor/assets/components/angular-cookies/.bower.json @@ -0,0 +1,20 @@ +{ + "name": "angular-cookies", + "version": "1.3.20", + "main": "./angular-cookies.js", + "ignore": [], + "dependencies": { + "angular": "1.3.20" + }, + "homepage": "https://github.com/angular/bower-angular-cookies", + "_release": "1.3.20", + "_resolution": { + "type": "version", + "tag": "v1.3.20", + "commit": "fe6acf196d30f65b2d75438c9f7d9eb283da0d6f" + }, + "_source": "git://github.com/angular/bower-angular-cookies.git", + "_target": "1.3.20", + "_originalSource": "angular-cookies", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-cookies/README.md b/vendor/assets/components/angular-cookies/README.md new file mode 100644 index 000000000..7b190d346 --- /dev/null +++ b/vendor/assets/components/angular-cookies/README.md @@ -0,0 +1,68 @@ +# packaged angular-cookies + +This repo is for distribution on `npm` and `bower`. The source for this module is in the +[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngCookies). +Please file issues and pull requests against that repo. + +## Install + +You can install this package either with `npm` or with `bower`. + +### npm + +```shell +npm install angular-cookies +``` + +Then add `ngCookies` as a dependency for your app: + +```javascript +angular.module('myApp', [require('angular-cookies')]); +``` + +### bower + +```shell +bower install angular-cookies +``` + +Add a ` +``` + +Then add `ngCookies` as a dependency for your app: + +```javascript +angular.module('myApp', ['ngCookies']); +``` + +## Documentation + +Documentation is available on the +[AngularJS docs site](http://docs.angularjs.org/api/ngCookies). + +## License + +The MIT License + +Copyright (c) 2010-2015 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/assets/components/angular-cookies/angular-cookies.js b/vendor/assets/components/angular-cookies/angular-cookies.js new file mode 100644 index 000000000..d564aa251 --- /dev/null +++ b/vendor/assets/components/angular-cookies/angular-cookies.js @@ -0,0 +1,207 @@ +/** + * @license AngularJS v1.3.20 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/** + * @ngdoc module + * @name ngCookies + * @description + * + * # ngCookies + * + * The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies. + * + * + *
    + * + * See {@link ngCookies.$cookies `$cookies`} and + * {@link ngCookies.$cookieStore `$cookieStore`} for usage. + */ + + +angular.module('ngCookies', ['ng']). + /** + * @ngdoc service + * @name $cookies + * + * @description + * Provides read/write access to browser's cookies. + * + * Only a simple Object is exposed and by adding or removing properties to/from this object, new + * cookies are created/deleted at the end of current $eval. + * The object's properties can only be strings. + * + * Requires the {@link ngCookies `ngCookies`} module to be installed. + * + * @example + * + * ```js + * angular.module('cookiesExample', ['ngCookies']) + * .controller('ExampleController', ['$cookies', function($cookies) { + * // Retrieving a cookie + * var favoriteCookie = $cookies.myFavorite; + * // Setting a cookie + * $cookies.myFavorite = 'oatmeal'; + * }]); + * ``` + */ + factory('$cookies', ['$rootScope', '$browser', function($rootScope, $browser) { + var cookies = {}, + lastCookies = {}, + lastBrowserCookies, + runEval = false, + copy = angular.copy, + isUndefined = angular.isUndefined; + + //creates a poller fn that copies all cookies from the $browser to service & inits the service + $browser.addPollFn(function() { + var currentCookies = $browser.cookies(); + if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl + lastBrowserCookies = currentCookies; + copy(currentCookies, lastCookies); + copy(currentCookies, cookies); + if (runEval) $rootScope.$apply(); + } + })(); + + runEval = true; + + //at the end of each eval, push cookies + //TODO: this should happen before the "delayed" watches fire, because if some cookies are not + // strings or browser refuses to store some cookies, we update the model in the push fn. + $rootScope.$watch(push); + + return cookies; + + + /** + * Pushes all the cookies from the service to the browser and verifies if all cookies were + * stored. + */ + function push() { + var name, + value, + browserCookies, + updated; + + //delete any cookies deleted in $cookies + for (name in lastCookies) { + if (isUndefined(cookies[name])) { + $browser.cookies(name, undefined); + delete lastCookies[name]; + } + } + + //update all cookies updated in $cookies + for (name in cookies) { + value = cookies[name]; + if (!angular.isString(value)) { + value = '' + value; + cookies[name] = value; + } + if (value !== lastCookies[name]) { + $browser.cookies(name, value); + lastCookies[name] = value; + updated = true; + } + } + + //verify what was actually stored + if (updated) { + browserCookies = $browser.cookies(); + + for (name in cookies) { + if (cookies[name] !== browserCookies[name]) { + //delete or reset all cookies that the browser dropped from $cookies + if (isUndefined(browserCookies[name])) { + delete cookies[name]; + delete lastCookies[name]; + } else { + cookies[name] = lastCookies[name] = browserCookies[name]; + } + } + } + } + } + }]). + + + /** + * @ngdoc service + * @name $cookieStore + * @requires $cookies + * + * @description + * Provides a key-value (string-object) storage, that is backed by session cookies. + * Objects put or retrieved from this storage are automatically serialized or + * deserialized by angular's toJson/fromJson. + * + * Requires the {@link ngCookies `ngCookies`} module to be installed. + * + * @example + * + * ```js + * angular.module('cookieStoreExample', ['ngCookies']) + * .controller('ExampleController', ['$cookieStore', function($cookieStore) { + * // Put cookie + * $cookieStore.put('myFavorite','oatmeal'); + * // Get cookie + * var favoriteCookie = $cookieStore.get('myFavorite'); + * // Removing a cookie + * $cookieStore.remove('myFavorite'); + * }]); + * ``` + */ + factory('$cookieStore', ['$cookies', function($cookies) { + + return { + /** + * @ngdoc method + * @name $cookieStore#get + * + * @description + * Returns the value of given cookie key + * + * @param {string} key Id to use for lookup. + * @returns {Object} Deserialized cookie value. + */ + get: function(key) { + var value = $cookies[key]; + return value ? angular.fromJson(value) : value; + }, + + /** + * @ngdoc method + * @name $cookieStore#put + * + * @description + * Sets a value for given cookie key + * + * @param {string} key Id for the `value`. + * @param {Object} value Value to be stored. + */ + put: function(key, value) { + $cookies[key] = angular.toJson(value); + }, + + /** + * @ngdoc method + * @name $cookieStore#remove + * + * @description + * Remove given cookie + * + * @param {string} key Id of the key-value pair to delete. + */ + remove: function(key) { + delete $cookies[key]; + } + }; + + }]); + + +})(window, window.angular); diff --git a/vendor/assets/components/angular-cookies/angular-cookies.min.js b/vendor/assets/components/angular-cookies/angular-cookies.min.js new file mode 100644 index 000000000..f73736b53 --- /dev/null +++ b/vendor/assets/components/angular-cookies/angular-cookies.min.js @@ -0,0 +1,8 @@ +/* + AngularJS v1.3.20 + (c) 2010-2014 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(p,g,n){'use strict';g.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(e,b){var c={},f={},h,k=!1,l=g.copy,m=g.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,l(a,f),l(a,c),k&&e.$apply())})();k=!0;e.$watch(function(){var a,d,e;for(a in f)m(c[a])&&(b.cookies(a,n),delete f[a]);for(a in c)d=c[a],g.isString(d)||(d=""+d,c[a]=d),d!==f[a]&&(b.cookies(a,d),f[a]=d,e=!0);if(e)for(a in d=b.cookies(),c)c[a]!==d[a]&&(m(d[a])?(delete c[a],delete f[a]):c[a]= +f[a]=d[a])});return c}]).factory("$cookieStore",["$cookies",function(e){return{get:function(b){return(b=e[b])?g.fromJson(b):b},put:function(b,c){e[b]=g.toJson(c)},remove:function(b){delete e[b]}}}])})(window,window.angular); +//# sourceMappingURL=angular-cookies.min.js.map diff --git a/vendor/assets/components/angular-cookies/angular-cookies.min.js.map b/vendor/assets/components/angular-cookies/angular-cookies.min.js.map new file mode 100644 index 000000000..87491ad1b --- /dev/null +++ b/vendor/assets/components/angular-cookies/angular-cookies.min.js.map @@ -0,0 +1,8 @@ +{ +"version":3, +"file":"angular-cookies.min.js", +"lineCount":7, +"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkBC,CAAlB,CAA6B,CAmBtCD,CAAAE,OAAA,CAAe,WAAf,CAA4B,CAAC,IAAD,CAA5B,CAAAC,QAAA,CA0BW,UA1BX,CA0BuB,CAAC,YAAD,CAAe,UAAf,CAA2B,QAAQ,CAACC,CAAD,CAAaC,CAAb,CAAuB,CAAA,IACvEC,EAAU,EAD6D,CAEvEC,EAAc,EAFyD,CAGvEC,CAHuE,CAIvEC,EAAU,CAAA,CAJ6D,CAKvEC,EAAOV,CAAAU,KALgE,CAMvEC,EAAcX,CAAAW,YAGlBN,EAAAO,UAAA,CAAmB,QAAQ,EAAG,CAC5B,IAAIC,EAAiBR,CAAAC,QAAA,EACjBE,EAAJ,EAA0BK,CAA1B,GACEL,CAGA,CAHqBK,CAGrB,CAFAH,CAAA,CAAKG,CAAL,CAAqBN,CAArB,CAEA,CADAG,CAAA,CAAKG,CAAL,CAAqBP,CAArB,CACA,CAAIG,CAAJ,EAAaL,CAAAU,OAAA,EAJf,CAF4B,CAA9B,CAAA,EAUAL,EAAA,CAAU,CAAA,CAKVL,EAAAW,OAAA,CASAC,QAAa,EAAG,CAAA,IACVC,CADU,CAEVC,CAFU,CAIVC,CAGJ,KAAKF,CAAL,GAAaV,EAAb,CACMI,CAAA,CAAYL,CAAA,CAAQW,CAAR,CAAZ,CAAJ,GACEZ,CAAAC,QAAA,CAAiBW,CAAjB,CAAuBhB,CAAvB,CACA,CAAA,OAAOM,CAAA,CAAYU,CAAZ,CAFT,CAOF,KAAKA,CAAL,GAAaX,EAAb,CACEY,CAKA,CALQZ,CAAA,CAAQW,CAAR,CAKR,CAJKjB,CAAAoB,SAAA,CAAiBF,CAAjB,CAIL,GAHEA,CACA,CADQ,EACR,CADaA,CACb,CAAAZ,CAAA,CAAQW,CAAR,CAAA,CAAgBC,CAElB,EAAIA,CAAJ,GAAcX,CAAA,CAAYU,CAAZ,CAAd,GACEZ,CAAAC,QAAA,CAAiBW,CAAjB,CAAuBC,CAAvB,CAEA,CADAX,CAAA,CAAYU,CAAZ,CACA,CADoBC,CACpB,CAAAC,CAAA,CAAU,CAAA,CAHZ,CAQF,IAAIA,CAAJ,CAGE,IAAKF,CAAL,GAFAI,EAEaf,CAFID,CAAAC,QAAA,EAEJA,CAAAA,CAAb,CACMA,CAAA,CAAQW,CAAR,CAAJ,GAAsBI,CAAA,CAAeJ,CAAf,CAAtB,GAEMN,CAAA,CAAYU,CAAA,CAAeJ,CAAf,CAAZ,CAAJ,EACE,OAAOX,CAAA,CAAQW,CAAR,CACP,CAAA,OAAOV,CAAA,CAAYU,CAAZ,CAFT,EAIEX,CAAA,CAAQW,CAAR,CAJF;AAIkBV,CAAA,CAAYU,CAAZ,CAJlB,CAIsCI,CAAA,CAAeJ,CAAf,CANxC,CAjCU,CAThB,CAEA,OAAOX,EA1BoE,CAA1D,CA1BvB,CAAAH,QAAA,CAqIW,cArIX,CAqI2B,CAAC,UAAD,CAAa,QAAQ,CAACmB,CAAD,CAAW,CAErD,MAAO,CAWLC,IAAKA,QAAQ,CAACC,CAAD,CAAM,CAEjB,MAAO,CADHN,CACG,CADKI,CAAA,CAASE,CAAT,CACL,EAAQxB,CAAAyB,SAAA,CAAiBP,CAAjB,CAAR,CAAkCA,CAFxB,CAXd,CA0BLQ,IAAKA,QAAQ,CAACF,CAAD,CAAMN,CAAN,CAAa,CACxBI,CAAA,CAASE,CAAT,CAAA,CAAgBxB,CAAA2B,OAAA,CAAeT,CAAf,CADQ,CA1BrB,CAuCLU,OAAQA,QAAQ,CAACJ,CAAD,CAAM,CACpB,OAAOF,CAAA,CAASE,CAAT,CADa,CAvCjB,CAF8C,CAAhC,CArI3B,CAnBsC,CAArC,CAAD,CAyMGzB,MAzMH,CAyMWA,MAAAC,QAzMX;", +"sources":["angular-cookies.js"], +"names":["window","angular","undefined","module","factory","$rootScope","$browser","cookies","lastCookies","lastBrowserCookies","runEval","copy","isUndefined","addPollFn","currentCookies","$apply","$watch","push","name","value","updated","isString","browserCookies","$cookies","get","key","fromJson","put","toJson","remove"] +} diff --git a/vendor/assets/components/angular-cookies/bower.json b/vendor/assets/components/angular-cookies/bower.json new file mode 100644 index 000000000..d4075a279 --- /dev/null +++ b/vendor/assets/components/angular-cookies/bower.json @@ -0,0 +1,9 @@ +{ + "name": "angular-cookies", + "version": "1.3.20", + "main": "./angular-cookies.js", + "ignore": [], + "dependencies": { + "angular": "1.3.20" + } +} diff --git a/vendor/assets/components/angular-cookies/index.js b/vendor/assets/components/angular-cookies/index.js new file mode 100644 index 000000000..657667549 --- /dev/null +++ b/vendor/assets/components/angular-cookies/index.js @@ -0,0 +1,2 @@ +require('./angular-cookies'); +module.exports = 'ngCookies'; diff --git a/vendor/assets/components/angular-cookies/package.json b/vendor/assets/components/angular-cookies/package.json new file mode 100644 index 000000000..97e74204f --- /dev/null +++ b/vendor/assets/components/angular-cookies/package.json @@ -0,0 +1,26 @@ +{ + "name": "angular-cookies", + "version": "1.3.20", + "description": "AngularJS module for cookies", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/angular/angular.js.git" + }, + "keywords": [ + "angular", + "framework", + "browser", + "cookies", + "client-side" + ], + "author": "Angular Core Team ", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/angular.js/issues" + }, + "homepage": "http://angularjs.org" +} diff --git a/vendor/assets/components/angular-google-analytics/.bower.json b/vendor/assets/components/angular-google-analytics/.bower.json index 65d4cd80e..4a8dc65a4 100644 --- a/vendor/assets/components/angular-google-analytics/.bower.json +++ b/vendor/assets/components/angular-google-analytics/.bower.json @@ -2,7 +2,7 @@ "author": "revolunet", "name": "angular-google-analytics", "description": "Easy Analytics for your AngularJS application", - "version": "0.0.13", + "version": "0.0.15", "homepage": "https://github.com/revolunet/angular-google-analytics", "repository": { "type": "git", @@ -16,11 +16,11 @@ "angular-mocks": ">=1", "angular-scenario": ">=1" }, - "_release": "0.0.13", + "_release": "0.0.15", "_resolution": { "type": "version", - "tag": "0.0.13", - "commit": "c549bd76b086b9ca7f5538e61edbfe9d20c222c3" + "tag": "0.0.15", + "commit": "966ec3a7752cfe6eb11c5649134bb07e21d1207c" }, "_source": "git://github.com/revolunet/angular-google-analytics.git", "_target": "~0.0.3", diff --git a/vendor/assets/components/angular-google-analytics/bower.json b/vendor/assets/components/angular-google-analytics/bower.json index 4e5db03b9..bef391977 100644 --- a/vendor/assets/components/angular-google-analytics/bower.json +++ b/vendor/assets/components/angular-google-analytics/bower.json @@ -2,7 +2,7 @@ "author": "revolunet", "name": "angular-google-analytics", "description": "Easy Analytics for your AngularJS application", - "version": "0.0.13", + "version": "0.0.15", "homepage": "https://github.com/revolunet/angular-google-analytics", "repository": { "type": "git", diff --git a/vendor/assets/components/angular-google-analytics/dist/angular-google-analytics.js b/vendor/assets/components/angular-google-analytics/dist/angular-google-analytics.js index 14e85087f..53e365e1e 100644 --- a/vendor/assets/components/angular-google-analytics/dist/angular-google-analytics.js +++ b/vendor/assets/components/angular-google-analytics/dist/angular-google-analytics.js @@ -1,6 +1,6 @@ /** * Angular Google Analytics - Easy tracking for your AngularJS application - * @version v0.0.13 - 2015-03-26 + * @version v0.0.15 - 2015-04-27 * @link http://github.com/revolunet/angular-google-analytics * @author Julien Bouquillon * @license MIT License, http://www.opensource.org/licenses/MIT diff --git a/vendor/assets/components/angular-google-analytics/dist/angular-google-analytics.min.js b/vendor/assets/components/angular-google-analytics/dist/angular-google-analytics.min.js index d74fbddda..85cc648df 100644 --- a/vendor/assets/components/angular-google-analytics/dist/angular-google-analytics.min.js +++ b/vendor/assets/components/angular-google-analytics/dist/angular-google-analytics.min.js @@ -1,6 +1,6 @@ /** * Angular Google Analytics - Easy tracking for your AngularJS application - * @version v0.0.13 - 2015-03-26 + * @version v0.0.15 - 2015-04-27 * @link http://github.com/revolunet/angular-google-analytics * @author Julien Bouquillon * @license MIT License, http://www.opensource.org/licenses/MIT diff --git a/vendor/assets/components/angular-google-analytics/package.json b/vendor/assets/components/angular-google-analytics/package.json index 76dcfdb40..b8590a6c5 100644 --- a/vendor/assets/components/angular-google-analytics/package.json +++ b/vendor/assets/components/angular-google-analytics/package.json @@ -1,7 +1,7 @@ { "name": "angular-google-analytics", "description": "Angular Google Analytics - Easy tracking for your AngularJS application", - "version": "0.0.13", + "version": "0.0.15", "homepage": "http://github.com/revolunet/angular-google-analytics", "author": "Julien Bouquillon ", "main": "./src/angular-google-analytics.js", @@ -30,8 +30,6 @@ "grunt-conventional-changelog": "0.0.12" }, "scripts": { - "postinstall": "node ./node_modules/bower/bin/bower install", - "postupdate": "node ./node_modules/bower/bin/bower update", "test": "karma start test/karma.conf.js --single-run --browsers Chrome", "test-server": "karma start test/karma.conf.js --browsers Chrome" } diff --git a/vendor/assets/components/angular-growl-v2/.bower.json b/vendor/assets/components/angular-growl-v2/.bower.json new file mode 100644 index 000000000..b88e46aac --- /dev/null +++ b/vendor/assets/components/angular-growl-v2/.bower.json @@ -0,0 +1,49 @@ +{ + "author": [ + "Marco Rinck", + "Jan Stevens", + "Silvan van Leeuwen" + ], + "name": "angular-growl-v2", + "description": "growl like notifications for angularJS projects, using bootstrap alert classes.", + "version": "0.7.9", + "homepage": "http://janstevens.github.io/angular-growl-2", + "repository": { + "type": "git", + "url": "https://github.com/Swilvan/angular-growl-2" + }, + "license": "MIT", + "main": [ + "./build/angular-growl.js", + "./build/angular-growl.css" + ], + "ignore": [ + ".jshintrc", + ".gitignore", + "README.md", + "CHANGELOG.md", + "package.json", + "gruntfile.js", + "karma.conf.js", + "doc", + "src", + "test", + "demo" + ], + "dependencies": { + "angular": ">=1.2.1" + }, + "devDependencies": { + "angular-mocks": ">=1.2.1" + }, + "_release": "0.7.9", + "_resolution": { + "type": "version", + "tag": "v0.7.9", + "commit": "255df72deadf61f7b244d3c8a1ec274aac7c1774" + }, + "_source": "git://github.com/JanStevens/angular-growl-2.git", + "_target": "~0.7.9", + "_originalSource": "angular-growl-v2", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-growl/LICENSE b/vendor/assets/components/angular-growl-v2/LICENSE similarity index 100% rename from vendor/assets/components/angular-growl/LICENSE rename to vendor/assets/components/angular-growl-v2/LICENSE diff --git a/vendor/assets/components/angular-growl-v2/bower.json b/vendor/assets/components/angular-growl-v2/bower.json new file mode 100644 index 000000000..48885582d --- /dev/null +++ b/vendor/assets/components/angular-growl-v2/bower.json @@ -0,0 +1,36 @@ +{ + "author": [ + "Marco Rinck", + "Jan Stevens", + "Silvan van Leeuwen" + ], + "name": "angular-growl-v2", + "description": "growl like notifications for angularJS projects, using bootstrap alert classes.", + "version": "0.7.9", + "homepage": "http://janstevens.github.io/angular-growl-2", + "repository": { + "type": "git", + "url": "https://github.com/Swilvan/angular-growl-2" + }, + "license": "MIT", + "main": ["./build/angular-growl.js", "./build/angular-growl.css"], + "ignore": [ + ".jshintrc", + ".gitignore", + "README.md", + "CHANGELOG.md", + "package.json", + "gruntfile.js", + "karma.conf.js", + "doc", + "src", + "test", + "demo" + ], + "dependencies": { + "angular": ">=1.2.1" + }, + "devDependencies": { + "angular-mocks": ">=1.2.1" + } +} diff --git a/vendor/assets/components/angular-growl-v2/build/angular-growl.css b/vendor/assets/components/angular-growl-v2/build/angular-growl.css new file mode 100644 index 000000000..b50e10d2d --- /dev/null +++ b/vendor/assets/components/angular-growl-v2/build/angular-growl.css @@ -0,0 +1,135 @@ +/** + * angular-growl-v2 - v0.7.8 - 2015-10-25 + * http://janstevens.github.io/angular-growl-2 + * Copyright (c) 2015 Marco Rinck,Jan Stevens,Silvan van Leeuwen; Licensed MIT + */ +/* + * growl-container styles + */ +.growl-container.growl-fixed { + position: fixed; + float: right; + width: 90%; + max-width: 400px; + z-index: 9999; +} +.growl-container.growl-fixed.top-right { + top: 10px; + right: 15px; +} +.growl-container.growl-fixed.bottom-right { + bottom: 10px; + right: 15px; +} +.growl-container.growl-fixed.middle-right { + top: 49%; + right: 15px; +} +.growl-container.growl-fixed.top-left { + top: 10px; + left: 15px; +} +.growl-container.growl-fixed.bottom-left { + bottom: 10px; + left: 15px; +} +.growl-container.growl-fixed.middle-left { + top: 49%; + left: 15px; +} +.growl-container.growl-fixed.top-center { + top: 10px; + left: 50%; + margin-left: -200px; +} +.growl-container.growl-fixed.bottom-center { + bottom: 10px; + left: 50%; + margin-left: -200px; +} +.growl-container.growl-fixed.middle-center { + top: 49%; + left: 50%; + margin-left: -200px; +} + +/* + * growl-item styles + */ +.growl-container > .growl-item { + padding: 10px; + padding-right: 35px; + margin-bottom: 10px; + cursor: pointer; +} + +.growl-container > button { + border: none; + outline:none; +} +.growl-container > .growl-item.ng-enter, +.growl-container > .growl-item.ng-leave { + -webkit-transition:0.5s linear all; + -moz-transition:0.5s linear all; + -o-transition:0.5s linear all; + transition:0.5s linear all; +} + +.growl-container > .growl-item.ng-enter, +.growl-container > .growl-item.ng-leave.ng-leave-active { + opacity:0; +} +.growl-container > .growl-item.ng-leave, +.growl-container > .growl-item.ng-enter.ng-enter-active { + opacity:1; +} + +.growl-container > div.growl-item { + background-position: 12px center; + background-repeat: no-repeat; +} + +/* + * growl-title styles + */ +.growl-title { + font-size: 16px; +} +.growl-item.icon > .growl-title { + margin: 0 0 0 40px; +} + +/* + * growl-message styles + */ +.growl-item.icon > .growl-message { + margin: 0 0 0 40px; +} + +/* + * growl background images + */ +.growl-container > .alert-info.icon { + /* for the white images + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII="); + */ + background-image: url("data:image/png;base64,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"); +} +.growl-container > .alert-error.icon { + /* for the white images + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII="); + */ + background-image: url("data:image/png;base64,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"); +} +.growl-container > .alert-success.icon { + /* for the white images + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg=="); + */ + background-image: url("data:image/png;base64,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"); +} +.growl-container > .alert-warning.icon { + /* for the white images + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII="); + */ + background-image: url("data:image/png;base64,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"); +} diff --git a/vendor/assets/components/angular-growl-v2/build/angular-growl.js b/vendor/assets/components/angular-growl-v2/build/angular-growl.js new file mode 100644 index 000000000..8e5f818e9 --- /dev/null +++ b/vendor/assets/components/angular-growl-v2/build/angular-growl.js @@ -0,0 +1,431 @@ +/** + * angular-growl-v2 - v0.7.8 - 2015-10-25 + * http://janstevens.github.io/angular-growl-2 + * Copyright (c) 2015 Marco Rinck,Jan Stevens,Silvan van Leeuwen; Licensed MIT + */ +angular.module('angular-growl', []); +angular.module('angular-growl').directive('growl', [function () { + 'use strict'; + return { + restrict: 'A', + templateUrl: 'templates/growl/growl.html', + replace: false, + scope: { + reference: '@', + inline: '=', + limitMessages: '=' + }, + controller: [ + '$scope', + '$interval', + 'growl', + 'growlMessages', + function ($scope, $interval, growl, growlMessages) { + $scope.referenceId = $scope.reference || 0; + growlMessages.initDirective($scope.referenceId, $scope.limitMessages); + $scope.growlMessages = growlMessages; + $scope.inlineMessage = angular.isDefined($scope.inline) ? $scope.inline : growl.inlineMessages(); + $scope.$watch('limitMessages', function (limitMessages) { + var directive = growlMessages.directives[$scope.referenceId]; + if (!angular.isUndefined(limitMessages) && !angular.isUndefined(directive)) { + directive.limitMessages = limitMessages; + } + }); + $scope.stopTimeoutClose = function (message) { + if (!message.clickToClose) { + angular.forEach(message.promises, function (promise) { + $interval.cancel(promise); + }); + if (message.close) { + growlMessages.deleteMessage(message); + } else { + message.close = true; + } + } + }; + $scope.alertClasses = function (message) { + return { + 'alert-success': message.severity === 'success', + 'alert-error': message.severity === 'error', + 'alert-danger': message.severity === 'error', + 'alert-info': message.severity === 'info', + 'alert-warning': message.severity === 'warning', + 'icon': message.disableIcons === false, + 'alert-dismissable': !message.disableCloseButton + }; + }; + $scope.showCountDown = function (message) { + return !message.disableCountDown && message.ttl > 0; + }; + $scope.wrapperClasses = function () { + var classes = {}; + classes['growl-fixed'] = !$scope.inlineMessage; + classes[growl.position()] = true; + return classes; + }; + $scope.computeTitle = function (message) { + var ret = { + 'success': 'Success', + 'error': 'Error', + 'info': 'Information', + 'warn': 'Warning' + }; + return ret[message.severity]; + }; + } + ] + }; + }]); +angular.module('angular-growl').run([ + '$templateCache', + function ($templateCache) { + 'use strict'; + if ($templateCache.get('templates/growl/growl.html') === undefined) { + $templateCache.put('templates/growl/growl.html', '
    ' + '
    ' + '' + '' + '

    ' + '
    ' + '
    ' + '
    '); + } + } +]); +angular.module('angular-growl').provider('growl', function () { + 'use strict'; + var _ttl = { + success: null, + error: null, + warning: null, + info: null + }, _messagesKey = 'messages', _messageTextKey = 'text', _messageTitleKey = 'title', _messageSeverityKey = 'severity', _messageTTLKey = 'ttl', _onlyUniqueMessages = true, _messageVariableKey = 'variables', _referenceId = 0, _inline = false, _position = 'top-right', _disableCloseButton = false, _disableIcons = false, _reverseOrder = false, _disableCountDown = false, _translateMessages = true; + this.globalTimeToLive = function (ttl) { + if (typeof ttl === 'object') { + for (var k in ttl) { + if (ttl.hasOwnProperty(k)) { + _ttl[k] = ttl[k]; + } + } + } else { + for (var severity in _ttl) { + if (_ttl.hasOwnProperty(severity)) { + _ttl[severity] = ttl; + } + } + } + return this; + }; + this.globalTranslateMessages = function (translateMessages) { + _translateMessages = translateMessages; + return this; + }; + this.globalDisableCloseButton = function (disableCloseButton) { + _disableCloseButton = disableCloseButton; + return this; + }; + this.globalDisableIcons = function (disableIcons) { + _disableIcons = disableIcons; + return this; + }; + this.globalReversedOrder = function (reverseOrder) { + _reverseOrder = reverseOrder; + return this; + }; + this.globalDisableCountDown = function (countDown) { + _disableCountDown = countDown; + return this; + }; + this.messageVariableKey = function (messageVariableKey) { + _messageVariableKey = messageVariableKey; + return this; + }; + this.globalInlineMessages = function (inline) { + _inline = inline; + return this; + }; + this.globalPosition = function (position) { + _position = position; + return this; + }; + this.messagesKey = function (messagesKey) { + _messagesKey = messagesKey; + return this; + }; + this.messageTextKey = function (messageTextKey) { + _messageTextKey = messageTextKey; + return this; + }; + this.messageTitleKey = function (messageTitleKey) { + _messageTitleKey = messageTitleKey; + return this; + }; + this.messageSeverityKey = function (messageSeverityKey) { + _messageSeverityKey = messageSeverityKey; + return this; + }; + this.messageTTLKey = function (messageTTLKey) { + _messageTTLKey = messageTTLKey; + return this; + }; + this.onlyUniqueMessages = function (onlyUniqueMessages) { + _onlyUniqueMessages = onlyUniqueMessages; + return this; + }; + this.serverMessagesInterceptor = [ + '$q', + 'growl', + function ($q, growl) { + function checkResponse(response) { + if (response !== undefined && response.data && response.data[_messagesKey] && response.data[_messagesKey].length > 0) { + growl.addServerMessages(response.data[_messagesKey]); + } + } + return { + 'response': function (response) { + checkResponse(response); + return response; + }, + 'responseError': function (rejection) { + checkResponse(rejection); + return $q.reject(rejection); + } + }; + } + ]; + this.$get = [ + '$rootScope', + '$interpolate', + '$sce', + '$filter', + '$interval', + 'growlMessages', + function ($rootScope, $interpolate, $sce, $filter, $interval, growlMessages) { + var translate; + growlMessages.onlyUnique = _onlyUniqueMessages; + growlMessages.reverseOrder = _reverseOrder; + try { + translate = $filter('translate'); + } catch (e) { + } + function broadcastMessage(message) { + if (translate && message.translateMessage) { + message.text = translate(message.text, message.variables) || message.text; + message.title = translate(message.title) || message.title; + } else { + var polation = $interpolate(message.text); + message.text = polation(message.variables); + } + var addedMessage = growlMessages.addMessage(message); + $rootScope.$broadcast('growlMessage', message); + $interval(function () { + }, 0, 1); + return addedMessage; + } + function sendMessage(text, config, severity) { + var _config = config || {}, message; + message = { + text: text, + title: _config.title, + severity: severity, + ttl: _config.ttl || _ttl[severity], + variables: _config.variables || {}, + disableCloseButton: _config.disableCloseButton === undefined ? _disableCloseButton : _config.disableCloseButton, + disableIcons: _config.disableIcons === undefined ? _disableIcons : _config.disableIcons, + disableCountDown: _config.disableCountDown === undefined ? _disableCountDown : _config.disableCountDown, + position: _config.position || _position, + referenceId: _config.referenceId || _referenceId, + translateMessage: _config.translateMessage === undefined ? _translateMessages : _config.translateMessage, + destroy: function () { + growlMessages.deleteMessage(message); + }, + setText: function (newText) { + message.text = $sce.trustAsHtml(String(newText)); + }, + onclose: _config.onclose, + onopen: _config.onopen + }; + return broadcastMessage(message); + } + function warning(text, config) { + return sendMessage(text, config, 'warning'); + } + function error(text, config) { + return sendMessage(text, config, 'error'); + } + function info(text, config) { + return sendMessage(text, config, 'info'); + } + function success(text, config) { + return sendMessage(text, config, 'success'); + } + function general(text, config, severity) { + severity = (severity || 'error').toLowerCase(); + return sendMessage(text, config, severity); + } + function addServerMessages(messages) { + if (!messages || !messages.length) { + return; + } + var i, message, severity, length; + length = messages.length; + for (i = 0; i < length; i++) { + message = messages[i]; + if (message[_messageTextKey]) { + severity = (message[_messageSeverityKey] || 'error').toLowerCase(); + var config = {}; + config.variables = message[_messageVariableKey] || {}; + config.title = message[_messageTitleKey]; + if (message[_messageTTLKey]) { + config.ttl = message[_messageTTLKey]; + } + sendMessage(message[_messageTextKey], config, severity); + } + } + } + function onlyUnique() { + return _onlyUniqueMessages; + } + function reverseOrder() { + return _reverseOrder; + } + function inlineMessages() { + return _inline; + } + function position() { + return _position; + } + return { + warning: warning, + error: error, + info: info, + success: success, + general: general, + addServerMessages: addServerMessages, + onlyUnique: onlyUnique, + reverseOrder: reverseOrder, + inlineMessages: inlineMessages, + position: position + }; + } + ]; +}); +angular.module('angular-growl').service('growlMessages', [ + '$sce', + '$interval', + function ($sce, $interval) { + 'use strict'; + var self = this; + this.directives = {}; + var preloadDirectives = {}; + function preLoad(referenceId) { + var directive; + if (preloadDirectives[referenceId]) { + directive = preloadDirectives[referenceId]; + } else { + directive = preloadDirectives[referenceId] = { messages: [] }; + } + return directive; + } + function directiveForRefId(referenceId) { + var refId = referenceId || 0; + return self.directives[refId] || preloadDirectives[refId]; + } + this.initDirective = function (referenceId, limitMessages) { + if (preloadDirectives[referenceId]) { + this.directives[referenceId] = preloadDirectives[referenceId]; + this.directives[referenceId].limitMessages = limitMessages; + } else { + this.directives[referenceId] = { + messages: [], + limitMessages: limitMessages + }; + } + return this.directives[referenceId]; + }; + this.getAllMessages = function (referenceId) { + referenceId = referenceId || 0; + var messages; + if (directiveForRefId(referenceId)) { + messages = directiveForRefId(referenceId).messages; + } else { + messages = []; + } + return messages; + }; + this.destroyAllMessages = function (referenceId) { + var messages = this.getAllMessages(referenceId); + for (var i = messages.length - 1; i >= 0; i--) { + messages[i].destroy(); + } + var directive = directiveForRefId(referenceId); + if (directive) { + directive.messages = []; + } + }; + this.addMessage = function (message) { + var directive, messages, found, msgText; + if (this.directives[message.referenceId]) { + directive = this.directives[message.referenceId]; + } else { + directive = preLoad(message.referenceId); + } + messages = directive.messages; + if (this.onlyUnique) { + angular.forEach(messages, function (msg) { + msgText = $sce.getTrustedHtml(msg.text); + if (message.text === msgText && message.severity === msg.severity && message.title === msg.title) { + found = true; + } + }); + if (found) { + return; + } + } + message.text = $sce.trustAsHtml(String(message.text)); + if (message.ttl && message.ttl !== -1) { + message.countdown = message.ttl / 1000; + message.promises = []; + message.close = false; + message.countdownFunction = function () { + if (message.countdown > 1) { + message.countdown--; + message.promises.push($interval(message.countdownFunction, 1000, 1, 1)); + } else { + message.countdown--; + } + }; + } + if (angular.isDefined(directive.limitMessages)) { + var diff = messages.length - (directive.limitMessages - 1); + if (diff > 0) { + messages.splice(directive.limitMessages - 1, diff); + } + } + if (this.reverseOrder) { + messages.unshift(message); + } else { + messages.push(message); + } + if (typeof message.onopen === 'function') { + message.onopen(); + } + if (message.ttl && message.ttl !== -1) { + var self = this; + message.promises.push($interval(angular.bind(this, function () { + self.deleteMessage(message); + }), message.ttl, 1, 1)); + message.promises.push($interval(message.countdownFunction, 1000, 1, 1)); + } + return message; + }; + this.deleteMessage = function (message) { + var messages = this.getAllMessages(message.referenceId), index = -1; + for (var i in messages) { + if (messages.hasOwnProperty(i)) { + index = messages[i] === message ? i : index; + } + } + if (index > -1) { + messages[index].close = true; + messages.splice(index, 1); + } + if (typeof message.onclose === 'function') { + message.onclose(); + } + }; + } +]); \ No newline at end of file diff --git a/vendor/assets/components/angular-growl-v2/build/angular-growl.min.css b/vendor/assets/components/angular-growl-v2/build/angular-growl.min.css new file mode 100644 index 000000000..1016b02e6 --- /dev/null +++ b/vendor/assets/components/angular-growl-v2/build/angular-growl.min.css @@ -0,0 +1,7 @@ +/** + * angular-growl-v2 - v0.7.5 - 2015-10-02 + * http://janstevens.github.io/angular-growl-2 + * Copyright (c) 2015 Marco Rinck,Jan Stevens,Silvan van Leeuwen; Licensed MIT + */ + +.growl-container.growl-fixed{position:fixed;float:right;width:90%;max-width:400px;z-index:9999}.growl-container.growl-fixed.top-right{top:10px;right:15px}.growl-container.growl-fixed.bottom-right{bottom:10px;right:15px}.growl-container.growl-fixed.middle-right{top:49%;right:15px}.growl-container.growl-fixed.top-left{top:10px;left:15px}.growl-container.growl-fixed.bottom-left{bottom:10px;left:15px}.growl-container.growl-fixed.middle-left{top:49%;left:15px}.growl-container.growl-fixed.top-center{top:10px;left:50%;margin-left:-200px}.growl-container.growl-fixed.bottom-center{bottom:10px;left:50%;margin-left:-200px}.growl-container.growl-fixed.middle-center{top:49%;left:50%;margin-left:-200px}.growl-container>.growl-item{padding:10px;padding-right:35px;margin-bottom:10px;cursor:pointer}.growl-container>button{border:0;outline:0}.growl-container>.growl-item.ng-enter,.growl-container>.growl-item.ng-leave{-webkit-transition:.5s linear all;-moz-transition:.5s linear all;-o-transition:.5s linear all;transition:.5s linear all}.growl-container>.growl-item.ng-enter,.growl-container>.growl-item.ng-leave.ng-leave-active{opacity:0}.growl-container>.growl-item.ng-leave,.growl-container>.growl-item.ng-enter.ng-enter-active{opacity:1}.growl-container>div.growl-item{background-position:12px center;background-repeat:no-repeat}.growl-title{font-size:16px}.growl-item.icon>.growl-title{margin:0 0 0 40px}.growl-item.icon>.growl-message{margin:0 0 0 40px}.growl-container>.alert-info.icon{background-image:url("data:image/png;base64,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")}.growl-container>.alert-error.icon{background-image:url("data:image/png;base64,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")}.growl-container>.alert-success.icon{background-image:url("data:image/png;base64,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")}.growl-container>.alert-warning.icon{background-image:url("data:image/png;base64,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")} \ No newline at end of file diff --git a/vendor/assets/components/angular-growl-v2/build/angular-growl.min.js b/vendor/assets/components/angular-growl-v2/build/angular-growl.min.js new file mode 100644 index 000000000..42b604d4a --- /dev/null +++ b/vendor/assets/components/angular-growl-v2/build/angular-growl.min.js @@ -0,0 +1,6 @@ +/** + * angular-growl-v2 - v0.7.8 - 2015-10-25 + * http://janstevens.github.io/angular-growl-2 + * Copyright (c) 2015 Marco Rinck,Jan Stevens,Silvan van Leeuwen; Licensed MIT + */ +angular.module("angular-growl",[]),angular.module("angular-growl").directive("growl",[function(){"use strict";return{restrict:"A",templateUrl:"templates/growl/growl.html",replace:!1,scope:{reference:"@",inline:"=",limitMessages:"="},controller:["$scope","$interval","growl","growlMessages",function(a,b,c,d){a.referenceId=a.reference||0,d.initDirective(a.referenceId,a.limitMessages),a.growlMessages=d,a.inlineMessage=angular.isDefined(a.inline)?a.inline:c.inlineMessages(),a.$watch("limitMessages",function(b){var c=d.directives[a.referenceId];angular.isUndefined(b)||angular.isUndefined(c)||(c.limitMessages=b)}),a.stopTimeoutClose=function(a){a.clickToClose||(angular.forEach(a.promises,function(a){b.cancel(a)}),a.close?d.deleteMessage(a):a.close=!0)},a.alertClasses=function(a){return{"alert-success":"success"===a.severity,"alert-error":"error"===a.severity,"alert-danger":"error"===a.severity,"alert-info":"info"===a.severity,"alert-warning":"warning"===a.severity,icon:a.disableIcons===!1,"alert-dismissable":!a.disableCloseButton}},a.showCountDown=function(a){return!a.disableCountDown&&a.ttl>0},a.wrapperClasses=function(){var b={};return b["growl-fixed"]=!a.inlineMessage,b[c.position()]=!0,b},a.computeTitle=function(a){var b={success:"Success",error:"Error",info:"Information",warn:"Warning"};return b[a.severity]}}]}}]),angular.module("angular-growl").run(["$templateCache",function(a){"use strict";void 0===a.get("templates/growl/growl.html")&&a.put("templates/growl/growl.html",'

    ')}]),angular.module("angular-growl").provider("growl",function(){"use strict";var a={success:null,error:null,warning:null,info:null},b="messages",c="text",d="title",e="severity",f="ttl",g=!0,h="variables",i=0,j=!1,k="top-right",l=!1,m=!1,n=!1,o=!1,p=!0;this.globalTimeToLive=function(b){if("object"==typeof b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);else for(var d in a)a.hasOwnProperty(d)&&(a[d]=b);return this},this.globalTranslateMessages=function(a){return p=a,this},this.globalDisableCloseButton=function(a){return l=a,this},this.globalDisableIcons=function(a){return m=a,this},this.globalReversedOrder=function(a){return n=a,this},this.globalDisableCountDown=function(a){return o=a,this},this.messageVariableKey=function(a){return h=a,this},this.globalInlineMessages=function(a){return j=a,this},this.globalPosition=function(a){return k=a,this},this.messagesKey=function(a){return b=a,this},this.messageTextKey=function(a){return c=a,this},this.messageTitleKey=function(a){return d=a,this},this.messageSeverityKey=function(a){return e=a,this},this.messageTTLKey=function(a){return f=a,this},this.onlyUniqueMessages=function(a){return g=a,this},this.serverMessagesInterceptor=["$q","growl",function(a,c){function d(a){void 0!==a&&a.data&&a.data[b]&&a.data[b].length>0&&c.addServerMessages(a.data[b])}return{response:function(a){return d(a),a},responseError:function(b){return d(b),a.reject(b)}}}],this.$get=["$rootScope","$interpolate","$sce","$filter","$interval","growlMessages",function(b,q,r,s,t,u){function v(a){if(H&&a.translateMessage)a.text=H(a.text,a.variables)||a.text,a.title=H(a.title)||a.title;else{var c=q(a.text);a.text=c(a.variables)}var d=u.addMessage(a);return b.$broadcast("growlMessage",a),t(function(){},0,1),d}function w(b,c,d){var e,f=c||{};return e={text:b,title:f.title,severity:d,ttl:f.ttl||a[d],variables:f.variables||{},disableCloseButton:void 0===f.disableCloseButton?l:f.disableCloseButton,disableIcons:void 0===f.disableIcons?m:f.disableIcons,disableCountDown:void 0===f.disableCountDown?o:f.disableCountDown,position:f.position||k,referenceId:f.referenceId||i,translateMessage:void 0===f.translateMessage?p:f.translateMessage,destroy:function(){u.deleteMessage(e)},setText:function(a){e.text=r.trustAsHtml(String(a))},onclose:f.onclose,onopen:f.onopen},v(e)}function x(a,b){return w(a,b,"warning")}function y(a,b){return w(a,b,"error")}function z(a,b){return w(a,b,"info")}function A(a,b){return w(a,b,"success")}function B(a,b,c){return c=(c||"error").toLowerCase(),w(a,b,c)}function C(a){if(a&&a.length){var b,g,i,j;for(j=a.length,b=0;j>b;b++)if(g=a[b],g[c]){i=(g[e]||"error").toLowerCase();var k={};k.variables=g[h]||{},k.title=g[d],g[f]&&(k.ttl=g[f]),w(g[c],k,i)}}}function D(){return g}function E(){return n}function F(){return j}function G(){return k}var H;u.onlyUnique=g,u.reverseOrder=n;try{H=s("translate")}catch(I){}return{warning:x,error:y,info:z,success:A,general:B,addServerMessages:C,onlyUnique:D,reverseOrder:E,inlineMessages:F,position:G}}]}),angular.module("angular-growl").service("growlMessages",["$sce","$interval",function(a,b){"use strict";function c(a){var b;return b=f[a]?f[a]:f[a]={messages:[]}}function d(a){var b=a||0;return e.directives[b]||f[b]}var e=this;this.directives={};var f={};this.initDirective=function(a,b){return f[a]?(this.directives[a]=f[a],this.directives[a].limitMessages=b):this.directives[a]={messages:[],limitMessages:b},this.directives[a]},this.getAllMessages=function(a){a=a||0;var b;return b=d(a)?d(a).messages:[]},this.destroyAllMessages=function(a){for(var b=this.getAllMessages(a),c=b.length-1;c>=0;c--)b[c].destroy();var e=d(a);e&&(e.messages=[])},this.addMessage=function(d){var e,f,g,h;if(e=this.directives[d.referenceId]?this.directives[d.referenceId]:c(d.referenceId),f=e.messages,!this.onlyUnique||(angular.forEach(f,function(b){h=a.getTrustedHtml(b.text),d.text===h&&d.severity===b.severity&&d.title===b.title&&(g=!0)}),!g)){if(d.text=a.trustAsHtml(String(d.text)),d.ttl&&-1!==d.ttl&&(d.countdown=d.ttl/1e3,d.promises=[],d.close=!1,d.countdownFunction=function(){d.countdown>1?(d.countdown--,d.promises.push(b(d.countdownFunction,1e3,1,1))):d.countdown--}),angular.isDefined(e.limitMessages)){var i=f.length-(e.limitMessages-1);i>0&&f.splice(e.limitMessages-1,i)}if(this.reverseOrder?f.unshift(d):f.push(d),"function"==typeof d.onopen&&d.onopen(),d.ttl&&-1!==d.ttl){var j=this;d.promises.push(b(angular.bind(this,function(){j.deleteMessage(d)}),d.ttl,1,1)),d.promises.push(b(d.countdownFunction,1e3,1,1))}return d}},this.deleteMessage=function(a){var b=this.getAllMessages(a.referenceId),c=-1;for(var d in b)b.hasOwnProperty(d)&&(c=b[d]===a?d:c);c>-1&&(b[c].close=!0,b.splice(c,1)),"function"==typeof a.onclose&&a.onclose()}}]); \ No newline at end of file diff --git a/vendor/assets/components/angular-growl/.bower.json b/vendor/assets/components/angular-growl/.bower.json deleted file mode 100644 index a91d2af0c..000000000 --- a/vendor/assets/components/angular-growl/.bower.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "author": "Marco Rinck", - "name": "angular-growl", - "description": "growl like notifications for angularJS projects, using bootstrap alert classes", - "version": "0.4.0", - "homepage": "https://github.com/marcorinck/angular-growl", - "repository": { - "type": "git", - "url": "https://github.com/marcorinck/angular-growl" - }, - "license": "MIT", - "main": "./build/angular-growl.js", - "ignore": [ - "src", - "test", - ".jshintrc", - "package.json", - "gruntfile.js", - "karma.conf.js", - "bower.json", - "demo", - ".gitignore" - ], - "dependencies": { - "angular": "1.2.1" - }, - "devDependencies": { - "angular-mocks": "1.2.1" - }, - "_release": "0.4.0", - "_resolution": { - "type": "version", - "tag": "v0.4.0", - "commit": "e0e8b2cda660f28c75eefa7c9703952368547cdd" - }, - "_source": "git://github.com/marcorinck/angular-growl.git", - "_target": ">=0.4.0", - "_originalSource": "angular-growl" -} \ No newline at end of file diff --git a/vendor/assets/components/angular-growl/README.md b/vendor/assets/components/angular-growl/README.md deleted file mode 100644 index 095036a6a..000000000 --- a/vendor/assets/components/angular-growl/README.md +++ /dev/null @@ -1,275 +0,0 @@ -#angular-growl - -> growl like notifications for angularJS projects, using bootstrap alert classes - -##Features - -![Standard bootstrap 2.x styles](doc/screenshot.jpg) - -* growl like notifications like in MacOS X -* using standard bootstrap classes (alert, alert-info, alert-error, alert-success) -* global or per message configuration of a timeout when message will be automatically closed -* automatic translation of messages if [angular-translate](https://github.com/PascalPrecht/angular-translate) filter is -present, you only have to provide keys as messages, angular-translate will translate them -* pre-defined $http-Interceptor to automatically handle $http responses for server-sent messages -* automatic CSS animations when adding/closing notifications (only when using >= angularJS 1.2) -* < 1 kB after GZIP - -##Changelog - -**0.4.0** - 19th Nov 2013 - -* updated dependency to angularJS 1.2.x, angular-growl does not work with 1.0.x anymore (BREAKING CHANGE) -* new option: only display unique messages, which is the new default, disable to allow same message more than once (BREAKING CHANGE) -* new option: allow html tags in messages, default is off you need to - -**0.3.1** - 1st Oct 2013 - -* bugfix: translating of messages works again -* change: also set alert css classes introduced by bootstrap 3 - -**0.3.0** - 26th Sept 2013 - -* adding css animations support via ngAnimate (for angularJS >= 1.2) -* ability to configure server message keys - -**0.2.0** - 22nd Sept 2013 - -* reworking, bugfixing and documenting handling of server sent messages/notifications -* externalizing css styles of growl class -* provide minified versions of js and css files in build folder - -**0.1.3** - 20th Sept 2013 - -* introducing ttl config option, fixes #2 - -##Installation - -You can install angular-growl with bower: - -> bower install angular-growl - -Alternatively you can download the files in the [build folder](build/) manually and include them in your project. - -````html - - - - - - - - - - -```` - -As angular-growl is based on its own angularJS module, you have to alter your dependency list when creating your application -module: - -````javascript -var app = angular.module('myApp', ['angular-growl']); -```` - -Finally, you have to include the directive somewhere in your HTML like this: - -````html - -
    - -```` - -##Usage - -Just let angular inject the growl Factory into your code and call the 4 functions that the factory provides accordingly: - -````javascript -app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { - $scope.addSpecialWarnMessage = function() { - growl.addWarnMessage("This adds a warn message"); - growl.addInfoMessage("This adds a info message"); - growl.addSuccessMessage("This adds a success message"); - growl.addErrorMessage("This adds a error message"); - } -}]); -```` - -If [angular-translate](https://github.com/PascalPrecht/angular-translate) is present, its filter is automatically called for translating of messages, so you have to provide -only the key: - -````javascript -app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { - $scope.addSpecialWarnMessage = function() { - growl.addSuccessMessage("SAVE_SUCCESS_MESSAGE"); - growl.addErrorMessage("VALIDATION_ERROR"); - } -}]); -```` - -##Configuration - -###Only unique messages - -* Default: true - -Accept only unique messages as a new message. If a message is already displayed (text and severity are the same) then this -message will not be added to the displayed message list. Set to false, to always display all messages regardless if they -are already displayed or not: - -````javascript -var app = angular.module('myApp', ['angular-growl']); - -app.config(['growlProvider', function(growlProvider) { - growlProvider.onlyUniqueMessages(false); -}]); -```` - -###Automatic closing of notifications (timeout, ttl) - -* Default: none (all messages need to be closed manually by the user.) - -However, you can configure a global timeout (TTL) after which notifications should be automatically closed. To do -this, you have to configure this during config phase of angular bootstrap like this: - -````javascript -var app = angular.module('myApp', ['angular-growl']); - -app.config(['growlProvider', function(growlProvider) { - growlProvider.globalTimeToLive(5000); -}]); -```` - -This sets a global timeout of 5 seconds after which every notification will be closed. - -You can override TTL generally for every single message if you want: - -````javascript -app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { - $scope.addSpecialWarnMessage = function() { - growl.addWarnMessage("Override global ttl setting", {ttl: 10000}); - } -}]); -```` - -This sets a 10 second timeout, after which the notification will be automatically closed. - -If you have set a global TTL, you can disable automatic closing of single notifications by setting their ttl to -1: - -````javascript -app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { - $scope.addSpecialWarnMessage = function() { - growl.addWarnMessage("this will not be closed automatically even when a global ttl is set", {ttl: -1}); - } -}]); -```` - -###Allow HTML in messages - -* Default: false - -Turn this on to be able to display html tags in messages, default behaviour is to NOT display HTML. - -For this to work, you have to declare a dependency to "ngSanitize" (and load the extra javascript) in your own application -module! - -````javascript -var app = angular.module('myApp', ['angular-growl', 'ngSanitize']); - -app.config(['growlProvider', function(growlProvider) { - growlProvider.globalEnableHtml(true); -}]); -```` - -You can override the global option and allow HTML tags in single messages too: - -````javascript -app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { - $scope.addSpecialWarnMessage = function() { - growl.addWarnMessage("This is a HTML message", {enableHtml: true}); - } -}]); -```` - -###Animations - -Beginning with angularJS 1.2 growl messages can be automatically animated with CSS animations when adding and/or closing -them. All you have to do is load the angular-animate.js provided by angularJS and add **ngAnimate** to your applications -dependency list: - -````html - - - - - - - - - - -```` - -````javascript -var app = angular.module('myApp', ['angular-growl', 'ngAnimate']); -```` - -That's it. The angular-growl.css comes with a pre-defined animation of 0.5s to opacity. - -To configure the animations, just change the _growl-item.*_ classes in the css file to your preference. F.i. to change length -of animation from 0.5s to 1s do this: - -````css -.growl-item.ng-enter, -.growl-item.ng-leave { - -webkit-transition:1s linear all; - -moz-transition:1s linear all; - -o-transition:1s linear all; - transition:1s linear all; -} -```` - -Basically you can style your animations just as you like if ngAnimate can pick it up automatically. See the [ngAnimate -docs](http://docs.angularjs.org/api/ngAnimate) for more info. - -###Handling of server sent notifications - -When doing $http requests, you can configure angular-growl to look automatically for messages in $http responses, so your -business logic on the server is able to send messages/notifications to the client and you can display them automagically: - -````javascript -var app = angular.module('myApp', ['angular-growl']); - -app.config(['growlProvider', '$httpProvider', function(growlProvider, $httpProvider) { - $httpProvider.responseInterceptors.push(growlProvider.serverMessagesInterceptor); -}]); -```` - -This adds a pre-defined angularJS HTTP interceptor that is called on every HTTP request and looks if response contains -messages. Interceptor looks in response for a "messages" array of objects with "text" and "severity" key. This is an example -response which results in 3 growl messages: - -````json -{ - "someOtherData": {...}, - "messages": [ - {"text":"this is a server message", "severity": "warn"}, - {"text":"this is another server message", "severity": "info"}, - {"text":"and another", "severity": "error"} - ] -} -```` - -You can configure the keys, the interceptor is looking for like this: - -````javascript -var app = angular.module("demo", ["angular-growl"]); - -app.config(["growlProvider", "$httpProvider", function(growlProvider, $httpProvider) { - growlProvider.messagesKey("my-messages"); - growlProvider.messageTextKey("messagetext"); - growlProvider.messageSeverityKey("severity-level"); - $httpProvider.responseInterceptors.push(growlProvider.serverMessagesInterceptor); -}]); -```` - -Server messages will be created with default TTL. diff --git a/vendor/assets/components/angular-growl/build/angular-growl.js b/vendor/assets/components/angular-growl/build/angular-growl.js deleted file mode 100644 index e4612960b..000000000 --- a/vendor/assets/components/angular-growl/build/angular-growl.js +++ /dev/null @@ -1,183 +0,0 @@ -/** - * angular-growl - v0.4.0 - 2013-11-19 - * https://github.com/marcorinck/angular-growl - * Copyright (c) 2013 Marco Rinck; Licensed MIT - */ -angular.module('angular-growl', []); -angular.module('angular-growl').directive('growl', [ - '$rootScope', - function ($rootScope) { - 'use strict'; - return { - restrict: 'A', - template: '
    ' + '\t
    ' + '\t\t' + '
    ' + '
    ' + '
    ' + '
    ' + '\t
    ' + '
    ', - replace: false, - scope: true, - controller: [ - '$scope', - '$timeout', - 'growl', - function ($scope, $timeout, growl) { - var onlyUnique = growl.onlyUnique(); - $scope.messages = []; - function addMessage(message) { - $scope.messages.push(message); - if (message.ttl && message.ttl !== -1) { - $timeout(function () { - $scope.deleteMessage(message); - }, message.ttl); - } - } - $rootScope.$on('growlMessage', function (event, message) { - var found; - if (onlyUnique) { - angular.forEach($scope.messages, function (msg) { - if (message.text === msg.text && message.severity === msg.severity) { - found = true; - } - }); - if (!found) { - addMessage(message); - } - } else { - addMessage(message); - } - }); - $scope.deleteMessage = function (message) { - var index = $scope.messages.indexOf(message); - if (index > -1) { - $scope.messages.splice(index, 1); - } - }; - $scope.computeClasses = function (message) { - return { - 'alert-success': message.severity === 'success', - 'alert-error': message.severity === 'error', - 'alert-danger': message.severity === 'error', - 'alert-info': message.severity === 'info', - 'alert-warning': message.severity === 'warn' - }; - }; - } - ] - }; - } -]); -angular.module('angular-growl').provider('growl', function () { - 'use strict'; - var _ttl = null, _enableHtml = false, _messagesKey = 'messages', _messageTextKey = 'text', _messageSeverityKey = 'severity', _onlyUniqueMessages = true; - this.globalTimeToLive = function (ttl) { - _ttl = ttl; - }; - this.globalEnableHtml = function (enableHtml) { - _enableHtml = enableHtml; - }; - this.messagesKey = function (messagesKey) { - _messagesKey = messagesKey; - }; - this.messageTextKey = function (messageTextKey) { - _messageTextKey = messageTextKey; - }; - this.messageSeverityKey = function (messageSeverityKey) { - _messageSeverityKey = messageSeverityKey; - }; - this.onlyUniqueMessages = function (onlyUniqueMessages) { - _onlyUniqueMessages = onlyUniqueMessages; - }; - this.serverMessagesInterceptor = [ - '$q', - 'growl', - function ($q, growl) { - function checkResponse(response) { - if (response.data[_messagesKey] && response.data[_messagesKey].length > 0) { - growl.addServerMessages(response.data[_messagesKey]); - } - } - function success(response) { - checkResponse(response); - return response; - } - function error(response) { - checkResponse(response); - return $q.reject(response); - } - return function (promise) { - return promise.then(success, error); - }; - } - ]; - this.$get = [ - '$rootScope', - '$filter', - function ($rootScope, $filter) { - var translate; - try { - translate = $filter('translate'); - } catch (e) { - } - function broadcastMessage(message) { - if (translate) { - message.text = translate(message.text); - } - $rootScope.$broadcast('growlMessage', message); - } - function sendMessage(text, config, severity) { - var _config = config || {}, message; - message = { - text: text, - severity: severity, - ttl: _config.ttl || _ttl, - enableHtml: _config.enableHtml || _enableHtml - }; - broadcastMessage(message); - } - function addWarnMessage(text, config) { - sendMessage(text, config, 'warn'); - } - function addErrorMessage(text, config) { - sendMessage(text, config, 'error'); - } - function addInfoMessage(text, config) { - sendMessage(text, config, 'info'); - } - function addSuccessMessage(text, config) { - sendMessage(text, config, 'success'); - } - function addServerMessages(messages) { - var i, message, severity, length; - length = messages.length; - for (i = 0; i < length; i++) { - message = messages[i]; - if (message[_messageTextKey] && message[_messageSeverityKey]) { - switch (message[_messageSeverityKey]) { - case 'warn': - severity = 'warn'; - break; - case 'success': - severity = 'success'; - break; - case 'info': - severity = 'info'; - break; - case 'error': - severity = 'error'; - break; - } - sendMessage(message[_messageTextKey], undefined, severity); - } - } - } - function onlyUnique() { - return _onlyUniqueMessages; - } - return { - addWarnMessage: addWarnMessage, - addErrorMessage: addErrorMessage, - addInfoMessage: addInfoMessage, - addSuccessMessage: addSuccessMessage, - addServerMessages: addServerMessages, - onlyUnique: onlyUnique - }; - } - ]; -}); \ No newline at end of file diff --git a/vendor/assets/components/angular-growl/build/angular-growl.min.css b/vendor/assets/components/angular-growl/build/angular-growl.min.css deleted file mode 100644 index e1ed8423b..000000000 --- a/vendor/assets/components/angular-growl/build/angular-growl.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/** - * angular-growl - v0.4.0 - 2013-11-19 - * https://github.com/marcorinck/angular-growl - * Copyright (c) 2013 Marco Rinck; Licensed MIT - */ - -.growl{position:fixed;top:10px;right:10px;float:right;width:250px}.growl-item.ng-enter,.growl-item.ng-leave{-webkit-transition:.5s linear all;-moz-transition:.5s linear all;-o-transition:.5s linear all;transition:.5s linear all}.growl-item.ng-enter,.growl-item.ng-leave.ng-leave-active{opacity:0}.growl-item.ng-leave,.growl-item.ng-enter.ng-enter-active{opacity:1} \ No newline at end of file diff --git a/vendor/assets/components/angular-growl/build/angular-growl.min.js b/vendor/assets/components/angular-growl/build/angular-growl.min.js deleted file mode 100644 index 83798c554..000000000 --- a/vendor/assets/components/angular-growl/build/angular-growl.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * angular-growl - v0.4.0 - 2013-11-19 - * https://github.com/marcorinck/angular-growl - * Copyright (c) 2013 Marco Rinck; Licensed MIT - */ -angular.module("angular-growl",[]),angular.module("angular-growl").directive("growl",["$rootScope",function(a){"use strict";return{restrict:"A",template:'
    ',replace:!1,scope:!0,controller:["$scope","$timeout","growl",function(b,c,d){function e(a){b.messages.push(a),a.ttl&&-1!==a.ttl&&c(function(){b.deleteMessage(a)},a.ttl)}var f=d.onlyUnique();b.messages=[],a.$on("growlMessage",function(a,c){var d;f?(angular.forEach(b.messages,function(a){c.text===a.text&&c.severity===a.severity&&(d=!0)}),d||e(c)):e(c)}),b.deleteMessage=function(a){var c=b.messages.indexOf(a);c>-1&&b.messages.splice(c,1)},b.computeClasses=function(a){return{"alert-success":"success"===a.severity,"alert-error":"error"===a.severity,"alert-danger":"error"===a.severity,"alert-info":"info"===a.severity,"alert-warning":"warn"===a.severity}}}]}}]),angular.module("angular-growl").provider("growl",function(){"use strict";var a=null,b=!1,c="messages",d="text",e="severity",f=!0;this.globalTimeToLive=function(b){a=b},this.globalEnableHtml=function(a){b=a},this.messagesKey=function(a){c=a},this.messageTextKey=function(a){d=a},this.messageSeverityKey=function(a){e=a},this.onlyUniqueMessages=function(a){f=a},this.serverMessagesInterceptor=["$q","growl",function(a,b){function d(a){a.data[c]&&a.data[c].length>0&&b.addServerMessages(a.data[c])}function e(a){return d(a),a}function f(b){return d(b),a.reject(b)}return function(a){return a.then(e,f)}}],this.$get=["$rootScope","$filter",function(c,g){function h(a){p&&(a.text=p(a.text)),c.$broadcast("growlMessage",a)}function i(c,d,e){var f,g=d||{};f={text:c,severity:e,ttl:g.ttl||a,enableHtml:g.enableHtml||b},h(f)}function j(a,b){i(a,b,"warn")}function k(a,b){i(a,b,"error")}function l(a,b){i(a,b,"info")}function m(a,b){i(a,b,"success")}function n(a){var b,c,f,g;for(g=a.length,b=0;g>b;b++)if(c=a[b],c[d]&&c[e]){switch(c[e]){case"warn":f="warn";break;case"success":f="success";break;case"info":f="info";break;case"error":f="error"}i(c[d],void 0,f)}}function o(){return f}var p;try{p=g("translate")}catch(q){}return{addWarnMessage:j,addErrorMessage:k,addInfoMessage:l,addSuccessMessage:m,addServerMessages:n,onlyUnique:o}}]}); \ No newline at end of file diff --git a/vendor/assets/components/angular-growl/doc/screenshot.jpg b/vendor/assets/components/angular-growl/doc/screenshot.jpg deleted file mode 100644 index 5c21b8e6f8c1bf06d9a3226315895ea5715ec81a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30921 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 diff --git a/vendor/assets/components/angular-i18n/.bower.json b/vendor/assets/components/angular-i18n/.bower.json index 8f754f13c..0bf4fad6a 100644 --- a/vendor/assets/components/angular-i18n/.bower.json +++ b/vendor/assets/components/angular-i18n/.bower.json @@ -15,7 +15,6 @@ "commit": "b69173b747aa6d40c19b4ce19cdff01df5c3cd11" }, "_source": "git://github.com/angular/bower-angular-i18n.git", - "_target": "~1.3.15", - "_originalSource": "angular-i18n", - "_direct": true + "_target": "=1.3.15", + "_originalSource": "angular-i18n" } \ No newline at end of file diff --git a/vendor/assets/components/angular-medium-editor/.bower.json b/vendor/assets/components/angular-medium-editor/.bower.json new file mode 100644 index 000000000..740ee98d0 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/.bower.json @@ -0,0 +1,19 @@ +{ + "name": "angular-medium-editor", + "version": "0.1.1", + "dependencies": { + "angular": "1.3.*", + "medium-editor": "4.4.*" + }, + "main": "./dist/angular-medium-editor.js", + "homepage": "https://github.com/thijsw/angular-medium-editor", + "_release": "0.1.1", + "_resolution": { + "type": "version", + "tag": "0.1.1", + "commit": "1683d0f4ada6e4e0011c4372335c702df9680fde" + }, + "_source": "git://github.com/thijsw/angular-medium-editor.git", + "_target": "~0.1.0", + "_originalSource": "angular-medium-editor" +} \ No newline at end of file diff --git a/vendor/assets/components/angular-medium-editor/.editorconfig b/vendor/assets/components/angular-medium-editor/.editorconfig new file mode 100644 index 000000000..e717f5eb6 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/vendor/assets/components/angular-medium-editor/.gitignore b/vendor/assets/components/angular-medium-editor/.gitignore new file mode 100644 index 000000000..264b0eaf1 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/.gitignore @@ -0,0 +1,4 @@ +/bower_components/ +/node_modules/ + +.DS_Store diff --git a/vendor/assets/components/angular-medium-editor/.jshintrc b/vendor/assets/components/angular-medium-editor/.jshintrc new file mode 100644 index 000000000..371d85575 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/.jshintrc @@ -0,0 +1,25 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": false, + "eqeqeq": true, + "immed": true, + "indent": 2, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": false, + "strict": true, + "globalstrict": true, + "trailing": true, + "smarttabs": true, + "predef": [ + "angular" + ] +} diff --git a/vendor/assets/components/angular-medium-editor/.travis.yml b/vendor/assets/components/angular-medium-editor/.travis.yml new file mode 100644 index 000000000..2cdacafbd --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - '0.10' + - '0.8' diff --git a/vendor/assets/components/angular-medium-editor/CONTRIBUTING.md b/vendor/assets/components/angular-medium-editor/CONTRIBUTING.md new file mode 100644 index 000000000..20d8c2b8d --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing + +## Important notes +Please don't edit files in the `dist` subdirectory as they are generated via Grunt. You'll find source code in the `src` subdirectory! + +### Code style +Regarding code style like indentation and whitespace, **follow the conventions you see used in the source already.** + +## Modifying the code +First, ensure that you have the latest [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed. + +Test that Grunt's CLI and Bower are installed by running `grunt --version` and `bower --version`. If the commands aren't found, run `npm install -g grunt-cli bower`. For more information about installing the tools, see the [getting started with Grunt guide](http://gruntjs.com/getting-started) or [bower.io](http://bower.io/) respectively. + +1. Fork and clone the repo. +1. Run `npm install` to install all build dependencies (including Grunt). +1. Run `bower install` to install the front-end dependencies. +1. Run `grunt` to grunt this project. + +Assuming that you don't see any red, you're ready to go. Just be sure to run `grunt` after making any changes, to ensure that nothing is broken. + +## Submitting pull requests + +1. Create a new branch, please don't work in your `master` branch directly. +1. Update the documentation to reflect any changes. +1. Push to your fork and submit a pull request. diff --git a/vendor/assets/components/angular-medium-editor/Gruntfile.js b/vendor/assets/components/angular-medium-editor/Gruntfile.js new file mode 100644 index 000000000..e3236eabe --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/Gruntfile.js @@ -0,0 +1,154 @@ +'use strict'; + +module.exports = function(grunt) { + + // Configurable paths + var yoConfig = { + livereload: 35729, + src: 'src', + dist: 'dist' + }; + + // Livereload setup + var lrSnippet = require('connect-livereload')({port: yoConfig.livereload}); + var mountFolder = function (connect, dir) { + return connect.static(require('path').resolve(dir)); + }; + + // Load all grunt tasks + require('load-grunt-tasks')(grunt, {scope: 'devDependencies'}); + + // Project configuration + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + yo: yoConfig, + meta: { + banner: '/**\n' + + ' * <%= pkg.name %>\n' + + ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + + ' * @link <%= pkg.homepage %>\n' + + ' * @author <%= pkg.author.name %> <<%= pkg.author.email %>>\n' + + ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + + ' */\n' + }, + open: { + server: { + path: 'http://localhost:<%= connect.options.port %>' + } + }, + clean: { + dist: { + files: [{ + dot: true, + src: [ + '.tmp', + '<%= yo.dist %>/*', + '!<%= yo.dist %>/.git*' + ] + }] + }, + server: '.tmp' + }, + watch: { + gruntfile: { + files: '<%= jshint.gruntfile.src %>', + tasks: ['jshint:gruntfile'] + }, + app: { + files: [ + '<%= yo.src %>/{,*/}*.html', + '{.tmp,<%= yo.src %>}/{,*/}*.css', + '{.tmp,<%= yo.src %>}/{,*/}*.js' + ], + tasks: ['concat'], + options: { + livereload: yoConfig.livereload + } + } + }, + connect: { + options: { + port: 9000, + hostname: '0.0.0.0' // Change this to '0.0.0.0' to access the server from outside. + }, + livereload: { + options: { + middleware: function (connect) { + return [ + lrSnippet, + mountFolder(connect, '.tmp'), + mountFolder(connect, yoConfig.src) + ]; + } + } + } + }, + less: { + options: { + // dumpLineNumbers: 'all', + paths: ['<%= yo.src %>'] + }, + dist: { + files: { + '<%= yo.src %>/<%= yo.name %>.css': '<%= yo.src %>/<%= yo.name %>.less' + } + } + }, + jshint: { + gruntfile: { + options: { + jshintrc: '.jshintrc' + }, + src: 'Gruntfile.js' + }, + src: { + options: { + jshintrc: '.jshintrc' + }, + src: ['<%= yo.src %>/{,*/}*.js'] + } + }, + ngAnnotate: { + options: { + banner: '<%= meta.banner %>' + }, + dist: { + src: ['<%= yo.src %>/<%= pkg.name %>.js'], + dest: '<%= yo.dist %>/<%= pkg.name %>.js' + } + // dist: { + // files: { + // '/.js': '/.js' + // } + // } + }, + concat: { + options: { + banner: '<%= meta.banner %>', + stripBanners: true + }, + dist: { + src: ['<%= yo.src %>/<%= pkg.name %>.js'], + dest: '<%= yo.dist %>/<%= pkg.name %>.js' + } + }, + uglify: { + options: { + banner: '<%= meta.banner %>' + }, + dist: { + src: '<%= concat.dist.dest %>', + dest: '<%= yo.dist %>/<%= pkg.name %>.min.js' + } + } + }); + + grunt.registerTask('build', [ + 'clean:dist', + 'ngAnnotate:dist', + 'uglify:dist' + ]); + + grunt.registerTask('default', ['build']); + +}; diff --git a/vendor/assets/components/angular-medium-editor/README.md b/vendor/assets/components/angular-medium-editor/README.md new file mode 100644 index 000000000..3e6b133c9 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/README.md @@ -0,0 +1,75 @@ +# angular-medium-editor +This is an AngularJS directive for the [medium.com inline editor clone](https://github.com/daviferreira/medium-editor) made by Davi Ferreira. + + +## Getting Started + +Download the [production version][min] or the [development version][max]. + +[min]: https://raw.github.com/thijsw/angular-medium-editor/master/dist/angular-medium-editor.min.js +[max]: https://raw.github.com/thijsw/angular-medium-editor/master/dist/angular-medium-editor.js + +In your web page: + +```html + + +``` + +## Demo +If you want to view the included demo, you have to run bower first in order to retrieve the dependencies. + +```sh +$ bower install --save angular-medium-editor +``` + +## Documentation +Header example limited to one line and no toolbar +```html +

    +``` + +Paragraph with support for multiple lines and customized toolbar buttons +```html +

    +``` + +Example for extending the toolbar with customized element 'highlighter' (using [rangy](https://code.google.com/p/rangy/) and the [CSS Class Applier Module](https://code.google.com/p/rangy/wiki/CSSClassApplierModule) to support highlighting of text). For more detailed info on extensions, please refer to [MediumEditor](https://github.com/daviferreira/medium-editor). +```html +

    +``` +```javascript +function Highlighter() { + this.button = document.createElement('button'); + this.button.className = 'medium-editor-action'; + this.button.innerText = 'H'; + this.button.onclick = this.onClick.bind(this); + this.classApplier = rangy.createCssClassApplier('highlight', { + elementTagName: 'mark', + normalize: true + }); +} +Highlighter.prototype.onClick = function() { + this.classApplier.toggleSelection(); +}; +Highlighter.prototype.getButton = function() { + return this.button; +}; +Highlighter.prototype.checkState = function(node) { + if (node.tagName == 'MARK') { + this.button.classList.add('medium-editor-button-active'); + } +}; + +scope.mediumBindOptions = { + extensions: { + 'highlight': new Highlighter() + } +}; +``` + +_(More coming soon)_ + +## Examples +_(Coming soon)_ + diff --git a/vendor/assets/components/angular-medium-editor/bower.json b/vendor/assets/components/angular-medium-editor/bower.json new file mode 100644 index 000000000..a7a62a600 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/bower.json @@ -0,0 +1,9 @@ +{ + "name": "angular-medium-editor", + "version": "0.1.1", + "dependencies": { + "angular": "1.3.*", + "medium-editor": "4.4.*" + }, + "main": "./dist/angular-medium-editor.js" +} diff --git a/vendor/assets/components/angular-medium-editor/demo/css/demo.css b/vendor/assets/components/angular-medium-editor/demo/css/demo.css new file mode 100644 index 000000000..4a7712304 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/demo/css/demo.css @@ -0,0 +1,104 @@ +*:focus { + outline: none; +} + +@font-face { + font-family: 'ProximaNovaBlack'; + src: url('http://dfimg.com/fonts/proximanova-black-webfont.eot'); + src: url('http://dfimg.com/fonts/proximanova-black-webfont.eot?#iefix') format('embedded-opentype'), + url('http://dfimg.com/fonts/proximanova-black-webfont.woff') format('woff'), + url('http://dfimg.com/fonts/proximanova-black-webfont.ttf') format('truetype'), + url('http://dfimg.com/fonts/proximanova-black-webfont.svg#ProximaNovaBlack') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'ProximaNovaRegular'; + src: url('http://dfimg.com/fonts/proximanova-regular-webfont.eot'); + src: url('http://dfimg.com/fonts/proximanova-regular-webfont.eot?#iefix') format('embedded-opentype'), + url('http://dfimg.com/fonts/proximanova-regular-webfont.woff') format('woff'), + url('http://dfimg.com/fonts/proximanova-regular-webfont.ttf') format('truetype'), + url('http://dfimg.com/fonts/proximanova-regular-webfont.svg#ProximaNovaRegular') format('svg'); + font-weight: normal; + font-style: normal; +} + +body { + font-family: ProximaNovaRegular, Helvetica, Arial, sans-serif; + font-size: 22px; + line-height: 30px; +} + +#container { + width: 960px; + margin: 30px auto; +} + +h1 { + font-family: ProximaNovaBlack, Helvetica, Arial, sans-serif; + font-size: 60px; + font-weight: normal; + text-align: center; + margin-bottom: 40px; + padding-bottom: 40px; + letter-spacing: -2px; + border-bottom: 1px solid #dbdbdb; +} + +p { + margin-bottom: 40px; +} + +b { + font-family: ProximaNovaRegular, Helvetica, Arial, sans-serif; + font-weight: bolder +} + +a { + color:black; +} + +a:hover { + color:green; +} + +blockquote { + display: block; + padding-left: 20px; + border-left: 6px solid #df0d32; +} + +.editable { + outline: none; + margin: 0 0 20px 0; + padding: 0 0 20px 0; + border-bottom: 1px solid #dbdbdb; +} + + +#container { + width: 960px; + margin: 30px auto; +} + + +h3 { + font-size: 32px; + line-height: 42px; +} + +h4 { + font-size: 26px; + line-height: 32px; +} + + +blockquote { + border-left: 5px solid #555; + margin-left: -15px; + padding-left: 15px; + font-style: italic; + color: #555; +} + diff --git a/vendor/assets/components/angular-medium-editor/demo/css/normalize.css b/vendor/assets/components/angular-medium-editor/demo/css/normalize.css new file mode 100644 index 000000000..6d24a3853 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/demo/css/normalize.css @@ -0,0 +1,402 @@ +/*! normalize.css v2.1.1 | MIT License | git.io/normalize */ + +/* ========================================================================== + HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined in IE 8/9. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} + +/** + * Correct `inline-block` display not defined in IE 8/9. + */ + +audio, +canvas, +video { + display: inline-block; +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +[hidden] { + display: none; +} + +/* ========================================================================== + Base + ========================================================================== */ + +/** + * 1. Prevent system color scheme's background color being used in Firefox, IE, + * and Opera. + * 2. Prevent system color scheme's text color being used in Firefox, IE, and + * Opera. + * 3. Set default font family to sans-serif. + * 4. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + background: #fff; /* 1 */ + color: #000; /* 2 */ + font-family: sans-serif; /* 3 */ + -ms-text-size-adjust: 100%; /* 4 */ + -webkit-text-size-adjust: 100%; /* 4 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* ========================================================================== + Links + ========================================================================== */ + +/** + * Address `outline` inconsistency between Chrome and other browsers. + */ + +a:focus { + outline: thin dotted; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* ========================================================================== + Typography + ========================================================================== */ + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari 5, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9, Safari 5, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari 5 and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Correct font family set oddly in Safari 5 and Chrome. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + font-size: 1em; +} + +/** + * Improve readability of pre-formatted text in all browsers. + */ + +pre { + white-space: pre-wrap; +} + +/** + * Set consistent quote types. + */ + +q { + quotes: "\201C" "\201D" "\2018" "\2019"; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* ========================================================================== + Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9. + */ + +img { + border: 0; +} + +/** + * Correct overflow displayed oddly in IE 9. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* ========================================================================== + Figures + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari 5. + */ + +figure { + margin: 0; +} + +/* ========================================================================== + Forms + ========================================================================== */ + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * 1. Correct font family not being inherited in all browsers. + * 2. Correct font size not being inherited in all browsers. + * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. + */ + +button, +input, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +button, +input { + line-height: normal; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. + * Correct `select` style inheritance in Firefox 4+ and Opera. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * 1. Address box sizing set to `content-box` in IE 8/9. + * 2. Remove excess padding in IE 8/9. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari 5 and Chrome + * on OS X. + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * 1. Remove default vertical scrollbar in IE 8/9. + * 2. Improve readability and alignment in all browsers. + */ + +textarea { + overflow: auto; /* 1 */ + vertical-align: top; /* 2 */ +} + +/* ========================================================================== + Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/vendor/assets/components/angular-medium-editor/demo/demo.js b/vendor/assets/components/angular-medium-editor/demo/demo.js new file mode 100644 index 000000000..c6d357413 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/demo/demo.js @@ -0,0 +1,34 @@ +'use strict'; + +angular.module('demo', ['angular-medium-editor']) + +.controller('demo', ['$scope', function($scope) { + + $scope.text = + '

    My father’s family name being Pirrip, and my Christian name Philip, my infant tongue could make of both names nothing long' + + 'er or more explicit than Pip. So, I called myself Pip, and came to be called Pip.

    ' + + '

    I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs' + + '. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw a' + + 'ny likeness of either of them (for their days were long before the days of photographs), my first' + + 'fancies regarding what they were like were unreasonably derived from their tombstones. The shape ' + + 'of the letters on my father’s, gave me an odd idea that he was a square, stout, dark man, with cu' + + 'rly black hair. From the character and turn of the inscription, “Also Georgiana Wife of the Above' + + ',” I drew a childish conclusion that my mother was freckled and sickly. To five little stone loze' + + 'nges, each about a foot and a half long, which were arranged in a neat row beside their grave, an' + + 'd were sacred to the memory of five little brothers of mine,—who gave up trying to get a living, ' + + 'exceedingly early in that universal struggle,—I am indebted for a belief I religiously entertaine' + + 'd that they had all been born on their backs with their hands in their trousers-pockets, and had ' + + 'never taken them out in this state of existence.

    ' + + '

    Ours was the marsh country, down by the river, within, as the river wound, twenty miles of the' + + ' sea. My first most vivid and broad impression of the identity of things seems to me to have been' + + ' gained on a memorable raw afternoon towards evening. At such a time I found out for certain that' + + ' this bleak place overgrown with nettles was the churchyard; and that Philip Pirrip, late of this' + + ' parish, and also Georgiana wife of the above, were dead and buried; and that Alexander, Bartholo' + + 'mew, Abraham, Tobias, and Roger, infant children of the aforesaid, were also dead and buried; and' + + ' that the dark flat wilderness beyond the churchyard, intersected with dikes and mounds and gates' + + ', with scattered cattle feeding on it, was the marshes; and that the low leaden line beyond was t' + + 'he river; and that the distant savage lair from which the wind was rushing was the sea; and that ' + + 'the small bundle of shivers growing afraid of it all and beginning to cry, was Pip.

    '; + +}]); \ No newline at end of file diff --git a/vendor/assets/components/angular-medium-editor/demo/img/medium-editor.jpg b/vendor/assets/components/angular-medium-editor/demo/img/medium-editor.jpg new file mode 100644 index 0000000000000000000000000000000000000000..17b9b24742bffd6a7a2b1d612d5ef28d95356534 GIT binary patch literal 176383 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 literal 0 HcmV?d00001 diff --git a/vendor/assets/components/angular-medium-editor/demo/index.html b/vendor/assets/components/angular-medium-editor/demo/index.html new file mode 100644 index 000000000..d7ca7063f --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/demo/index.html @@ -0,0 +1,26 @@ + + + + + angularjs medium editor | demo + + + + + + + +
    +

    Medium Editor for AngularJS

    +
    +
    +
    +
    +

    Source

    + + + + + + diff --git a/vendor/assets/components/angular-medium-editor/dist/angular-medium-editor.js b/vendor/assets/components/angular-medium-editor/dist/angular-medium-editor.js new file mode 100644 index 000000000..b10bb54a4 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/dist/angular-medium-editor.js @@ -0,0 +1,85 @@ +'use strict'; + +angular.module('angular-medium-editor', []) + + .directive('mediumEditor', function() { + + return { + require: 'ngModel', + restrict: 'AE', + scope: { bindOptions: '=' }, + link: function(scope, iElement, iAttrs, ctrl) { + + angular.element(iElement).addClass('angular-medium-editor'); + + // Parse options + var opts = {}, + placeholder = ''; + var prepOpts = function() { + if (iAttrs.options) { + opts = scope.$eval(iAttrs.options); + } + var bindOpts = {}; + if (scope.bindOptions !== undefined) { + bindOpts = scope.bindOptions; + } + opts = angular.extend(opts, bindOpts); + }; + prepOpts(); + placeholder = opts.placeholder; + scope.$watch('bindOptions', function() { + // in case options are provided after mediumEditor directive has been compiled and linked (and after $render function executed) + // we need to re-initialize + if (ctrl.editor) { + ctrl.editor.destroy(); + } + prepOpts(); + // Hide placeholder when the model is not empty + if (!ctrl.$isEmpty(ctrl.$viewValue)) { + opts.placeholder = ''; + } + ctrl.editor = new MediumEditor(iElement, opts); + }); + + var onChange = function() { + + scope.$apply(function() { + + // If user cleared the whole text, we have to reset the editor because MediumEditor + // lacks an API method to alter placeholder after initialization + if (iElement.html() === '


    ' || iElement.html() === '') { + opts.placeholder = placeholder; + var editor = new MediumEditor(iElement, opts); + } + + ctrl.$setViewValue(iElement.html()); + }); + }; + + // view -> model + iElement.on('blur', onChange); + iElement.on('input', onChange); + iElement.on('paste', onChange); + + // model -> view + ctrl.$render = function() { + + if (!this.editor) { + // Hide placeholder when the model is not empty + if (!ctrl.$isEmpty(ctrl.$viewValue)) { + opts.placeholder = ''; + } + + this.editor = new MediumEditor(iElement, opts); + } + + iElement.html(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); + + // hide placeholder when view is not empty + if(!ctrl.$isEmpty(ctrl.$viewValue)) angular.element(iElement).removeClass('medium-editor-placeholder'); + }; + + } + }; + + }); diff --git a/vendor/assets/components/angular-medium-editor/dist/angular-medium-editor.min.js b/vendor/assets/components/angular-medium-editor/dist/angular-medium-editor.min.js new file mode 100644 index 000000000..3539a84bd --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/dist/angular-medium-editor.min.js @@ -0,0 +1,8 @@ +/** + * angular-medium-editor + * @version v0.1.1 - 2015-11-23 + * @link https://github.com/thijsw/angular-medium-editor + * @author Thijs Wijnmaalen + * @license MIT License, http://www.opensource.org/licenses/MIT + */ +"use strict";angular.module("angular-medium-editor",[]).directive("mediumEditor",function(){return{require:"ngModel",restrict:"AE",scope:{bindOptions:"="},link:function(a,b,c,d){angular.element(b).addClass("angular-medium-editor");var e={},f="",g=function(){c.options&&(e=a.$eval(c.options));var b={};void 0!==a.bindOptions&&(b=a.bindOptions),e=angular.extend(e,b)};g(),f=e.placeholder,a.$watch("bindOptions",function(){d.editor&&d.editor.destroy(),g(),d.$isEmpty(d.$viewValue)||(e.placeholder=""),d.editor=new MediumEditor(b,e)});var h=function(){a.$apply(function(){if("


    "===b.html()||""===b.html()){e.placeholder=f;{new MediumEditor(b,e)}}d.$setViewValue(b.html())})};b.on("blur",h),b.on("input",h),b.on("paste",h),d.$render=function(){this.editor||(d.$isEmpty(d.$viewValue)||(e.placeholder=""),this.editor=new MediumEditor(b,e)),b.html(d.$isEmpty(d.$viewValue)?"":d.$viewValue),d.$isEmpty(d.$viewValue)||angular.element(b).removeClass("medium-editor-placeholder")}}}}); \ No newline at end of file diff --git a/vendor/assets/components/angular-medium-editor/package.json b/vendor/assets/components/angular-medium-editor/package.json new file mode 100644 index 000000000..33a6a3714 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/package.json @@ -0,0 +1,40 @@ +{ + "name": "angular-medium-editor", + "version": "0.1.1", + "description": "AngularJS directive for Medium.com editor clone", + "keywords": [ + "angular", + "medium-editor" + ], + "homepage": "https://github.com/thijsw/angular-medium-editor", + "bugs": "https://github.com/thijsw/angular-medium-editor/issues", + "author": { + "name": "Thijs Wijnmaalen", + "email": "thijs@wijnmaalen.name", + "url": "https://twitter.com/thijsw" + }, + "repository": { + "type": "git", + "url": "https://github.com/thijsw/angular-medium-editor.git" + }, + "licenses": [ + { + "type": "MIT" + } + ], + "devDependencies": { + "connect-livereload": "^0.4.0", + "grunt": "^0.4.5", + "grunt-bump": "0.0.16", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-concat": "^0.5.0", + "grunt-contrib-connect": "^0.8.0", + "grunt-contrib-jshint": "^0.10.0", + "grunt-contrib-less": "^0.11.4", + "grunt-contrib-uglify": "^0.6.0", + "grunt-contrib-watch": "^0.6.1", + "grunt-ng-annotate": "0.4.0", + "grunt-open": "^0.2.3", + "load-grunt-tasks": "^2.0.0" + } +} diff --git a/vendor/assets/components/angular-medium-editor/src/angular-medium-editor.js b/vendor/assets/components/angular-medium-editor/src/angular-medium-editor.js new file mode 100644 index 000000000..b10bb54a4 --- /dev/null +++ b/vendor/assets/components/angular-medium-editor/src/angular-medium-editor.js @@ -0,0 +1,85 @@ +'use strict'; + +angular.module('angular-medium-editor', []) + + .directive('mediumEditor', function() { + + return { + require: 'ngModel', + restrict: 'AE', + scope: { bindOptions: '=' }, + link: function(scope, iElement, iAttrs, ctrl) { + + angular.element(iElement).addClass('angular-medium-editor'); + + // Parse options + var opts = {}, + placeholder = ''; + var prepOpts = function() { + if (iAttrs.options) { + opts = scope.$eval(iAttrs.options); + } + var bindOpts = {}; + if (scope.bindOptions !== undefined) { + bindOpts = scope.bindOptions; + } + opts = angular.extend(opts, bindOpts); + }; + prepOpts(); + placeholder = opts.placeholder; + scope.$watch('bindOptions', function() { + // in case options are provided after mediumEditor directive has been compiled and linked (and after $render function executed) + // we need to re-initialize + if (ctrl.editor) { + ctrl.editor.destroy(); + } + prepOpts(); + // Hide placeholder when the model is not empty + if (!ctrl.$isEmpty(ctrl.$viewValue)) { + opts.placeholder = ''; + } + ctrl.editor = new MediumEditor(iElement, opts); + }); + + var onChange = function() { + + scope.$apply(function() { + + // If user cleared the whole text, we have to reset the editor because MediumEditor + // lacks an API method to alter placeholder after initialization + if (iElement.html() === '


    ' || iElement.html() === '') { + opts.placeholder = placeholder; + var editor = new MediumEditor(iElement, opts); + } + + ctrl.$setViewValue(iElement.html()); + }); + }; + + // view -> model + iElement.on('blur', onChange); + iElement.on('input', onChange); + iElement.on('paste', onChange); + + // model -> view + ctrl.$render = function() { + + if (!this.editor) { + // Hide placeholder when the model is not empty + if (!ctrl.$isEmpty(ctrl.$viewValue)) { + opts.placeholder = ''; + } + + this.editor = new MediumEditor(iElement, opts); + } + + iElement.html(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); + + // hide placeholder when view is not empty + if(!ctrl.$isEmpty(ctrl.$viewValue)) angular.element(iElement).removeClass('medium-editor-placeholder'); + }; + + } + }; + + }); diff --git a/vendor/assets/components/angular-minicolors/.bower.json b/vendor/assets/components/angular-minicolors/.bower.json new file mode 100644 index 000000000..738623700 --- /dev/null +++ b/vendor/assets/components/angular-minicolors/.bower.json @@ -0,0 +1,39 @@ +{ + "name": "angular-minicolors", + "version": "0.0.5", + "homepage": "https://github.com/kaihenzler/angular-minicolors", + "authors": [ + "Kai Henzler " + ], + "description": "A wrapper around JQuery MiniColors by Cory LaViska", + "keywords": [ + "angular", + "minicolors", + "colorpicker", + "color-picker", + "color", + "picker" + ], + "main": "angular-minicolors.js", + "dependencies": { + "jquery-minicolors": "2.1.7" + }, + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "_release": "0.0.5", + "_resolution": { + "type": "version", + "tag": "0.0.5", + "commit": "377c8fb8976c0cee397d8f5c26f90e17c8399ce5" + }, + "_source": "git://github.com/kaihenzler/angular-minicolors.git", + "_target": "~0.0.5", + "_originalSource": "angular-minicolors", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-minicolors/LICENSE b/vendor/assets/components/angular-minicolors/LICENSE new file mode 100644 index 000000000..6e11c8340 --- /dev/null +++ b/vendor/assets/components/angular-minicolors/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Kai Henzler + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/assets/components/angular-minicolors/README.md b/vendor/assets/components/angular-minicolors/README.md new file mode 100644 index 000000000..f3a819ceb --- /dev/null +++ b/vendor/assets/components/angular-minicolors/README.md @@ -0,0 +1,95 @@ +angular-minicolors +================== + +## General + +My first try of wrtiting a wrapper-directive around JQuery MiniColors by [Cory LaViska ](https://github.com/claviska) [https://github.com/claviska/jquery-minicolors](https://github.com/claviska/jquery-minicolors) + +Works with Bootstrap 3 and works fine with mobile browsers such as Safari on iPad. + +##[DEMO and API](https://kaihenzler.github.io/angular-minicolors) + +## How To Install + +1. Install by typing `bower install angular-minicolors` consider using the `--save` option to save the dependency to your own bower.json file + +## How To Use + +1. Include the JQuery MiniColors Files from the bower_components folder (bower_components/jquery-minicolors/) in your project. +The files you need are: `jquery-minicolors.js` `jquery-minicolors.css` and `jquery-minicolors.png` and of course JQuery itself + +2. Add the dependency to your app definition `angular.module('myApp', ['minicolors'])` + +3. Append `minicolors` attribute to any input-field. If you want to pass in a settings object, do it like this: `minicolors="MySettingsObject"` + +angular-minicolors is planned to be API compatible with: [http://labs.abeautifulsite.net/jquery-minicolors/](http://labs.abeautifulsite.net/jquery-minicolors/) + +keep in mind, that this is my first public angular-directive and it is by far not finished. + +## default config + +the default config is as follows: + +```js + theme: 'bootstrap', + position: 'top left', + defaultValue: '', + animationSpeed: 50, + animationEasing: 'swing', + change: null, + changeDelay: 0, + control: 'hue', + hide: null, + hideSpeed: 100, + inline: false, + letterCase: 'lowercase', + opacity: false, + show: null, + showSpeed: 100 +``` + + +## app-wide config + +a Provider is now exposed and you can edit the global config like this: + +```js +angular.module('my-app').config(function (minicolorsProvider) { + angular.extend(minicolorsProvider.defaults, { + control: 'hue', + position: 'top left' + }); +}); +``` + +## TODO + +- wrap the original events in angular events +- add protection against false color values + +## Found an issue? + +Please report the issue and feel free to submit a pull request + +## Copyright and license + +The MIT License (MIT) + +Copyright (c) 2013 Kai Henzler + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/assets/components/angular-minicolors/angular-minicolors.js b/vendor/assets/components/angular-minicolors/angular-minicolors.js new file mode 100644 index 000000000..befa3b1f0 --- /dev/null +++ b/vendor/assets/components/angular-minicolors/angular-minicolors.js @@ -0,0 +1,94 @@ +'use strict'; + +angular.module('minicolors', []); + +angular.module('minicolors').provider('minicolors', function () { + this.defaults = { + theme: 'bootstrap', + position: 'top left', + defaultValue: '', + animationSpeed: 50, + animationEasing: 'swing', + change: null, + changeDelay: 0, + control: 'hue', + hide: null, + hideSpeed: 100, + inline: false, + letterCase: 'lowercase', + opacity: false, + show: null, + showSpeed: 100 + }; + + this.$get = function() { + return this; + }; + +}); + +angular.module('minicolors').directive('minicolors', ['minicolors', '$timeout', function (minicolors, $timeout) { + return { + require: '?ngModel', + restrict: 'A', + priority: 1, //since we bind on an input element, we have to set a higher priority than angular-default input + link: function(scope, element, attrs, ngModel) { + + var inititalized = false; + + //gets the settings object + var getSettings = function () { + var config = angular.extend({}, minicolors.defaults, scope.$eval(attrs.minicolors)); + return config; + }; + + //what to do if the value changed + ngModel.$render = function () { + + //we are in digest or apply, and therefore call a timeout function + $timeout(function() { + var color = ngModel.$viewValue; + element.minicolors('value', color); + }, 0, false); + }; + + //init method + var initMinicolors = function () { + + if(!ngModel) { + return; + } + var settings = getSettings(); + settings.change = function (hex) { + scope.$apply(function () { + ngModel.$setViewValue(hex); + }); + }; + + // If we don't destroy the old one it doesn't update properly when the config changes + element.minicolors('destroy'); + + // Create the new minicolors widget + element.minicolors(settings); + + // are we inititalized yet ? + //needs to be wrapped in $timeout, to prevent $apply / $digest errors + //$scope.$apply will be called by $timeout, so we don't have to handle that case + if (!inititalized) { + $timeout(function() { + var color = ngModel.$viewValue; + element.minicolors('value', color); + }, 0); + inititalized = true; + return; + } + }; + + initMinicolors(); + //initital call + + // Watch for changes to the directives options and then call init method again + scope.$watch(getSettings, initMinicolors, true); + } + }; +}]); diff --git a/vendor/assets/components/angular-minicolors/bower.json b/vendor/assets/components/angular-minicolors/bower.json new file mode 100644 index 000000000..8d732f667 --- /dev/null +++ b/vendor/assets/components/angular-minicolors/bower.json @@ -0,0 +1,29 @@ +{ + "name": "angular-minicolors", + "version": "0.0.5", + "homepage": "https://github.com/kaihenzler/angular-minicolors", + "authors": [ + "Kai Henzler " + ], + "description": "A wrapper around JQuery MiniColors by Cory LaViska", + "keywords": [ + "angular", + "minicolors", + "colorpicker", + "color-picker", + "color", + "picker" + ], + "main": "angular-minicolors.js", + "dependencies": { + "jquery-minicolors": "2.1.7" + }, + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/vendor/assets/components/angular-moment/.bower.json b/vendor/assets/components/angular-moment/.bower.json index 9713e3a53..854f9a0fd 100644 --- a/vendor/assets/components/angular-moment/.bower.json +++ b/vendor/assets/components/angular-moment/.bower.json @@ -1,6 +1,5 @@ { "name": "angular-moment", - "version": "0.10.0", "description": "Moment.JS directives & filters for AngularJS (timeago alternative)", "author": "Uri Shaked", "license": "MIT", @@ -12,20 +11,21 @@ "moment": ">=2.8.0 <2.11.0" }, "devDependencies": { - "angular-mocks": "1.3.x", - "moment-timezone": "0.3.1" + "angular-mocks": "1.4.x", + "moment-timezone": "0.4.0" }, "repository": { "type": "git", "url": "git://github.com/urish/angular-moment.git" }, - "_release": "0.10.0", + "version": "0.10.3", + "_release": "0.10.3", "_resolution": { "type": "version", - "tag": "0.10.0", - "commit": "96d89aab944112e51177e06d4cd82a8b9306e656" + "tag": "0.10.3", + "commit": "07d373b7a2fd17ff25ab927ff7fcb0b53b2d13fd" }, "_source": "git://github.com/urish/angular-moment.git", - "_target": ">=0.7.0", + "_target": ">=0.10.3", "_originalSource": "angular-moment" } \ No newline at end of file diff --git a/vendor/assets/components/angular-moment/.gitignore b/vendor/assets/components/angular-moment/.gitignore index fccb56b32..d6e547b43 100644 --- a/vendor/assets/components/angular-moment/.gitignore +++ b/vendor/assets/components/angular-moment/.gitignore @@ -1,4 +1,5 @@ /.idea /bower_components /node_modules -/coverage \ No newline at end of file +/coverage +npm-debug.log \ No newline at end of file diff --git a/vendor/assets/components/angular-moment/.travis.yml b/vendor/assets/components/angular-moment/.travis.yml index c85bdee3c..5aa13e442 100644 --- a/vendor/assets/components/angular-moment/.travis.yml +++ b/vendor/assets/components/angular-moment/.travis.yml @@ -1,4 +1,5 @@ language: node_js +sudo: false node_js: - "0.10" before_script: diff --git a/vendor/assets/components/angular-moment/CHANGELOG.md b/vendor/assets/components/angular-moment/CHANGELOG.md index e55ec3a53..a9bdab648 100644 --- a/vendor/assets/components/angular-moment/CHANGELOG.md +++ b/vendor/assets/components/angular-moment/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.10.3 - 2015-09-05 +- Allow `amDateFormat` to work with custom formatted input date strings ([#162](https://github.com/urish/angular-moment/pull/162), contributed by [jblashka](https://github.com/jblashka)) +- `amAdd`, `amSubtract` - add/subtract a value from a given date ([#171](https://github.com/urish/angular-moment/pull/171), contributed by [nicholasruggeri](https://github.com/nicholasruggeri)) +- Bugfix: Timezones with a 'Z' somewhere in them all become UTC ([#168](https://github.com/urish/angular-moment/issues/168)). + +## 0.10.2 - 2015-07-28 +- Look for `moment` on the `global` object ([#133](https://github.com/urish/angular-moment/pull/133), contributed by [kitbrennan90](https://github.com/kitbrennan90)) +- Add support to use UTC offset timezones in addition to named timezones ([#151](https://github.com/urish/angular-moment/pull/151), contributed by [DiegoZoracKy](https://github.com/DiegoZoracKy)) +- Add timezone parameter for amCalendar filter ([#152](https://github.com/urish/angular-moment/pull/152), contributed by [DiegoZoracKy](https://github.com/DiegoZoracKy)) +- Add `from` parameter to the `amTimeAgo` filter ([#146](https://github.com/urish/angular-moment/pull/146), contributed by [pipo02mix](https://github.com/pipo02mix)) + +## 0.10.1 - 2015-05-01 +- Fix broken SystemJS/JSPM support (see [#104](https://github.com/urish/angular-moment/issues/104)) + ## 0.10.0 - 2015-04-10 - Breaking change: removed one-time binding for `am-time-ago` in favor of AngularJS 1.3's one time binding ([#122](https://github.com/urish/angular-moment/issues/122)) - Remove support for AngularJS 1.0.x and 1.1.x. diff --git a/vendor/assets/components/angular-moment/README.md b/vendor/assets/components/angular-moment/README.md index 1618cde3e..284690da6 100644 --- a/vendor/assets/components/angular-moment/README.md +++ b/vendor/assets/components/angular-moment/README.md @@ -15,7 +15,7 @@ You can choose your preferred method of installation: * Through bower: `bower install angular-moment --save` * Through npm: `npm install angular-moment --save` * Through NuGet: `Install-Package angular-moment` -* From a CDN: [jsDelivr](https://cdn.jsdelivr.net/angular.moment/0.10.0/angular-moment.min.js) or [CDNJS](https://cdnjs.cloudflare.com/ajax/libs/angular-moment/0.10.0/angular-moment.min.js) +* From a CDN: [jsDelivr](https://cdn.jsdelivr.net/angular.moment/0.10.3/angular-moment.min.js) or [CDNJS](https://cdnjs.cloudflare.com/ajax/libs/angular-moment/0.10.3/angular-moment.min.js) * Download from github: [angular-moment.min.js](https://raw.github.com/urish/angular-moment/master/angular-moment.min.js) Usage @@ -119,6 +119,44 @@ This snippet will return the number of days between the current date and the dat For more information about Moment.JS difference function, see the [docs for the diff() function](http://momentjs.com/docs/#/displaying/difference/). +### amDurationFormat filter + +Formats a duration (such as 5 days) in a human readable format. See [Moment.JS documentation](http://momentjs.com/docs/#/durations/creating/) for a list of supported duration formats, and [`humanize() documentation`](http://momentjs.com/docs/#/durations/humanize/) for explanation about the formatting algorithm. + +Example: + +```html +Message age: {{message.ageInMinutes | amDurationFormat : 'minute' }} +``` + +Will display the age of the message (e.g. 10 minutes, 1 hour, 2 days, etc). + +### amSubtract filter + +Subtract values (hours, minutes, seconds ...) from a specified date. + +See [Moment.JS documentation](http://momentjs.com/docs/#/durations/creating/) for a list of supported duration formats. + +Example: + +```html +Start time: {{day.start | amSubtract : '1' : 'hours' | amDateFormat : 'hh'}} : {{day.start | amSubtract : '30' : 'minutes' | amDateFormat : 'mm'}} + +``` + +### amAdd filter + +Add values (hours, minutes, seconds ...) to a specified date. + +See [Moment.JS documentation](http://momentjs.com/docs/#/durations/creating/) for a list of supported duration formats. + +Example: + +```html +Start time: {{day.start | amAdd : '1' : 'hours' | amDateFormat : 'hh'}} : {{day.start | amAdd : '30' : 'minutes' | amDateFormat : 'mm'}} + +``` + ### Time zone support The `amDateFormat` and `amCalendar` filters can be configured to display dates aligned diff --git a/vendor/assets/components/angular-moment/angular-moment.js b/vendor/assets/components/angular-moment/angular-moment.js index 5d163b03f..100b88da7 100644 --- a/vendor/assets/components/angular-moment/angular-moment.js +++ b/vendor/assets/components/angular-moment/angular-moment.js @@ -1,9 +1,7 @@ -/* angular-moment.js / v0.10.0 / (c) 2013, 2014, 2015 Uri Shaked / MIT Licence */ +/* angular-moment.js / v0.10.3 / (c) 2013, 2014, 2015 Uri Shaked / MIT Licence */ -'format global'; +'format amd'; /* global define */ -'deps angular'; -'deps moment'; (function () { 'use strict'; @@ -175,11 +173,14 @@ var localDate = new Date().getTime(); var preprocess = angularMomentConfig.preprocess; var modelName = attr.amTimeAgo; + var currentFrom; var isTimeElement = ('TIME' === element[0].nodeName.toUpperCase()); function getNow() { var now; - if (amTimeAgoConfig.serverTime) { + if (currentFrom) { + now = currentFrom; + } else if (amTimeAgoConfig.serverTime) { var localNow = new Date().getTime(); var nowMillis = localNow - localDate + amTimeAgoConfig.serverTime; now = moment(nowMillis); @@ -258,6 +259,17 @@ updateMoment(); }); + if (angular.isDefined(attr.amFrom)) { + scope.$watch(attr.amFrom, function (value) { + if ((typeof value === 'undefined') || (value === null) || (value === '')) { + currentFrom = null; + } else { + currentFrom = moment(value); + } + updateMoment(); + }); + } + if (angular.isDefined(attr.amWithoutSuffix)) { scope.$watch(attr.amWithoutSuffix, function (value) { if (typeof value === 'boolean') { @@ -396,24 +408,29 @@ * @methodOf angularMoment.service.amMoment * * @description - * Apply a timezone onto a given moment object - if moment-timezone.js is included - * Otherwise, it'll not apply any timezone shift. + * Apply a timezone onto a given moment object. It can be a named timezone (e.g. 'America/Phoenix') or an offset from UTC (e.g. '+0300') + * moment-timezone.js is needed when a named timezone is used, otherwise, it'll not apply any timezone shift. * * @param {Moment} aMoment a moment() instance to apply the timezone shift to * @param {string=} timezone The timezone to apply. If none given, will apply the timezone - * configured in angularMomentConfig.timezone. + * configured in angularMomentConfig.timezone. It can be a named timezone (e.g. 'America/Phoenix') or an offset from UTC (e.g. '+0300') * * @returns {Moment} The given moment with the timezone shift applied */ this.applyTimezone = function (aMoment, timezone) { timezone = timezone || angularMomentConfig.timezone; - if (aMoment && timezone) { - if (aMoment.tz) { - aMoment = aMoment.tz(timezone); - } else { - $log.warn('angular-moment: timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?'); - } + if (!timezone) { + return aMoment; } + + if (timezone.match(/^Z|[+-]\d\d:?\d\d$/i)) { + aMoment = aMoment.utcOffset(timezone); + } else if (aMoment.tz) { + aMoment = aMoment.tz(timezone); + } else { + $log.warn('angular-moment: named timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?'); + } + return aMoment; }; }]) @@ -424,7 +441,7 @@ * @module angularMoment */ .filter('amCalendar', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) { - function amCalendarFilter(value, preprocess) { + function amCalendarFilter(value, preprocess, timezone) { if (typeof value === 'undefined' || value === null) { return ''; } @@ -435,7 +452,7 @@ return ''; } - return amMoment.applyTimezone(date).calendar(); + return amMoment.applyTimezone(date, timezone).calendar(); } // Since AngularJS 1.3, filters have to explicitly define being stateful @@ -488,12 +505,13 @@ * @function */ .filter('amDateFormat', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) { - function amDateFormatFilter(value, format, preprocess, timezone) { + function amDateFormatFilter(value, format, preprocess, timezone, inputFormat) { + var currentFormat = inputFormat || angularMomentConfig.format; if (typeof value === 'undefined' || value === null) { return ''; } - value = amMoment.preprocessDate(value, preprocess); + value = amMoment.preprocessDate(value, preprocess, currentFormat); var date = moment(value); if (!date.isValid()) { return ''; @@ -534,23 +552,72 @@ * @function */ .filter('amTimeAgo', ['moment', 'amMoment', 'angularMomentConfig', function (moment, amMoment, angularMomentConfig) { - function amTimeAgoFilter(value, preprocess, suffix) { + function amTimeAgoFilter(value, preprocess, suffix, from) { + var date, dateFrom; + if (typeof value === 'undefined' || value === null) { return ''; } value = amMoment.preprocessDate(value, preprocess); - var date = moment(value); + date = moment(value); if (!date.isValid()) { return ''; } + dateFrom = moment(from); + if (typeof from !== 'undefined' && dateFrom.isValid()) { + return amMoment.applyTimezone(date).from(dateFrom, suffix); + } + return amMoment.applyTimezone(date).fromNow(suffix); } amTimeAgoFilter.$stateful = angularMomentConfig.statefulFilters; return amTimeAgoFilter; + }]) + + /** + * @ngdoc filter + * @name angularMoment.filter:amSubtract + * @module angularMoment + * @function + */ + .filter('amSubtract', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) { + function amSubtractFilter(value, amount, type) { + + if (typeof value === 'undefined' || value === null) { + return ''; + } + + return moment(value).subtract(parseInt(amount, 10), type); + } + + amSubtractFilter.$stateful = angularMomentConfig.statefulFilters; + + return amSubtractFilter; + }]) + + /** + * @ngdoc filter + * @name angularMoment.filter:amAdd + * @module angularMoment + * @function + */ + .filter('amAdd', ['moment', 'angularMomentConfig', function (moment, angularMomentConfig) { + function amAddFilter(value, amount, type) { + + if (typeof value === 'undefined' || value === null) { + return ''; + } + + return moment(value).add(parseInt(amount, 10), type); + } + + amAddFilter.$stateful = angularMomentConfig.statefulFilters; + + return amAddFilter; }]); } @@ -560,6 +627,6 @@ angularMoment(angular, require('moment')); module.exports = 'angularMoment'; } else { - angularMoment(angular, window.moment); + angularMoment(angular, (typeof global !== 'undefined' ? global : window).moment); } })(); diff --git a/vendor/assets/components/angular-moment/angular-moment.min.js b/vendor/assets/components/angular-moment/angular-moment.min.js index 2c4614848..c541d91ef 100644 --- a/vendor/assets/components/angular-moment/angular-moment.min.js +++ b/vendor/assets/components/angular-moment/angular-moment.min.js @@ -1,2 +1,2 @@ -"format global";"deps angular";"deps moment";!function(){"use strict";function a(a,b){return a.module("angularMoment",[]).constant("angularMomentConfig",{preprocess:null,timezone:"",format:null,statefulFilters:!0}).constant("moment",b).constant("amTimeAgoConfig",{withoutSuffix:!1,serverTime:null,titleFormat:null,fullDateThreshold:null,fullDateFormat:null}).directive("amTimeAgo",["$window","moment","amMoment","amTimeAgoConfig","angularMomentConfig",function(b,c,d,e,f){return function(g,h,i){function j(){var a;if(e.serverTime){var b=(new Date).getTime(),d=b-v+e.serverTime;a=c(d)}else a=c();return a}function k(){p&&(b.clearTimeout(p),p=null)}function l(a){var c=j().diff(a,"day"),d=t&&c>=t;if(h.text(d?a.format(u):a.from(j(),r)),s&&!h.attr("title")&&h.attr("title",a.local().format(s)),!d){var e=Math.abs(j().diff(a,"minute")),f=3600;1>e?f=1:60>e?f=30:180>e&&(f=300),p=b.setTimeout(function(){l(a)},1e3*f)}}function m(a){y&&h.attr("datetime",a)}function n(){if(k(),o){var a=d.preprocessDate(o,w,q);l(a),m(a.toISOString())}}var o,p=null,q=f.format,r=e.withoutSuffix,s=e.titleFormat,t=e.fullDateThreshold,u=e.fullDateFormat,v=(new Date).getTime(),w=f.preprocess,x=i.amTimeAgo,y="TIME"===h[0].nodeName.toUpperCase();g.$watch(x,function(a){return"undefined"==typeof a||null===a||""===a?(k(),void(o&&(h.text(""),m(""),o=null))):(o=a,void n())}),a.isDefined(i.amWithoutSuffix)&&g.$watch(i.amWithoutSuffix,function(a){"boolean"==typeof a?(r=a,n()):r=e.withoutSuffix}),i.$observe("amFormat",function(a){"undefined"!=typeof a&&(q=a,n())}),i.$observe("amPreprocess",function(a){w=a,n()}),i.$observe("amFullDateThreshold",function(a){t=a,n()}),i.$observe("amFullDateFormat",function(a){u=a,n()}),g.$on("$destroy",function(){k()}),g.$on("amMoment:localeChanged",function(){n()})}}]).service("amMoment",["moment","$rootScope","$log","angularMomentConfig",function(b,c,d,e){this.preprocessors={utc:b.utc,unix:b.unix},this.changeLocale=function(d,e){var f=b.locale(d,e);return a.isDefined(d)&&c.$broadcast("amMoment:localeChanged"),f},this.changeTimezone=function(a){e.timezone=a,c.$broadcast("amMoment:timezoneChanged")},this.preprocessDate=function(c,f,g){return a.isUndefined(f)&&(f=e.preprocess),this.preprocessors[f]?this.preprocessors[f](c,g):(f&&d.warn("angular-moment: Ignoring unsupported value for preprocess: "+f),!isNaN(parseFloat(c))&&isFinite(c)?b(parseInt(c,10)):b(c,g))},this.applyTimezone=function(a){var b=e.timezone;return a&&b&&(a.tz?a=a.tz(b):d.warn("angular-moment: timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?")),a}}]).filter("amCalendar",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,d);var e=a(c);return e.isValid()?b.applyTimezone(e).calendar():""}return d.$stateful=c.statefulFilters,d}]).filter("amDifference",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d,e,f,g,h){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,g);var i=a(c);if(!i.isValid())return"";var j;if("undefined"==typeof d||null===d)j=a();else if(d=b.preprocessDate(d,h),j=a(d),!j.isValid())return"";return b.applyTimezone(i).diff(b.applyTimezone(j),e,f)}return d.$stateful=c.statefulFilters,d}]).filter("amDateFormat",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d,e){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,e);var f=a(c);return f.isValid()?b.applyTimezone(f).format(d):""}return d.$stateful=c.statefulFilters,d}]).filter("amDurationFormat",["moment","angularMomentConfig",function(a,b){function c(b,c,d){return"undefined"==typeof b||null===b?"":a.duration(b,c).humanize(d)}return c.$stateful=b.statefulFilters,c}]).filter("amTimeAgo",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d,e){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,d);var f=a(c);return f.isValid()?b.applyTimezone(f).fromNow(e):""}return d.$stateful=c.statefulFilters,d}])}"function"==typeof define&&define.amd?define(["angular","moment"],a):"undefined"!=typeof module&&module&&module.exports?(a(angular,require("moment")),module.exports="angularMoment"):a(angular,window.moment)}(); +"format amd";!function(){"use strict";function a(a,b){return a.module("angularMoment",[]).constant("angularMomentConfig",{preprocess:null,timezone:"",format:null,statefulFilters:!0}).constant("moment",b).constant("amTimeAgoConfig",{withoutSuffix:!1,serverTime:null,titleFormat:null,fullDateThreshold:null,fullDateFormat:null}).directive("amTimeAgo",["$window","moment","amMoment","amTimeAgoConfig","angularMomentConfig",function(b,c,d,e,f){return function(g,h,i){function j(){var a;if(p)a=p;else if(e.serverTime){var b=(new Date).getTime(),d=b-w+e.serverTime;a=c(d)}else a=c();return a}function k(){q&&(b.clearTimeout(q),q=null)}function l(a){var c=j().diff(a,"day"),d=u&&c>=u;if(h.text(d?a.format(v):a.from(j(),s)),t&&!h.attr("title")&&h.attr("title",a.local().format(t)),!d){var e=Math.abs(j().diff(a,"minute")),f=3600;1>e?f=1:60>e?f=30:180>e&&(f=300),q=b.setTimeout(function(){l(a)},1e3*f)}}function m(a){z&&h.attr("datetime",a)}function n(){if(k(),o){var a=d.preprocessDate(o,x,r);l(a),m(a.toISOString())}}var o,p,q=null,r=f.format,s=e.withoutSuffix,t=e.titleFormat,u=e.fullDateThreshold,v=e.fullDateFormat,w=(new Date).getTime(),x=f.preprocess,y=i.amTimeAgo,z="TIME"===h[0].nodeName.toUpperCase();g.$watch(y,function(a){return"undefined"==typeof a||null===a||""===a?(k(),void(o&&(h.text(""),m(""),o=null))):(o=a,void n())}),a.isDefined(i.amFrom)&&g.$watch(i.amFrom,function(a){p="undefined"==typeof a||null===a||""===a?null:c(a),n()}),a.isDefined(i.amWithoutSuffix)&&g.$watch(i.amWithoutSuffix,function(a){"boolean"==typeof a?(s=a,n()):s=e.withoutSuffix}),i.$observe("amFormat",function(a){"undefined"!=typeof a&&(r=a,n())}),i.$observe("amPreprocess",function(a){x=a,n()}),i.$observe("amFullDateThreshold",function(a){u=a,n()}),i.$observe("amFullDateFormat",function(a){v=a,n()}),g.$on("$destroy",function(){k()}),g.$on("amMoment:localeChanged",function(){n()})}}]).service("amMoment",["moment","$rootScope","$log","angularMomentConfig",function(b,c,d,e){this.preprocessors={utc:b.utc,unix:b.unix},this.changeLocale=function(d,e){var f=b.locale(d,e);return a.isDefined(d)&&c.$broadcast("amMoment:localeChanged"),f},this.changeTimezone=function(a){e.timezone=a,c.$broadcast("amMoment:timezoneChanged")},this.preprocessDate=function(c,f,g){return a.isUndefined(f)&&(f=e.preprocess),this.preprocessors[f]?this.preprocessors[f](c,g):(f&&d.warn("angular-moment: Ignoring unsupported value for preprocess: "+f),!isNaN(parseFloat(c))&&isFinite(c)?b(parseInt(c,10)):b(c,g))},this.applyTimezone=function(a,b){return(b=b||e.timezone)?(b.match(/^Z|[+-]\d\d:?\d\d$/i)?a=a.utcOffset(b):a.tz?a=a.tz(b):d.warn("angular-moment: named timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?"),a):a}}]).filter("amCalendar",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d,e){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,d);var f=a(c);return f.isValid()?b.applyTimezone(f,e).calendar():""}return d.$stateful=c.statefulFilters,d}]).filter("amDifference",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d,e,f,g,h){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,g);var i=a(c);if(!i.isValid())return"";var j;if("undefined"==typeof d||null===d)j=a();else if(d=b.preprocessDate(d,h),j=a(d),!j.isValid())return"";return b.applyTimezone(i).diff(b.applyTimezone(j),e,f)}return d.$stateful=c.statefulFilters,d}]).filter("amDateFormat",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(d,e,f,g,h){var i=h||c.format;if("undefined"==typeof d||null===d)return"";d=b.preprocessDate(d,f,i);var j=a(d);return j.isValid()?b.applyTimezone(j,g).format(e):""}return d.$stateful=c.statefulFilters,d}]).filter("amDurationFormat",["moment","angularMomentConfig",function(a,b){function c(b,c,d){return"undefined"==typeof b||null===b?"":a.duration(b,c).humanize(d)}return c.$stateful=b.statefulFilters,c}]).filter("amTimeAgo",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d,e,f){var g,h;return"undefined"==typeof c||null===c?"":(c=b.preprocessDate(c,d),g=a(c),g.isValid()?(h=a(f),"undefined"!=typeof f&&h.isValid()?b.applyTimezone(g).from(h,e):b.applyTimezone(g).fromNow(e)):"")}return d.$stateful=c.statefulFilters,d}]).filter("amSubtract",["moment","angularMomentConfig",function(a,b){function c(b,c,d){return"undefined"==typeof b||null===b?"":a(b).subtract(parseInt(c,10),d)}return c.$stateful=b.statefulFilters,c}]).filter("amAdd",["moment","angularMomentConfig",function(a,b){function c(b,c,d){return"undefined"==typeof b||null===b?"":a(b).add(parseInt(c,10),d)}return c.$stateful=b.statefulFilters,c}])}"function"==typeof define&&define.amd?define(["angular","moment"],a):"undefined"!=typeof module&&module&&module.exports?(a(angular,require("moment")),module.exports="angularMoment"):a(angular,("undefined"!=typeof global?global:window).moment)}(); //# sourceMappingURL=angular-moment.min.js.map \ No newline at end of file diff --git a/vendor/assets/components/angular-moment/angular-moment.min.js.map b/vendor/assets/components/angular-moment/angular-moment.min.js.map index 35d812929..c61f98625 100644 --- a/vendor/assets/components/angular-moment/angular-moment.min.js.map +++ b/vendor/assets/components/angular-moment/angular-moment.min.js.map @@ -1 +1 @@ -{"version":3,"file":"angular-moment.min.js","sources":["angular-moment.js"],"names":["angularMoment","angular","moment","module","constant","preprocess","timezone","format","statefulFilters","withoutSuffix","serverTime","titleFormat","fullDateThreshold","fullDateFormat","directive","$window","amMoment","amTimeAgoConfig","angularMomentConfig","scope","element","attr","getNow","now","localNow","Date","getTime","nowMillis","localDate","cancelTimer","activeTimeout","clearTimeout","updateTime","momentInstance","daysAgo","diff","showFullDate","text","from","local","howOld","Math","abs","secondsUntilUpdate","setTimeout","updateDateTimeAttr","value","isTimeElement","updateMoment","currentValue","momentValue","preprocessDate","currentFormat","toISOString","modelName","amTimeAgo","nodeName","toUpperCase","$watch","isDefined","amWithoutSuffix","$observe","newValue","$on","service","$rootScope","$log","this","preprocessors","utc","unix","changeLocale","locale","customization","result","$broadcast","changeTimezone","isUndefined","warn","isNaN","parseFloat","isFinite","parseInt","applyTimezone","aMoment","tz","filter","amCalendarFilter","date","isValid","calendar","$stateful","amDifferenceFilter","otherValue","unit","usePrecision","preprocessValue","preprocessOtherValue","date2","amDateFormatFilter","amDurationFormatFilter","suffix","duration","humanize","amTimeAgoFilter","fromNow","define","amd","exports","require","window"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"angular-moment.min.js","sources":["angular-moment.js"],"names":["angularMoment","angular","moment","module","constant","preprocess","timezone","format","statefulFilters","withoutSuffix","serverTime","titleFormat","fullDateThreshold","fullDateFormat","directive","$window","amMoment","amTimeAgoConfig","angularMomentConfig","scope","element","attr","getNow","now","currentFrom","localNow","Date","getTime","nowMillis","localDate","cancelTimer","activeTimeout","clearTimeout","updateTime","momentInstance","daysAgo","diff","showFullDate","text","from","local","howOld","Math","abs","secondsUntilUpdate","setTimeout","updateDateTimeAttr","value","isTimeElement","updateMoment","currentValue","momentValue","preprocessDate","currentFormat","toISOString","modelName","amTimeAgo","nodeName","toUpperCase","$watch","isDefined","amFrom","amWithoutSuffix","$observe","newValue","$on","service","$rootScope","$log","this","preprocessors","utc","unix","changeLocale","locale","customization","result","$broadcast","changeTimezone","isUndefined","warn","isNaN","parseFloat","isFinite","parseInt","applyTimezone","aMoment","match","utcOffset","tz","filter","amCalendarFilter","date","isValid","calendar","$stateful","amDifferenceFilter","otherValue","unit","usePrecision","preprocessValue","preprocessOtherValue","date2","amDateFormatFilter","inputFormat","amDurationFormatFilter","suffix","duration","humanize","amTimeAgoFilter","dateFrom","fromNow","amSubtractFilter","amount","type","subtract","amAddFilter","add","define","amd","exports","require","global","window"],"mappings":"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"} \ No newline at end of file diff --git a/vendor/assets/components/angular-moment/angular-moment.nuspec b/vendor/assets/components/angular-moment/angular-moment.nuspec index 695a0ae85..fe96441c1 100644 --- a/vendor/assets/components/angular-moment/angular-moment.nuspec +++ b/vendor/assets/components/angular-moment/angular-moment.nuspec @@ -2,7 +2,7 @@ angular-moment - 0.10.0 + 0.10.3 Angular Moment urish https://github.com/urish/angular-moment @@ -12,7 +12,7 @@ - + AngularJS MomentJS Moment diff --git a/vendor/assets/components/angular-moment/bower.json b/vendor/assets/components/angular-moment/bower.json index 2d365d06d..ee728a1a1 100644 --- a/vendor/assets/components/angular-moment/bower.json +++ b/vendor/assets/components/angular-moment/bower.json @@ -1,6 +1,5 @@ { "name": "angular-moment", - "version": "0.10.0", "description": "Moment.JS directives & filters for AngularJS (timeago alternative)", "author": "Uri Shaked", "license": "MIT", @@ -13,8 +12,8 @@ "moment": ">=2.8.0 <2.11.0" }, "devDependencies": { - "angular-mocks": "1.3.x", - "moment-timezone": "0.3.1" + "angular-mocks": "1.4.x", + "moment-timezone": "0.4.0" }, "repository": { "type": "git", diff --git a/vendor/assets/components/angular-moment/package.json b/vendor/assets/components/angular-moment/package.json index 61f8d1f1a..804a3d114 100644 --- a/vendor/assets/components/angular-moment/package.json +++ b/vendor/assets/components/angular-moment/package.json @@ -1,6 +1,6 @@ { "name": "angular-moment", - "version": "0.10.0", + "version": "0.10.3", "main": "angular-moment.js", "repository": { "type": "git", @@ -18,6 +18,7 @@ "grunt-contrib-uglify": "0.8.1", "grunt-karma": "~0.10.1", "grunt-ngdocs": "^0.2.7", + "jasmine-core": "2.2.0", "karma": "~0.12.0", "karma-coverage": "~0.2.0", "karma-jasmine": "~0.3.5", diff --git a/vendor/assets/components/angular-moment/tests.js b/vendor/assets/components/angular-moment/tests.js index 044c2f90f..241352282 100644 --- a/vendor/assets/components/angular-moment/tests.js +++ b/vendor/assets/components/angular-moment/tests.js @@ -28,6 +28,7 @@ describe('module angularMoment', function () { (moment.locale || moment.lang)('en'); // Add a sample timezones for tests moment.tz.add('UTC|UTC|0|0|'); + moment.tz.add('Europe/Zurich|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-19Lc0 11A0 1o00 11A0 1xG10 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00'); moment.tz.add('Pacific/Tahiti|LMT TAHT|9W.g a0|01|-2joe1.I'); })); @@ -277,7 +278,7 @@ describe('module angularMoment', function () { var element = angular.element(''); element = $compile(element)($rootScope); $rootScope.$digest(); - expect(element.text()).toMatch(/^2012-06-05T00:00:00\+\d\d:\d\d$/); + expect(element.text()).toMatch(/^2012-06-05T00:00:00[\+\-]\d\d:\d\d$/); }); it('should display full time using the given format', function () { @@ -305,13 +306,24 @@ describe('module angularMoment', function () { it('should support setting the full date format through attribute', function () { amTimeAgoConfig.fullDateThreshold = 7; - $rootScope.testDate = new Date(2013, 11, 15); + $rootScope.testDate = new Date(2013, 11, 15); var element = angular.element(''); element = $compile(element)($rootScope); $rootScope.$digest(); expect(element.text()).toBe('2013-12-15'); }); }); + + describe('am-from attribute', function () { + it('should make the calculations from the am-from given', function () { + $rootScope.from = new Date(2015, 6, 11); + $rootScope.testDate = new Date(2015, 6, 12); + var element = angular.element(''); + element = $compile(element)($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('in a day'); + }); + }); }); describe('am-without-suffix attribute', function () { @@ -357,7 +369,8 @@ describe('module angularMoment', function () { describe('am-format attribute', function () { it('should support custom date format', function () { var today = new Date(); - $rootScope.testDate = today.getFullYear() + '#' + today.getDate() + '#' + today.getMonth(); + var date = Math.min(today.getDate(), 28); + $rootScope.testDate = today.getFullYear() + '#' + date + '#' + today.getMonth(); var element = angular.element(''); element = $compile(element)($rootScope); $rootScope.$digest(); @@ -366,7 +379,8 @@ describe('module angularMoment', function () { it('should support angular expressions in date format', function () { var today = new Date(); - $rootScope.testDate = today.getMonth() + '@' + today.getFullYear() + '@' + today.getDate(); + var date = Math.min(today.getDate(), 28); + $rootScope.testDate = today.getMonth() + '@' + today.getFullYear() + '@' + date; var element = angular.element(''); element = $compile(element)($rootScope); $rootScope.$digest(); @@ -380,7 +394,8 @@ describe('module angularMoment', function () { it('should be used when no `am-format` attribute is found', function () { angularMomentConfig.format = 'MM@YYYY@DD'; var today = new Date(); - $rootScope.testDate = today.getMonth() + '@' + today.getFullYear() + '@' + today.getDate(); + var date = Math.min(today.getDate(), 28); + $rootScope.testDate = today.getMonth() + '@' + today.getFullYear() + '@' + date; var element = angular.element(''); element = $compile(element)($rootScope); $rootScope.$digest(); @@ -390,7 +405,8 @@ describe('module angularMoment', function () { it('should be overridable by `am-format` attribute', function () { angularMomentConfig.format = 'YYYY@MM@@DD'; var today = new Date(); - $rootScope.testDate = today.getMonth() + '@' + today.getFullYear() + '@' + today.getDate(); + var date = Math.min(today.getDate(), 28); + $rootScope.testDate = today.getMonth() + '@' + today.getFullYear() + '@' + date; var element = angular.element(''); element = $compile(element)($rootScope); $rootScope.$digest(); @@ -440,6 +456,25 @@ describe('module angularMoment', function () { expect(amCalendar(Date.UTC(2012, 0, 22, 4, 46, 54))).toBe('01/21/2012'); }); + it('should respect the timezone parameter', function () { + var timestamp = Date.UTC(2012, 0, 22, 12, 46, 54); + amMoment.changeLocale('en', {calendar: {sameElse: '(HH,mm,ss);MM.DD.YYYY'}}); + expect(amCalendar(timestamp, 'utc', 'Pacific/Tahiti')).toBe('(02,46,54);01.22.2012'); + amMoment.changeLocale('en', {calendar: {sameElse: 'L'}}); + }); + + it('should parse timezones containing Z correctly (issue #168)', function () { + angularMomentConfig.timezone = 'Europe/Zurich'; + expect(amCalendar(Date.UTC(2015, 8, 3, 23, 55, 55))).toBe('2015-09-04T01:55:55+02:00'); + }); + + it('should accept UTC offset as a timezone parameter', function () { + var timestamp = Date.UTC(2012, 0, 22, 12, 46, 54); + amMoment.changeLocale('en', {calendar: {sameElse: '(HH,mm,ss);MM.DD.YYYY'}}); + expect(amCalendar(timestamp, 'utc', '-10:00')).toBe('(02,46,54);01.22.2012'); + amMoment.changeLocale('en', {calendar: {sameElse: 'L'}}); + }); + it('should apply the "utc" preprocessor when the string "utc" is given in the second argument', function () { expect(amCalendar(Date.UTC(2012, 0, 22, 0, 0, 0), 'utc')).toBe('01/22/2012'); expect(amCalendar(Date.UTC(2012, 0, 22, 23, 59, 59), 'utc')).toBe('01/22/2012'); @@ -571,9 +606,33 @@ describe('module angularMoment', function () { expect(amDateFormat(timestamp, '(HH,mm,ss);MM.DD.YYYY', 'utc', 'Pacific/Tahiti')).toBe('(02,46,54);01.22.2012'); }); + it('should accept UTC offset as a timezone parameter', function () { + var timestamp = Date.UTC(2012, 0, 22, 12, 46, 54); + expect(amDateFormat(timestamp, '(HH,mm,ss);MM.DD.YYYY', 'utc', '-10:00')).toBe('(02,46,54);01.22.2012'); + }); + it('should return an empty string for invalid input', function () { expect(amDateFormat('blah blah', '(HH,mm,ss);MM.DD.YYYY')).toBe(''); }); + + it('should accept a string format to parse input date', function () { + var timestamp = '20120122124654'; + expect(amDateFormat(timestamp, '(HH,mm,ss);MM.DD.YYYY', 'utc', '-10:00', 'YYYYMMDDHHmmss')).toBe('(02,46,54);01.22.2012'); + }); + + describe('format config property', function () { + it('should be used when no inputFormat parameter is set', function () { + var timestamp = '20120122124654'; + angularMomentConfig.format = 'YYYYMMDDHHmmss'; + expect(amDateFormat(timestamp, '(HH,mm,ss);MM.DD.YYYY', 'utc', '-10:00')).toBe('(02,46,54);01.22.2012'); + }); + + it('should be overrideable by inputFormat parameter', function () { + var timestamp = '20120122124654'; + angularMomentConfig.format = 'ssmmHHDDMMYYYY'; + expect(amDateFormat(timestamp, '(HH,mm,ss);MM.DD.YYYY', 'utc', '-10:00', 'YYYYMMDDHHmmss')).toBe('(02,46,54);01.22.2012'); + }); + }); }); describe('amDurationFormat filter', function () { @@ -622,6 +681,13 @@ describe('module angularMoment', function () { expect(amTimeAgo(date, null, true)).toBe('a few seconds'); }); + it('should support started date as fourth parameter', function () { + var date = new Date(2015, 7, 14), + from = new Date(2015, 7, 15); + expect(amTimeAgo(date, null, null, from)).toBe('a day ago'); + expect(amTimeAgo(date, null, true, from)).toBe('a day'); + }); + it('should gracefully handle undefined values', function () { expect(amTimeAgo()).toBe(''); }); @@ -632,6 +698,49 @@ describe('module angularMoment', function () { }); + + describe('amSubtract filter', function () { + + var amSubtract; + + beforeEach(function () { + amSubtract = $filter('amSubtract'); + }); + + it('should subtract 1 hour from date', function () { + var date = new Date(2000, 1, 1, 0, 0, 0); + expect(amSubtract(date, 1, 'hours').toString()).toMatch(/^Mon Jan 31 2000 23:00:00/); + }); + + it('should subtract 1 minute from date', function () { + var date = new Date(2000, 1, 1, 0, 0, 0); + expect(amSubtract(date, 1, 'minutes').toString()).toMatch(/^Mon Jan 31 2000 23:59:00/); + }); + + }); + + + describe('amAdd filter', function () { + + var amAdd; + + beforeEach(function () { + amAdd = $filter('amAdd'); + }); + + it('should add 1 hour to date', function () { + var date = new Date(2000, 1, 1, 0, 0, 0); + expect(amAdd(date, 1, 'hours').toString()).toMatch(/^Tue Feb 01 2000 01:00:00/); + }); + + it('should add 1 minute to date', function () { + var date = new Date(2000, 1, 1, 0, 0, 0); + expect(amAdd(date, 1, 'minutes').toString()).toMatch(/^Tue Feb 01 2000 00:01:00/); + }); + + }); + + describe('amMoment service', function () { describe('#changeLocale', function () { it('should convert today\'s date to custom calendar format', function () { diff --git a/vendor/assets/components/angular-resource/.bower.json b/vendor/assets/components/angular-resource/.bower.json new file mode 100644 index 000000000..254272b0d --- /dev/null +++ b/vendor/assets/components/angular-resource/.bower.json @@ -0,0 +1,20 @@ +{ + "name": "angular-resource", + "version": "1.3.20", + "main": "./angular-resource.js", + "ignore": [], + "dependencies": { + "angular": "1.3.20" + }, + "homepage": "https://github.com/angular/bower-angular-resource", + "_release": "1.3.20", + "_resolution": { + "type": "version", + "tag": "v1.3.20", + "commit": "de1c35544156981ec879e72d0f87298d29a8706c" + }, + "_source": "git://github.com/angular/bower-angular-resource.git", + "_target": "1.3.20", + "_originalSource": "angular-resource", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-resource/README.md b/vendor/assets/components/angular-resource/README.md new file mode 100644 index 000000000..f3bd119ce --- /dev/null +++ b/vendor/assets/components/angular-resource/README.md @@ -0,0 +1,68 @@ +# packaged angular-resource + +This repo is for distribution on `npm` and `bower`. The source for this module is in the +[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngResource). +Please file issues and pull requests against that repo. + +## Install + +You can install this package either with `npm` or with `bower`. + +### npm + +```shell +npm install angular-resource +``` + +Then add `ngResource` as a dependency for your app: + +```javascript +angular.module('myApp', [require('angular-resource')]); +``` + +### bower + +```shell +bower install angular-resource +``` + +Add a ` +``` + +Then add `ngResource` as a dependency for your app: + +```javascript +angular.module('myApp', ['ngResource']); +``` + +## Documentation + +Documentation is available on the +[AngularJS docs site](http://docs.angularjs.org/api/ngResource). + +## License + +The MIT License + +Copyright (c) 2010-2015 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/assets/components/angular-resource/angular-resource.js b/vendor/assets/components/angular-resource/angular-resource.js new file mode 100644 index 000000000..fb1ec7823 --- /dev/null +++ b/vendor/assets/components/angular-resource/angular-resource.js @@ -0,0 +1,668 @@ +/** + * @license AngularJS v1.3.20 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +var $resourceMinErr = angular.$$minErr('$resource'); + +// Helper functions and regex to lookup a dotted path on an object +// stopping at undefined/null. The path must be composed of ASCII +// identifiers (just like $parse) +var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/; + +function isValidDottedPath(path) { + return (path != null && path !== '' && path !== 'hasOwnProperty' && + MEMBER_NAME_REGEX.test('.' + path)); +} + +function lookupDottedPath(obj, path) { + if (!isValidDottedPath(path)) { + throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); + } + var keys = path.split('.'); + for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) { + var key = keys[i]; + obj = (obj !== null) ? obj[key] : undefined; + } + return obj; +} + +/** + * Create a shallow copy of an object and clear other fields from the destination + */ +function shallowClearAndCopy(src, dst) { + dst = dst || {}; + + angular.forEach(dst, function(value, key) { + delete dst[key]; + }); + + for (var key in src) { + if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { + dst[key] = src[key]; + } + } + + return dst; +} + +/** + * @ngdoc module + * @name ngResource + * @description + * + * # ngResource + * + * The `ngResource` module provides interaction support with RESTful services + * via the $resource service. + * + * + *
    + * + * See {@link ngResource.$resource `$resource`} for usage. + */ + +/** + * @ngdoc service + * @name $resource + * @requires $http + * + * @description + * A factory which creates a resource object that lets you interact with + * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. + * + * The returned resource object has action methods which provide high-level behaviors without + * the need to interact with the low level {@link ng.$http $http} service. + * + * Requires the {@link ngResource `ngResource`} module to be installed. + * + * By default, trailing slashes will be stripped from the calculated URLs, + * which can pose problems with server backends that do not expect that + * behavior. This can be disabled by configuring the `$resourceProvider` like + * this: + * + * ```js + app.config(['$resourceProvider', function($resourceProvider) { + // Don't strip trailing slashes from calculated URLs + $resourceProvider.defaults.stripTrailingSlashes = false; + }]); + * ``` + * + * @param {string} url A parametrized URL template with parameters prefixed by `:` as in + * `/user/:username`. If you are using a URL with a port number (e.g. + * `http://example.com:8080/api`), it will be respected. + * + * If you are using a url with a suffix, just add the suffix, like this: + * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` + * or even `$resource('http://example.com/resource/:resource_id.:format')` + * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be + * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you + * can escape it with `/\.`. + * + * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in + * `actions` methods. If any of the parameter value is a function, it will be executed every time + * when a param value needs to be obtained for a request (unless the param was overridden). + * + * Each key value in the parameter object is first bound to url template if present and then any + * excess keys are appended to the url search query after the `?`. + * + * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in + * URL `/path/greet?salutation=Hello`. + * + * If the parameter value is prefixed with `@` then the value for that parameter will be extracted + * from the corresponding property on the `data` object (provided when calling an action method). For + * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam` + * will be `data.someProp`. + * + * @param {Object.=} actions Hash with declaration of custom actions that should extend + * the default set of resource actions. The declaration should be created in the format of {@link + * ng.$http#usage $http.config}: + * + * {action1: {method:?, params:?, isArray:?, headers:?, ...}, + * action2: {method:?, params:?, isArray:?, headers:?, ...}, + * ...} + * + * Where: + * + * - **`action`** – {string} – The name of action. This name becomes the name of the method on + * your resource object. + * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, + * `DELETE`, `JSONP`, etc). + * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of + * the parameter value is a function, it will be executed every time when a param value needs to + * be obtained for a request (unless the param was overridden). + * - **`url`** – {string} – action specific `url` override. The url templating is supported just + * like for the resource-level urls. + * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, + * see `returns` section. + * - **`transformRequest`** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * By default, transformRequest will contain one function that checks if the request data is + * an object and serializes to using `angular.toJson`. To prevent this behavior, set + * `transformRequest` to an empty array: `transformRequest: []` + * - **`transformResponse`** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * response body and headers and returns its transformed (typically deserialized) version. + * By default, transformResponse will contain one function that checks if the response looks like + * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set + * `transformResponse` to an empty array: `transformResponse: []` + * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the + * GET request, otherwise if a cache instance built with + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that + * should abort the request when resolved. + * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the + * XHR object. See + * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) + * for more information. + * - **`responseType`** - `{string}` - see + * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). + * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - + * `response` and `responseError`. Both `response` and `responseError` interceptors get called + * with `http response` object. See {@link ng.$http $http interceptors}. + * + * @param {Object} options Hash with custom settings that should extend the + * default `$resourceProvider` behavior. The only supported option is + * + * Where: + * + * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing + * slashes from any calculated URL will be stripped. (Defaults to true.) + * + * @returns {Object} A resource "class" object with methods for the default set of resource actions + * optionally extended with custom `actions`. The default set contains these actions: + * ```js + * { 'get': {method:'GET'}, + * 'save': {method:'POST'}, + * 'query': {method:'GET', isArray:true}, + * 'remove': {method:'DELETE'}, + * 'delete': {method:'DELETE'} }; + * ``` + * + * Calling these methods invoke an {@link ng.$http} with the specified http method, + * destination and parameters. When the data is returned from the server then the object is an + * instance of the resource class. The actions `save`, `remove` and `delete` are available on it + * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, + * read, update, delete) on server-side data like this: + * ```js + * var User = $resource('/user/:userId', {userId:'@id'}); + * var user = User.get({userId:123}, function() { + * user.abc = true; + * user.$save(); + * }); + * ``` + * + * It is important to realize that invoking a $resource object method immediately returns an + * empty reference (object or array depending on `isArray`). Once the data is returned from the + * server the existing reference is populated with the actual data. This is a useful trick since + * usually the resource is assigned to a model which is then rendered by the view. Having an empty + * object results in no rendering, once the data arrives from the server then the object is + * populated with the data and the view automatically re-renders itself showing the new data. This + * means that in most cases one never has to write a callback function for the action methods. + * + * The action methods on the class object or instance object can be invoked with the following + * parameters: + * + * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` + * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` + * - non-GET instance actions: `instance.$action([parameters], [success], [error])` + * + * + * Success callback is called with (value, responseHeaders) arguments. Error callback is called + * with (httpResponse) argument. + * + * Class actions return empty instance (with additional properties below). + * Instance actions return promise of the action. + * + * The Resource instances and collection have these additional properties: + * + * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this + * instance or collection. + * + * On success, the promise is resolved with the same resource instance or collection object, + * updated with data from server. This makes it easy to use in + * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view + * rendering until the resource(s) are loaded. + * + * On failure, the promise is resolved with the {@link ng.$http http response} object, without + * the `resource` property. + * + * If an interceptor object was provided, the promise will instead be resolved with the value + * returned by the interceptor. + * + * - `$resolved`: `true` after first server interaction is completed (either with success or + * rejection), `false` before that. Knowing if the Resource has been resolved is useful in + * data-binding. + * + * @example + * + * # Credit card resource + * + * ```js + // Define CreditCard class + var CreditCard = $resource('/user/:userId/card/:cardId', + {userId:123, cardId:'@id'}, { + charge: {method:'POST', params:{charge:true}} + }); + + // We can retrieve a collection from the server + var cards = CreditCard.query(function() { + // GET: /user/123/card + // server returns: [ {id:456, number:'1234', name:'Smith'} ]; + + var card = cards[0]; + // each item is an instance of CreditCard + expect(card instanceof CreditCard).toEqual(true); + card.name = "J. Smith"; + // non GET methods are mapped onto the instances + card.$save(); + // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} + // server returns: {id:456, number:'1234', name: 'J. Smith'}; + + // our custom method is mapped as well. + card.$charge({amount:9.99}); + // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} + }); + + // we can create an instance as well + var newCard = new CreditCard({number:'0123'}); + newCard.name = "Mike Smith"; + newCard.$save(); + // POST: /user/123/card {number:'0123', name:'Mike Smith'} + // server returns: {id:789, number:'0123', name: 'Mike Smith'}; + expect(newCard.id).toEqual(789); + * ``` + * + * The object returned from this function execution is a resource "class" which has "static" method + * for each action in the definition. + * + * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and + * `headers`. + * When the data is returned from the server then the object is an instance of the resource type and + * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD + * operations (create, read, update, delete) on server-side data. + + ```js + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}, function(user) { + user.abc = true; + user.$save(); + }); + ``` + * + * It's worth noting that the success callback for `get`, `query` and other methods gets passed + * in the response that came from the server as well as $http header getter function, so one + * could rewrite the above example and get access to http headers as: + * + ```js + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}, function(u, getResponseHeaders){ + u.abc = true; + u.$save(function(u, putResponseHeaders) { + //u => saved user object + //putResponseHeaders => $http header getter + }); + }); + ``` + * + * You can also access the raw `$http` promise via the `$promise` property on the object returned + * + ``` + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}) + .$promise.then(function(user) { + $scope.user = user; + }); + ``` + + * # Creating a custom 'PUT' request + * In this example we create a custom method on our resource to make a PUT request + * ```js + * var app = angular.module('app', ['ngResource', 'ngRoute']); + * + * // Some APIs expect a PUT request in the format URL/object/ID + * // Here we are creating an 'update' method + * app.factory('Notes', ['$resource', function($resource) { + * return $resource('/notes/:id', null, + * { + * 'update': { method:'PUT' } + * }); + * }]); + * + * // In our controller we get the ID from the URL using ngRoute and $routeParams + * // We pass in $routeParams and our Notes factory along with $scope + * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', + function($scope, $routeParams, Notes) { + * // First get a note object from the factory + * var note = Notes.get({ id:$routeParams.id }); + * $id = note.id; + * + * // Now call update passing in the ID first then the object you are updating + * Notes.update({ id:$id }, note); + * + * // This will PUT /notes/ID with the note object in the request payload + * }]); + * ``` + */ +angular.module('ngResource', ['ng']). + provider('$resource', function() { + var provider = this; + + this.defaults = { + // Strip slashes by default + stripTrailingSlashes: true, + + // Default actions configuration + actions: { + 'get': {method: 'GET'}, + 'save': {method: 'POST'}, + 'query': {method: 'GET', isArray: true}, + 'remove': {method: 'DELETE'}, + 'delete': {method: 'DELETE'} + } + }; + + this.$get = ['$http', '$q', function($http, $q) { + + var noop = angular.noop, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + isFunction = angular.isFunction; + + /** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set + * (pchar) allowed in path segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); + } + + + /** + * This method is intended for encoding *key* or *value* parts of query component. We need a + * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't + * have to be encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } + + function Route(template, defaults) { + this.template = template; + this.defaults = extend({}, provider.defaults, defaults); + this.urlParams = {}; + } + + Route.prototype = { + setUrlParams: function(config, params, actionUrl) { + var self = this, + url = actionUrl || self.template, + val, + encodedVal; + + var urlParams = self.urlParams = {}; + forEach(url.split(/\W/), function(param) { + if (param === 'hasOwnProperty') { + throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); + } + if (!(new RegExp("^\\d+$").test(param)) && param && + (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { + urlParams[param] = true; + } + }); + url = url.replace(/\\:/g, ':'); + + params = params || {}; + forEach(self.urlParams, function(_, urlParam) { + val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; + if (angular.isDefined(val) && val !== null) { + encodedVal = encodeUriSegment(val); + url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { + return encodedVal + p1; + }); + } else { + url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, + leadingSlashes, tail) { + if (tail.charAt(0) == '/') { + return tail; + } else { + return leadingSlashes + tail; + } + }); + } + }); + + // strip trailing slashes and set the url (unless this behavior is specifically disabled) + if (self.defaults.stripTrailingSlashes) { + url = url.replace(/\/+$/, '') || '/'; + } + + // then replace collapse `/.` if found in the last URL path segment before the query + // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` + url = url.replace(/\/\.(?=\w+($|\?))/, '.'); + // replace escaped `/\.` with `/.` + config.url = url.replace(/\/\\\./, '/.'); + + + // set params - delegate param encoding to $http + forEach(params, function(value, key) { + if (!self.urlParams[key]) { + config.params = config.params || {}; + config.params[key] = value; + } + }); + } + }; + + + function resourceFactory(url, paramDefaults, actions, options) { + var route = new Route(url, options); + + actions = extend({}, provider.defaults.actions, actions); + + function extractParams(data, actionParams) { + var ids = {}; + actionParams = extend({}, paramDefaults, actionParams); + forEach(actionParams, function(value, key) { + if (isFunction(value)) { value = value(); } + ids[key] = value && value.charAt && value.charAt(0) == '@' ? + lookupDottedPath(data, value.substr(1)) : value; + }); + return ids; + } + + function defaultResponseInterceptor(response) { + return response.resource; + } + + function Resource(value) { + shallowClearAndCopy(value || {}, this); + } + + Resource.prototype.toJSON = function() { + var data = extend({}, this); + delete data.$promise; + delete data.$resolved; + return data; + }; + + forEach(actions, function(action, name) { + var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); + + Resource[name] = function(a1, a2, a3, a4) { + var params = {}, data, success, error; + + /* jshint -W086 */ /* (purposefully fall through case statements) */ + switch (arguments.length) { + case 4: + error = a4; + success = a3; + //fallthrough + case 3: + case 2: + if (isFunction(a2)) { + if (isFunction(a1)) { + success = a1; + error = a2; + break; + } + + success = a2; + error = a3; + //fallthrough + } else { + params = a1; + data = a2; + success = a3; + break; + } + case 1: + if (isFunction(a1)) success = a1; + else if (hasBody) data = a1; + else params = a1; + break; + case 0: break; + default: + throw $resourceMinErr('badargs', + "Expected up to 4 arguments [params, data, success, error], got {0} arguments", + arguments.length); + } + /* jshint +W086 */ /* (purposefully fall through case statements) */ + + var isInstanceCall = this instanceof Resource; + var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); + var httpConfig = {}; + var responseInterceptor = action.interceptor && action.interceptor.response || + defaultResponseInterceptor; + var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || + undefined; + + forEach(action, function(value, key) { + if (key != 'params' && key != 'isArray' && key != 'interceptor') { + httpConfig[key] = copy(value); + } + }); + + if (hasBody) httpConfig.data = data; + route.setUrlParams(httpConfig, + extend({}, extractParams(data, action.params || {}), params), + action.url); + + var promise = $http(httpConfig).then(function(response) { + var data = response.data, + promise = value.$promise; + + if (data) { + // Need to convert action.isArray to boolean in case it is undefined + // jshint -W018 + if (angular.isArray(data) !== (!!action.isArray)) { + throw $resourceMinErr('badcfg', + 'Error in resource configuration for action `{0}`. Expected response to ' + + 'contain an {1} but got an {2}', name, action.isArray ? 'array' : 'object', + angular.isArray(data) ? 'array' : 'object'); + } + // jshint +W018 + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + if (typeof item === "object") { + value.push(new Resource(item)); + } else { + // Valid JSON values may be string literals, and these should not be converted + // into objects. These items will not have access to the Resource prototype + // methods, but unfortunately there + value.push(item); + } + }); + } else { + shallowClearAndCopy(data, value); + value.$promise = promise; + } + } + + value.$resolved = true; + + response.resource = value; + + return response; + }, function(response) { + value.$resolved = true; + + (error || noop)(response); + + return $q.reject(response); + }); + + promise = promise.then( + function(response) { + var value = responseInterceptor(response); + (success || noop)(value, response.headers); + return value; + }, + responseErrorInterceptor); + + if (!isInstanceCall) { + // we are creating instance / collection + // - set the initial promise + // - return the instance / collection + value.$promise = promise; + value.$resolved = false; + + return value; + } + + // instance call + return promise; + }; + + + Resource.prototype['$' + name] = function(params, success, error) { + if (isFunction(params)) { + error = success; success = params; params = {}; + } + var result = Resource[name].call(this, params, this, success, error); + return result.$promise || result; + }; + }); + + Resource.bind = function(additionalParamDefaults) { + return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); + }; + + return Resource; + } + + return resourceFactory; + }]; + }); + + +})(window, window.angular); diff --git a/vendor/assets/components/angular-resource/angular-resource.min.js b/vendor/assets/components/angular-resource/angular-resource.min.js new file mode 100644 index 000000000..ed1415ca9 --- /dev/null +++ b/vendor/assets/components/angular-resource/angular-resource.min.js @@ -0,0 +1,13 @@ +/* + AngularJS v1.3.20 + (c) 2010-2014 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(I,d,B){'use strict';function D(f,q){q=q||{};d.forEach(q,function(d,h){delete q[h]});for(var h in f)!f.hasOwnProperty(h)||"$"===h.charAt(0)&&"$"===h.charAt(1)||(q[h]=f[h]);return q}var w=d.$$minErr("$resource"),C=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;d.module("ngResource",["ng"]).provider("$resource",function(){var f=this;this.defaults={stripTrailingSlashes:!0,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}}; +this.$get=["$http","$q",function(q,h){function t(d,g){this.template=d;this.defaults=s({},f.defaults,g);this.urlParams={}}function v(x,g,l,m){function c(b,k){var c={};k=s({},g,k);r(k,function(a,k){u(a)&&(a=a());var d;if(a&&a.charAt&&"@"==a.charAt(0)){d=b;var e=a.substr(1);if(null==e||""===e||"hasOwnProperty"===e||!C.test("."+e))throw w("badmember",e);for(var e=e.split("."),n=0,g=e.length;n", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/angular.js/issues" + }, + "homepage": "http://angularjs.org" +} diff --git a/vendor/assets/components/angular-sanitize/.bower.json b/vendor/assets/components/angular-sanitize/.bower.json new file mode 100644 index 000000000..a80909dd3 --- /dev/null +++ b/vendor/assets/components/angular-sanitize/.bower.json @@ -0,0 +1,20 @@ +{ + "name": "angular-sanitize", + "version": "1.3.20", + "main": "./angular-sanitize.js", + "ignore": [], + "dependencies": { + "angular": "1.3.20" + }, + "homepage": "https://github.com/angular/bower-angular-sanitize", + "_release": "1.3.20", + "_resolution": { + "type": "version", + "tag": "v1.3.20", + "commit": "1072923abc9a6bc076e0625ae115b7b8f3ed70d1" + }, + "_source": "git://github.com/angular/bower-angular-sanitize.git", + "_target": "1.3.20", + "_originalSource": "angular-sanitize", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-sanitize/README.md b/vendor/assets/components/angular-sanitize/README.md new file mode 100644 index 000000000..b84aaf6db --- /dev/null +++ b/vendor/assets/components/angular-sanitize/README.md @@ -0,0 +1,68 @@ +# packaged angular-sanitize + +This repo is for distribution on `npm` and `bower`. The source for this module is in the +[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngSanitize). +Please file issues and pull requests against that repo. + +## Install + +You can install this package either with `npm` or with `bower`. + +### npm + +```shell +npm install angular-sanitize +``` + +Then add `ngSanitize` as a dependency for your app: + +```javascript +angular.module('myApp', [require('angular-sanitize')]); +``` + +### bower + +```shell +bower install angular-sanitize +``` + +Add a ` +``` + +Then add `ngSanitize` as a dependency for your app: + +```javascript +angular.module('myApp', ['ngSanitize']); +``` + +## Documentation + +Documentation is available on the +[AngularJS docs site](http://docs.angularjs.org/api/ngSanitize). + +## License + +The MIT License + +Copyright (c) 2010-2015 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/assets/components/angular-sanitize/angular-sanitize.js b/vendor/assets/components/angular-sanitize/angular-sanitize.js new file mode 100644 index 000000000..6285a08ae --- /dev/null +++ b/vendor/assets/components/angular-sanitize/angular-sanitize.js @@ -0,0 +1,679 @@ +/** + * @license AngularJS v1.3.20 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +var $sanitizeMinErr = angular.$$minErr('$sanitize'); + +/** + * @ngdoc module + * @name ngSanitize + * @description + * + * # ngSanitize + * + * The `ngSanitize` module provides functionality to sanitize HTML. + * + * + *
    + * + * See {@link ngSanitize.$sanitize `$sanitize`} for usage. + */ + +/* + * HTML Parser By Misko Hevery (misko@hevery.com) + * based on: HTML Parser By John Resig (ejohn.org) + * Original code by Erik Arvidsson, Mozilla Public License + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + * + * // Use like so: + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + */ + + +/** + * @ngdoc service + * @name $sanitize + * @kind function + * + * @description + * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are + * then serialized back to properly escaped html string. This means that no unsafe input can make + * it into the returned string, however, since our parser is more strict than a typical browser + * parser, it's possible that some obscure input, which would be recognized as valid HTML by a + * browser, won't make it through the sanitizer. The input may also contain SVG markup. + * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and + * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. + * + * @param {string} html HTML input. + * @returns {string} Sanitized HTML. + * + * @example + + + +
    + Snippet: + + + + + + + + + + + + + + + + + + + + + + + + + +
    DirectiveHowSourceRendered
    ng-bind-htmlAutomatically uses $sanitize
    <div ng-bind-html="snippet">
    </div>
    ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value +
    <div ng-bind-html="deliberatelyTrustDangerousSnippet()">
    +</div>
    +
    ng-bindAutomatically escapes
    <div ng-bind="snippet">
    </div>
    +
    +
    + + it('should sanitize the html snippet by default', function() { + expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). + toBe('

    an html\nclick here\nsnippet

    '); + }); + + it('should inline raw snippet if bound to a trusted value', function() { + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). + toBe("

    an html\n" + + "click here\n" + + "snippet

    "); + }); + + it('should escape snippet without any filter', function() { + expect(element(by.css('#bind-default div')).getInnerHtml()). + toBe("<p style=\"color:blue\">an html\n" + + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + + "snippet</p>"); + }); + + it('should update', function() { + element(by.model('snippet')).clear(); + element(by.model('snippet')).sendKeys('new text'); + expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). + toBe('new text'); + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( + 'new text'); + expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( + "new <b onclick=\"alert(1)\">text</b>"); + }); +
    +
    + */ +function $SanitizeProvider() { + this.$get = ['$$sanitizeUri', function($$sanitizeUri) { + return function(html) { + var buf = []; + htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { + return !/^unsafe/.test($$sanitizeUri(uri, isImage)); + })); + return buf.join(''); + }; + }]; +} + +function sanitizeText(chars) { + var buf = []; + var writer = htmlSanitizeWriter(buf, angular.noop); + writer.chars(chars); + return buf.join(''); +} + + +// Regular Expressions for parsing tags and attributes +var START_TAG_REGEXP = + /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/, + END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/, + ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, + BEGIN_TAG_REGEXP = /^/g, + DOCTYPE_REGEXP = /]*?)>/i, + CDATA_REGEXP = //g, + SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + // Match everything outside of normal chars and " (quote character) + NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; + + +// Good source of info about elements and attributes +// http://dev.w3.org/html5/spec/Overview.html#semantics +// http://simon.html5.org/html-elements + +// Safe Void Elements - HTML5 +// http://dev.w3.org/html5/spec/Overview.html#void-elements +var voidElements = makeMap("area,br,col,hr,img,wbr"); + +// Elements that you can, intentionally, leave open (and which close themselves) +// http://dev.w3.org/html5/spec/Overview.html#optional-tags +var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), + optionalEndTagInlineElements = makeMap("rp,rt"), + optionalEndTagElements = angular.extend({}, + optionalEndTagInlineElements, + optionalEndTagBlockElements); + +// Safe Block Elements - HTML5 +var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + + "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + + "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); + +// Inline Elements - HTML5 +var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + + "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + + "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); + +// SVG Elements +// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements +var svgElements = makeMap("animate,animateColor,animateMotion,animateTransform,circle,defs," + + "desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient," + + "line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,set," + + "stop,svg,switch,text,title,tspan,use"); + +// Special Elements (can contain anything) +var specialElements = makeMap("script,style"); + +var validElements = angular.extend({}, + voidElements, + blockElements, + inlineElements, + optionalEndTagElements, + svgElements); + +//Attributes that have href and hence need to be sanitized +var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href"); + +var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + + 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + + 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + + 'scope,scrolling,shape,size,span,start,summary,target,title,type,' + + 'valign,value,vspace,width'); + +// SVG attributes (without "id" and "name" attributes) +// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes +var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + + 'attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,' + + 'color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,' + + 'font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,' + + 'gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,' + + 'keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,' + + 'markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,' + + 'overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,' + + 'repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,' + + 'stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,' + + 'stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,' + + 'stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,' + + 'underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,' + + 'viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,' + + 'xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,' + + 'zoomAndPan'); + +var validAttrs = angular.extend({}, + uriAttrs, + svgAttrs, + htmlAttrs); + +function makeMap(str) { + var obj = {}, items = str.split(','), i; + for (i = 0; i < items.length; i++) obj[items[i]] = true; + return obj; +} + + +/** + * @example + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + * @param {string} html string + * @param {object} handler + */ +function htmlParser(html, handler) { + if (typeof html !== 'string') { + if (html === null || typeof html === 'undefined') { + html = ''; + } else { + html = '' + html; + } + } + var index, chars, match, stack = [], last = html, text; + stack.last = function() { return stack[stack.length - 1]; }; + + while (html) { + text = ''; + chars = true; + + // Make sure we're not in a script or style element + if (!stack.last() || !specialElements[stack.last()]) { + + // Comment + if (html.indexOf("", index) === index) { + if (handler.comment) handler.comment(html.substring(4, index)); + html = html.substring(index + 3); + chars = false; + } + // DOCTYPE + } else if (DOCTYPE_REGEXP.test(html)) { + match = html.match(DOCTYPE_REGEXP); + + if (match) { + html = html.replace(match[0], ''); + chars = false; + } + // end tag + } else if (BEGING_END_TAGE_REGEXP.test(html)) { + match = html.match(END_TAG_REGEXP); + + if (match) { + html = html.substring(match[0].length); + match[0].replace(END_TAG_REGEXP, parseEndTag); + chars = false; + } + + // start tag + } else if (BEGIN_TAG_REGEXP.test(html)) { + match = html.match(START_TAG_REGEXP); + + if (match) { + // We only have a valid start-tag if there is a '>'. + if (match[4]) { + html = html.substring(match[0].length); + match[0].replace(START_TAG_REGEXP, parseStartTag); + } + chars = false; + } else { + // no ending tag found --- this piece should be encoded as an entity. + text += '<'; + html = html.substring(1); + } + } + + if (chars) { + index = html.indexOf("<"); + + text += index < 0 ? html : html.substring(0, index); + html = index < 0 ? "" : html.substring(index); + + if (handler.chars) handler.chars(decodeEntities(text)); + } + + } else { + // IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w]. + html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), + function(all, text) { + text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); + + if (handler.chars) handler.chars(decodeEntities(text)); + + return ""; + }); + + parseEndTag("", stack.last()); + } + + if (html == last) { + throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + + "of html: {0}", html); + } + last = html; + } + + // Clean up any remaining tags + parseEndTag(); + + function parseStartTag(tag, tagName, rest, unary) { + tagName = angular.lowercase(tagName); + if (blockElements[tagName]) { + while (stack.last() && inlineElements[stack.last()]) { + parseEndTag("", stack.last()); + } + } + + if (optionalEndTagElements[tagName] && stack.last() == tagName) { + parseEndTag("", tagName); + } + + unary = voidElements[tagName] || !!unary; + + if (!unary) + stack.push(tagName); + + var attrs = {}; + + rest.replace(ATTR_REGEXP, + function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { + var value = doubleQuotedValue + || singleQuotedValue + || unquotedValue + || ''; + + attrs[name] = decodeEntities(value); + }); + if (handler.start) handler.start(tagName, attrs, unary); + } + + function parseEndTag(tag, tagName) { + var pos = 0, i; + tagName = angular.lowercase(tagName); + if (tagName) + // Find the closest opened tag of the same type + for (pos = stack.length - 1; pos >= 0; pos--) + if (stack[pos] == tagName) + break; + + if (pos >= 0) { + // Close all the open elements, up the stack + for (i = stack.length - 1; i >= pos; i--) + if (handler.end) handler.end(stack[i]); + + // Remove the open elements from the stack + stack.length = pos; + } + } +} + +var hiddenPre=document.createElement("pre"); +/** + * decodes all entities into regular string + * @param value + * @returns {string} A string with decoded entities. + */ +function decodeEntities(value) { + if (!value) { return ''; } + + hiddenPre.innerHTML = value.replace(//g, '>'); +} + +/** + * create an HTML/XML writer which writes to buffer + * @param {Array} buf use buf.jain('') to get out sanitized html string + * @returns {object} in the form of { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * } + */ +function htmlSanitizeWriter(buf, uriValidator) { + var ignore = false; + var out = angular.bind(buf, buf.push); + return { + start: function(tag, attrs, unary) { + tag = angular.lowercase(tag); + if (!ignore && specialElements[tag]) { + ignore = tag; + } + if (!ignore && validElements[tag] === true) { + out('<'); + out(tag); + angular.forEach(attrs, function(value, key) { + var lkey=angular.lowercase(key); + var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); + if (validAttrs[lkey] === true && + (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { + out(' '); + out(key); + out('="'); + out(encodeEntities(value)); + out('"'); + } + }); + out(unary ? '/>' : '>'); + } + }, + end: function(tag) { + tag = angular.lowercase(tag); + if (!ignore && validElements[tag] === true) { + out(''); + } + if (tag == ignore) { + ignore = false; + } + }, + chars: function(chars) { + if (!ignore) { + out(encodeEntities(chars)); + } + } + }; +} + + +// define ngSanitize module and register $sanitize service +angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); + +/* global sanitizeText: false */ + +/** + * @ngdoc filter + * @name linky + * @kind function + * + * @description + * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and + * plain email address links. + * + * Requires the {@link ngSanitize `ngSanitize`} module to be installed. + * + * @param {string} text Input text. + * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. + * @returns {string} Html-linkified text. + * + * @usage + + * + * @example + + + +
    + Snippet: + + + + + + + + + + + + + + + + + + + + + +
    FilterSourceRendered
    linky filter +
    <div ng-bind-html="snippet | linky">
    </div>
    +
    +
    +
    linky target +
    <div ng-bind-html="snippetWithTarget | linky:'_blank'">
    </div>
    +
    +
    +
    no filter
    <div ng-bind="snippet">
    </div>
    + + + it('should linkify the snippet with urls', function() { + expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). + toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + + 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); + expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); + }); + + it('should not linkify snippet without the linky filter', function() { + expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). + toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + + 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); + expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); + }); + + it('should update', function() { + element(by.model('snippet')).clear(); + element(by.model('snippet')).sendKeys('new http://link.'); + expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). + toBe('new http://link.'); + expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); + expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) + .toBe('new http://link.'); + }); + + it('should work with the target property', function() { + expect(element(by.id('linky-target')). + element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). + toBe('http://angularjs.org/'); + expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); + }); + + + */ +angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { + var LINKY_URL_REGEXP = + /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/i, + MAILTO_REGEXP = /^mailto:/i; + + return function(text, target) { + if (!text) return text; + var match; + var raw = text; + var html = []; + var url; + var i; + while ((match = raw.match(LINKY_URL_REGEXP))) { + // We can not end in these as they are sometimes found at the end of the sentence + url = match[0]; + // if we did not match ftp/http/www/mailto then assume mailto + if (!match[2] && !match[4]) { + url = (match[3] ? 'http://' : 'mailto:') + url; + } + i = match.index; + addText(raw.substr(0, i)); + addLink(url, match[0].replace(MAILTO_REGEXP, '')); + raw = raw.substring(i + match[0].length); + } + addText(raw); + return $sanitize(html.join('')); + + function addText(text) { + if (!text) { + return; + } + html.push(sanitizeText(text)); + } + + function addLink(url, text) { + html.push(''); + addText(text); + html.push(''); + } + }; +}]); + + +})(window, window.angular); diff --git a/vendor/assets/components/angular-sanitize/angular-sanitize.min.js b/vendor/assets/components/angular-sanitize/angular-sanitize.min.js new file mode 100644 index 000000000..453a1fa58 --- /dev/null +++ b/vendor/assets/components/angular-sanitize/angular-sanitize.min.js @@ -0,0 +1,16 @@ +/* + AngularJS v1.3.20 + (c) 2010-2014 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(n,h,p){'use strict';function E(a){var e=[];r(e,h.noop).chars(a);return e.join("")}function g(a){var e={};a=a.split(",");var d;for(d=0;d=c;d--)e.end&&e.end(f[d]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,k,f=[],m=a,l;for(f.last=function(){return f[f.length-1]};a;){l="";k=!0;if(f.last()&&w[f.last()])a=a.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(H,"$1").replace(I,"$1");e.chars&&e.chars(q(b));return""}),c("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(e.comment&& +e.comment(a.substring(4,b)),a=a.substring(b+3),k=!1);else if(x.test(a)){if(b=a.match(x))a=a.replace(b[0],""),k=!1}else if(J.test(a)){if(b=a.match(y))a=a.substring(b[0].length),b[0].replace(y,c),k=!1}else K.test(a)&&((b=a.match(z))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(z,d)),k=!1):(l+="<",a=a.substring(1)));k&&(b=a.indexOf("<"),l+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),e.chars&&e.chars(q(l)))}if(a==m)throw L("badparse",a);m=a}c()}function q(a){if(!a)return"";A.innerHTML=a.replace(//g,">")}function r(a,e){var d=!1,c=h.bind(a,a.push);return{start:function(a,k,f){a=h.lowercase(a);!d&&w[a]&&(d=a);d||!0!==C[a]||(c("<"),c(a),h.forEach(k,function(d,f){var k=h.lowercase(f),g="img"===a&&"src"===k||"background"=== +k;!0!==O[k]||!0===D[k]&&!e(d,g)||(c(" "),c(f),c('="'),c(B(d)),c('"'))}),c(f?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==C[a]||(c(""));a==d&&(d=!1)},chars:function(a){d||c(B(a))}}}var L=h.$$minErr("$sanitize"),z=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,y=/^<\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^]*?)>/i, +I=/"\u201d\u2019]/i,d=/^mailto:/i;return function(c,b){function k(a){a&&g.push(E(a))}function f(a,c){g.push("');k(c);g.push("")}if(!c)return c;for(var m,l=c,g=[],n,p;m=l.match(e);)n=m[0],m[2]||m[4]||(n=(m[3]?"http://":"mailto:")+n),p=m.index,k(l.substr(0,p)),f(n,m[0].replace(d,"")),l=l.substring(p+m[0].length);k(l);return a(g.join(""))}}])})(window,window.angular); +//# sourceMappingURL=angular-sanitize.min.js.map diff --git a/vendor/assets/components/angular-sanitize/angular-sanitize.min.js.map b/vendor/assets/components/angular-sanitize/angular-sanitize.min.js.map new file mode 100644 index 000000000..010412986 --- /dev/null +++ b/vendor/assets/components/angular-sanitize/angular-sanitize.min.js.map @@ -0,0 +1,8 @@ +{ +"version":3, +"file":"angular-sanitize.min.js", +"lineCount":15, +"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkBC,CAAlB,CAA6B,CA6JtCC,QAASA,EAAY,CAACC,CAAD,CAAQ,CAC3B,IAAIC,EAAM,EACGC,EAAAC,CAAmBF,CAAnBE,CAAwBN,CAAAO,KAAxBD,CACbH,MAAA,CAAaA,CAAb,CACA,OAAOC,EAAAI,KAAA,CAAS,EAAT,CAJoB,CAmG7BC,QAASA,EAAO,CAACC,CAAD,CAAM,CAAA,IAChBC,EAAM,EAAIC,EAAAA,CAAQF,CAAAG,MAAA,CAAU,GAAV,CAAtB,KAAsCC,CACtC,KAAKA,CAAL,CAAS,CAAT,CAAYA,CAAZ,CAAgBF,CAAAG,OAAhB,CAA8BD,CAAA,EAA9B,CAAmCH,CAAA,CAAIC,CAAA,CAAME,CAAN,CAAJ,CAAA,CAAgB,CAAA,CACnD,OAAOH,EAHa,CAmBtBK,QAASA,EAAU,CAACC,CAAD,CAAOC,CAAP,CAAgB,CAiGjCC,QAASA,EAAa,CAACC,CAAD,CAAMC,CAAN,CAAeC,CAAf,CAAqBC,CAArB,CAA4B,CAChDF,CAAA,CAAUrB,CAAAwB,UAAA,CAAkBH,CAAlB,CACV,IAAII,CAAA,CAAcJ,CAAd,CAAJ,CACE,IAAA,CAAOK,CAAAC,KAAA,EAAP,EAAuBC,CAAA,CAAeF,CAAAC,KAAA,EAAf,CAAvB,CAAA,CACEE,CAAA,CAAY,EAAZ,CAAgBH,CAAAC,KAAA,EAAhB,CAIAG,EAAA,CAAuBT,CAAvB,CAAJ,EAAuCK,CAAAC,KAAA,EAAvC,EAAuDN,CAAvD,EACEQ,CAAA,CAAY,EAAZ,CAAgBR,CAAhB,CAKF,EAFAE,CAEA,CAFQQ,CAAA,CAAaV,CAAb,CAER,EAFiC,CAAEE,CAAAA,CAEnC,GACEG,CAAAM,KAAA,CAAWX,CAAX,CAEF,KAAIY,EAAQ,EAEZX,EAAAY,QAAA,CAAaC,CAAb,CACE,QAAQ,CAACC,CAAD,CAAQC,CAAR,CAAcC,CAAd,CAAiCC,CAAjC,CAAoDC,CAApD,CAAmE,CAMzEP,CAAA,CAAMI,CAAN,CAAA,CAAcI,CAAA,CALFH,CAKE,EAJTC,CAIS,EAHTC,CAGS,EAFT,EAES,CAN2D,CAD7E,CASItB,EAAAwB,MAAJ,EAAmBxB,CAAAwB,MAAA,CAAcrB,CAAd,CAAuBY,CAAvB,CAA8BV,CAA9B,CA5B6B,CA+BlDM,QAASA,EAAW,CAACT,CAAD,CAAMC,CAAN,CAAe,CAAA,IAC7BsB,EAAM,CADuB,CACpB7B,CAEb,IADAO,CACA,CADUrB,CAAAwB,UAAA,CAAkBH,CAAlB,CACV,CAEE,IAAKsB,CAAL,CAAWjB,CAAAX,OAAX,CAA0B,CAA1B,CAAoC,CAApC,EAA6B4B,CAA7B,EACMjB,CAAA,CAAMiB,CAAN,CADN,EACoBtB,CADpB,CAAuCsB,CAAA,EAAvC;AAIF,GAAW,CAAX,EAAIA,CAAJ,CAAc,CAEZ,IAAK7B,CAAL,CAASY,CAAAX,OAAT,CAAwB,CAAxB,CAA2BD,CAA3B,EAAgC6B,CAAhC,CAAqC7B,CAAA,EAArC,CACMI,CAAA0B,IAAJ,EAAiB1B,CAAA0B,IAAA,CAAYlB,CAAA,CAAMZ,CAAN,CAAZ,CAGnBY,EAAAX,OAAA,CAAe4B,CANH,CATmB,CA/Hf,QAApB,GAAI,MAAO1B,EAAX,GAEIA,CAFJ,CACe,IAAb,GAAIA,CAAJ,EAAqC,WAArC,GAAqB,MAAOA,EAA5B,CACS,EADT,CAGS,EAHT,CAGcA,CAJhB,CADiC,KAQ7B4B,CAR6B,CAQtB1C,CARsB,CAQRuB,EAAQ,EARA,CAQIC,EAAOV,CARX,CAQiB6B,CAGlD,KAFApB,CAAAC,KAEA,CAFaoB,QAAQ,EAAG,CAAE,MAAOrB,EAAA,CAAMA,CAAAX,OAAN,CAAqB,CAArB,CAAT,CAExB,CAAOE,CAAP,CAAA,CAAa,CACX6B,CAAA,CAAO,EACP3C,EAAA,CAAQ,CAAA,CAGR,IAAKuB,CAAAC,KAAA,EAAL,EAAsBqB,CAAA,CAAgBtB,CAAAC,KAAA,EAAhB,CAAtB,CA2DEV,CASA,CATOA,CAAAiB,QAAA,CAAa,IAAIe,MAAJ,CAAW,yBAAX,CAAuCvB,CAAAC,KAAA,EAAvC,CAAsD,QAAtD,CAAgE,GAAhE,CAAb,CACL,QAAQ,CAACuB,CAAD,CAAMJ,CAAN,CAAY,CAClBA,CAAA,CAAOA,CAAAZ,QAAA,CAAaiB,CAAb,CAA6B,IAA7B,CAAAjB,QAAA,CAA2CkB,CAA3C,CAAyD,IAAzD,CAEHlC,EAAAf,MAAJ,EAAmBe,CAAAf,MAAA,CAAcsC,CAAA,CAAeK,CAAf,CAAd,CAEnB,OAAO,EALW,CADf,CASP,CAAAjB,CAAA,CAAY,EAAZ,CAAgBH,CAAAC,KAAA,EAAhB,CApEF,KAAqD,CAGnD,GAA6B,CAA7B,GAAIV,CAAAoC,QAAA,CAAa,SAAb,CAAJ,CAEER,CAEA,CAFQ5B,CAAAoC,QAAA,CAAa,IAAb,CAAmB,CAAnB,CAER,CAAa,CAAb,EAAIR,CAAJ,EAAkB5B,CAAAqC,YAAA,CAAiB,QAAjB,CAAwBT,CAAxB,CAAlB,GAAqDA,CAArD,GACM3B,CAAAqC,QAEJ;AAFqBrC,CAAAqC,QAAA,CAAgBtC,CAAAuC,UAAA,CAAe,CAAf,CAAkBX,CAAlB,CAAhB,CAErB,CADA5B,CACA,CADOA,CAAAuC,UAAA,CAAeX,CAAf,CAAuB,CAAvB,CACP,CAAA1C,CAAA,CAAQ,CAAA,CAHV,CAJF,KAUO,IAAIsD,CAAAC,KAAA,CAAoBzC,CAApB,CAAJ,CAGL,IAFAmB,CAEA,CAFQnB,CAAAmB,MAAA,CAAWqB,CAAX,CAER,CACExC,CACA,CADOA,CAAAiB,QAAA,CAAaE,CAAA,CAAM,CAAN,CAAb,CAAuB,EAAvB,CACP,CAAAjC,CAAA,CAAQ,CAAA,CAFV,CAHK,IAQA,IAAIwD,CAAAD,KAAA,CAA4BzC,CAA5B,CAAJ,CAGL,IAFAmB,CAEA,CAFQnB,CAAAmB,MAAA,CAAWwB,CAAX,CAER,CACE3C,CAEA,CAFOA,CAAAuC,UAAA,CAAepB,CAAA,CAAM,CAAN,CAAArB,OAAf,CAEP,CADAqB,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAiB0B,CAAjB,CAAiC/B,CAAjC,CACA,CAAA1B,CAAA,CAAQ,CAAA,CAHV,CAHK,IAUI0D,EAAAH,KAAA,CAAsBzC,CAAtB,CAAJ,GAGL,CAFAmB,CAEA,CAFQnB,CAAAmB,MAAA,CAAW0B,CAAX,CAER,GAEM1B,CAAA,CAAM,CAAN,CAIJ,GAHEnB,CACA,CADOA,CAAAuC,UAAA,CAAepB,CAAA,CAAM,CAAN,CAAArB,OAAf,CACP,CAAAqB,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAiB4B,CAAjB,CAAmC3C,CAAnC,CAEF,EAAAhB,CAAA,CAAQ,CAAA,CANV,GASE2C,CACA,EADQ,GACR,CAAA7B,CAAA,CAAOA,CAAAuC,UAAA,CAAe,CAAf,CAVT,CAHK,CAiBHrD,EAAJ,GACE0C,CAKA,CALQ5B,CAAAoC,QAAA,CAAa,GAAb,CAKR,CAHAP,CAGA,EAHgB,CAAR,CAAAD,CAAA,CAAY5B,CAAZ,CAAmBA,CAAAuC,UAAA,CAAe,CAAf,CAAkBX,CAAlB,CAG3B,CAFA5B,CAEA,CAFe,CAAR,CAAA4B,CAAA,CAAY,EAAZ,CAAiB5B,CAAAuC,UAAA,CAAeX,CAAf,CAExB,CAAI3B,CAAAf,MAAJ,EAAmBe,CAAAf,MAAA,CAAcsC,CAAA,CAAeK,CAAf,CAAd,CANrB,CAhDmD,CAuErD,GAAI7B,CAAJ,EAAYU,CAAZ,CACE,KAAMoC,EAAA,CAAgB,UAAhB,CAC4C9C,CAD5C,CAAN,CAGFU,CAAA,CAAOV,CAhFI,CAoFbY,CAAA,EA/FiC,CA0JnCY,QAASA,EAAc,CAACuB,CAAD,CAAQ,CAC7B,GAAKA,CAAAA,CAAL,CAAc,MAAO,EAErBC,EAAAC,UAAA,CAAsBF,CAAA9B,QAAA,CAAc,IAAd;AAAmB,MAAnB,CAGtB,OAAO+B,EAAAE,YANsB,CAgB/BC,QAASA,EAAc,CAACJ,CAAD,CAAQ,CAC7B,MAAOA,EAAA9B,QAAA,CACG,IADH,CACS,OADT,CAAAA,QAAA,CAEGmC,CAFH,CAE0B,QAAQ,CAACL,CAAD,CAAQ,CAC7C,IAAIM,EAAKN,CAAAO,WAAA,CAAiB,CAAjB,CACLC,EAAAA,CAAMR,CAAAO,WAAA,CAAiB,CAAjB,CACV,OAAO,IAAP,EAAgC,IAAhC,EAAiBD,CAAjB,CAAsB,KAAtB,GAA0CE,CAA1C,CAAgD,KAAhD,EAA0D,KAA1D,EAAqE,GAHxB,CAF1C,CAAAtC,QAAA,CAOGuC,CAPH,CAO4B,QAAQ,CAACT,CAAD,CAAQ,CAC/C,MAAO,IAAP,CAAcA,CAAAO,WAAA,CAAiB,CAAjB,CAAd,CAAoC,GADW,CAP5C,CAAArC,QAAA,CAUG,IAVH,CAUS,MAVT,CAAAA,QAAA,CAWG,IAXH,CAWS,MAXT,CADsB,CAyB/B7B,QAASA,EAAkB,CAACD,CAAD,CAAMsE,CAAN,CAAoB,CAC7C,IAAIC,EAAS,CAAA,CAAb,CACIC,EAAM5E,CAAA6E,KAAA,CAAazE,CAAb,CAAkBA,CAAA4B,KAAlB,CACV,OAAO,CACLU,MAAOA,QAAQ,CAACtB,CAAD,CAAMa,CAAN,CAAaV,CAAb,CAAoB,CACjCH,CAAA,CAAMpB,CAAAwB,UAAA,CAAkBJ,CAAlB,CACDuD,EAAAA,CAAL,EAAe3B,CAAA,CAAgB5B,CAAhB,CAAf,GACEuD,CADF,CACWvD,CADX,CAGKuD,EAAL,EAAsC,CAAA,CAAtC,GAAeG,CAAA,CAAc1D,CAAd,CAAf,GACEwD,CAAA,CAAI,GAAJ,CAcA,CAbAA,CAAA,CAAIxD,CAAJ,CAaA,CAZApB,CAAA+E,QAAA,CAAgB9C,CAAhB,CAAuB,QAAQ,CAAC+B,CAAD,CAAQgB,CAAR,CAAa,CAC1C,IAAIC,EAAKjF,CAAAwB,UAAA,CAAkBwD,CAAlB,CAAT,CACIE,EAAmB,KAAnBA,GAAW9D,CAAX8D,EAAqC,KAArCA,GAA4BD,CAA5BC,EAAyD,YAAzDA;AAAgDD,CAC3B,EAAA,CAAzB,GAAIE,CAAA,CAAWF,CAAX,CAAJ,EACsB,CAAA,CADtB,GACGG,CAAA,CAASH,CAAT,CADH,EAC8B,CAAAP,CAAA,CAAaV,CAAb,CAAoBkB,CAApB,CAD9B,GAEEN,CAAA,CAAI,GAAJ,CAIA,CAHAA,CAAA,CAAII,CAAJ,CAGA,CAFAJ,CAAA,CAAI,IAAJ,CAEA,CADAA,CAAA,CAAIR,CAAA,CAAeJ,CAAf,CAAJ,CACA,CAAAY,CAAA,CAAI,GAAJ,CANF,CAH0C,CAA5C,CAYA,CAAAA,CAAA,CAAIrD,CAAA,CAAQ,IAAR,CAAe,GAAnB,CAfF,CALiC,CAD9B,CAwBLqB,IAAKA,QAAQ,CAACxB,CAAD,CAAM,CACfA,CAAA,CAAMpB,CAAAwB,UAAA,CAAkBJ,CAAlB,CACDuD,EAAL,EAAsC,CAAA,CAAtC,GAAeG,CAAA,CAAc1D,CAAd,CAAf,GACEwD,CAAA,CAAI,IAAJ,CAEA,CADAA,CAAA,CAAIxD,CAAJ,CACA,CAAAwD,CAAA,CAAI,GAAJ,CAHF,CAKIxD,EAAJ,EAAWuD,CAAX,GACEA,CADF,CACW,CAAA,CADX,CAPe,CAxBd,CAmCLxE,MAAOA,QAAQ,CAACA,CAAD,CAAQ,CACdwE,CAAL,EACEC,CAAA,CAAIR,CAAA,CAAejE,CAAf,CAAJ,CAFiB,CAnClB,CAHsC,CAzc/C,IAAI4D,EAAkB/D,CAAAqF,SAAA,CAAiB,WAAjB,CAAtB,CAyJIvB,EACG,wGA1JP,CA2JEF,EAAiB,wBA3JnB,CA4JEzB,EAAc,yEA5JhB,CA6JE0B,EAAmB,IA7JrB,CA8JEF,EAAyB,MA9J3B,CA+JER,EAAiB,qBA/JnB,CAgKEM,EAAiB,qBAhKnB;AAiKEL,EAAe,yBAjKjB,CAkKEiB,EAAwB,iCAlK1B,CAoKEI,EAA0B,gBApK5B,CA6KI1C,EAAetB,CAAA,CAAQ,wBAAR,CAIf6E,EAAAA,CAA8B7E,CAAA,CAAQ,gDAAR,CAC9B8E,EAAAA,CAA+B9E,CAAA,CAAQ,OAAR,CADnC,KAEIqB,EAAyB9B,CAAAwF,OAAA,CAAe,EAAf,CACeD,CADf,CAEeD,CAFf,CAF7B,CAOI7D,EAAgBzB,CAAAwF,OAAA,CAAe,EAAf,CAAmBF,CAAnB,CAAgD7E,CAAA,CAAQ,4KAAR,CAAhD,CAPpB,CAYImB,EAAiB5B,CAAAwF,OAAA,CAAe,EAAf,CAAmBD,CAAnB,CAAiD9E,CAAA,CAAQ,2JAAR,CAAjD,CAMjBgF;CAAAA,CAAchF,CAAA,CAAQ,oRAAR,CAMlB,KAAIuC,EAAkBvC,CAAA,CAAQ,cAAR,CAAtB,CAEIqE,EAAgB9E,CAAAwF,OAAA,CAAe,EAAf,CACezD,CADf,CAEeN,CAFf,CAGeG,CAHf,CAIeE,CAJf,CAKe2D,CALf,CAFpB,CAUIL,EAAW3E,CAAA,CAAQ,qDAAR,CAEXiF,EAAAA,CAAYjF,CAAA,CAAQ,ySAAR,CAQZkF;CAAAA,CAAWlF,CAAA,CAAQ,4vCAAR,CAiBf;IAAI0E,EAAanF,CAAAwF,OAAA,CAAe,EAAf,CACeJ,CADf,CAEeO,CAFf,CAGeD,CAHf,CAAjB,CA4KIzB,EAAU2B,QAAAC,cAAA,CAAuB,KAAvB,CA+Fd7F,EAAA8F,OAAA,CAAe,YAAf,CAA6B,EAA7B,CAAAC,SAAA,CAA0C,WAA1C,CArXAC,QAA0B,EAAG,CAC3B,IAAAC,KAAA,CAAY,CAAC,eAAD,CAAkB,QAAQ,CAACC,CAAD,CAAgB,CACpD,MAAO,SAAQ,CAACjF,CAAD,CAAO,CACpB,IAAIb,EAAM,EACVY,EAAA,CAAWC,CAAX,CAAiBZ,CAAA,CAAmBD,CAAnB,CAAwB,QAAQ,CAAC+F,CAAD,CAAMjB,CAAN,CAAe,CAC9D,MAAO,CAAC,SAAAxB,KAAA,CAAewC,CAAA,CAAcC,CAAd,CAAmBjB,CAAnB,CAAf,CADsD,CAA/C,CAAjB,CAGA,OAAO9E,EAAAI,KAAA,CAAS,EAAT,CALa,CAD8B,CAA1C,CADe,CAqX7B,CAwGAR,EAAA8F,OAAA,CAAe,YAAf,CAAAM,OAAA,CAAoC,OAApC,CAA6C,CAAC,WAAD,CAAc,QAAQ,CAACC,CAAD,CAAY,CAAA,IACzEC,EACE,yFAFuE,CAGzEC,EAAgB,WAEpB,OAAO,SAAQ,CAACzD,CAAD,CAAO0D,CAAP,CAAe,CAsB5BC,QAASA,EAAO,CAAC3D,CAAD,CAAO,CAChBA,CAAL,EAGA7B,CAAAe,KAAA,CAAU9B,CAAA,CAAa4C,CAAb,CAAV,CAJqB,CAOvB4D,QAASA,EAAO,CAACC,CAAD,CAAM7D,CAAN,CAAY,CAC1B7B,CAAAe,KAAA,CAAU,KAAV,CACIhC;CAAA4G,UAAA,CAAkBJ,CAAlB,CAAJ,EACEvF,CAAAe,KAAA,CAAU,UAAV,CACUwE,CADV,CAEU,IAFV,CAIFvF,EAAAe,KAAA,CAAU,QAAV,CACU2E,CAAAzE,QAAA,CAAY,IAAZ,CAAkB,QAAlB,CADV,CAEU,IAFV,CAGAuE,EAAA,CAAQ3D,CAAR,CACA7B,EAAAe,KAAA,CAAU,MAAV,CAX0B,CA5B5B,GAAKc,CAAAA,CAAL,CAAW,MAAOA,EAMlB,KALA,IAAIV,CAAJ,CACIyE,EAAM/D,CADV,CAEI7B,EAAO,EAFX,CAGI0F,CAHJ,CAII7F,CACJ,CAAQsB,CAAR,CAAgByE,CAAAzE,MAAA,CAAUkE,CAAV,CAAhB,CAAA,CAEEK,CAQA,CARMvE,CAAA,CAAM,CAAN,CAQN,CANKA,CAAA,CAAM,CAAN,CAML,EANkBA,CAAA,CAAM,CAAN,CAMlB,GALEuE,CAKF,EALSvE,CAAA,CAAM,CAAN,CAAA,CAAW,SAAX,CAAuB,SAKhC,EAL6CuE,CAK7C,EAHA7F,CAGA,CAHIsB,CAAAS,MAGJ,CAFA4D,CAAA,CAAQI,CAAAC,OAAA,CAAW,CAAX,CAAchG,CAAd,CAAR,CAEA,CADA4F,CAAA,CAAQC,CAAR,CAAavE,CAAA,CAAM,CAAN,CAAAF,QAAA,CAAiBqE,CAAjB,CAAgC,EAAhC,CAAb,CACA,CAAAM,CAAA,CAAMA,CAAArD,UAAA,CAAc1C,CAAd,CAAkBsB,CAAA,CAAM,CAAN,CAAArB,OAAlB,CAER0F,EAAA,CAAQI,CAAR,CACA,OAAOR,EAAA,CAAUpF,CAAAT,KAAA,CAAU,EAAV,CAAV,CApBqB,CAL+C,CAAlC,CAA7C,CA9mBsC,CAArC,CAAD,CAiqBGT,MAjqBH,CAiqBWA,MAAAC,QAjqBX;", +"sources":["angular-sanitize.js"], +"names":["window","angular","undefined","sanitizeText","chars","buf","htmlSanitizeWriter","writer","noop","join","makeMap","str","obj","items","split","i","length","htmlParser","html","handler","parseStartTag","tag","tagName","rest","unary","lowercase","blockElements","stack","last","inlineElements","parseEndTag","optionalEndTagElements","voidElements","push","attrs","replace","ATTR_REGEXP","match","name","doubleQuotedValue","singleQuotedValue","unquotedValue","decodeEntities","start","pos","end","index","text","stack.last","specialElements","RegExp","all","COMMENT_REGEXP","CDATA_REGEXP","indexOf","lastIndexOf","comment","substring","DOCTYPE_REGEXP","test","BEGING_END_TAGE_REGEXP","END_TAG_REGEXP","BEGIN_TAG_REGEXP","START_TAG_REGEXP","$sanitizeMinErr","value","hiddenPre","innerHTML","textContent","encodeEntities","SURROGATE_PAIR_REGEXP","hi","charCodeAt","low","NON_ALPHANUMERIC_REGEXP","uriValidator","ignore","out","bind","validElements","forEach","key","lkey","isImage","validAttrs","uriAttrs","$$minErr","optionalEndTagBlockElements","optionalEndTagInlineElements","extend","svgElements","htmlAttrs","svgAttrs","document","createElement","module","provider","$SanitizeProvider","$get","$$sanitizeUri","uri","filter","$sanitize","LINKY_URL_REGEXP","MAILTO_REGEXP","target","addText","addLink","url","isDefined","raw","substr"] +} diff --git a/vendor/assets/components/angular-sanitize/bower.json b/vendor/assets/components/angular-sanitize/bower.json new file mode 100644 index 000000000..81659fd06 --- /dev/null +++ b/vendor/assets/components/angular-sanitize/bower.json @@ -0,0 +1,9 @@ +{ + "name": "angular-sanitize", + "version": "1.3.20", + "main": "./angular-sanitize.js", + "ignore": [], + "dependencies": { + "angular": "1.3.20" + } +} diff --git a/vendor/assets/components/angular-sanitize/index.js b/vendor/assets/components/angular-sanitize/index.js new file mode 100644 index 000000000..dd5d22e4a --- /dev/null +++ b/vendor/assets/components/angular-sanitize/index.js @@ -0,0 +1,2 @@ +require('./angular-sanitize'); +module.exports = 'ngSanitize'; diff --git a/vendor/assets/components/angular-sanitize/package.json b/vendor/assets/components/angular-sanitize/package.json new file mode 100644 index 000000000..2c89248cc --- /dev/null +++ b/vendor/assets/components/angular-sanitize/package.json @@ -0,0 +1,26 @@ +{ + "name": "angular-sanitize", + "version": "1.3.20", + "description": "AngularJS module for sanitizing HTML", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/angular/angular.js.git" + }, + "keywords": [ + "angular", + "framework", + "browser", + "html", + "client-side" + ], + "author": "Angular Core Team ", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/angular.js/issues" + }, + "homepage": "http://angularjs.org" +} diff --git a/vendor/assets/components/angular-summernote/.bower.json b/vendor/assets/components/angular-summernote/.bower.json index d6b0c9943..713689229 100644 --- a/vendor/assets/components/angular-summernote/.bower.json +++ b/vendor/assets/components/angular-summernote/.bower.json @@ -1,7 +1,7 @@ { "name": "angular-summernote", "description": "AngularJS directive to Summernote", - "version": "0.3.2", + "version": "0.7.1", "main": [ "./dist/angular-summernote.js" ], @@ -38,23 +38,25 @@ "url": "git://github.com/summernote/angular-summernote.git" }, "dependencies": { - "summernote": "~0.6.0", - "angular": "~1.3.0" + "summernote": "~0.7.0", + "angular": "~1.4.0" }, "devDependencies": { "angular-1.2": "https://raw.githubusercontent.com/angular/bower-angular/v1.2.26/angular.min.js", - "chai": "~1.10.0", - "angular-mocks": "~1.3.0", - "angular-mocks-1.2": "https://raw.githubusercontent.com/angular/bower-angular-mocks/v1.2.26/angular-mocks.js" + "angular-1.3": "https://raw.githubusercontent.com/angular/bower-angular/v1.3.17/angular.min.js", + "chai": "~2.3.0", + "angular-mocks": "~1.4.0", + "angular-mocks-1.2": "https://raw.githubusercontent.com/angular/bower-angular-mocks/v1.2.26/angular-mocks.js", + "angular-mocks-1.3": "https://raw.githubusercontent.com/angular/bower-angular-mocks/v1.3.17/angular-mocks.js" }, "homepage": "https://github.com/summernote/angular-summernote", - "_release": "0.3.2", + "_release": "0.7.1", "_resolution": { "type": "version", - "tag": "0.3.2", - "commit": "0e329e681879af08bea0c776e227b53fb81c8657" + "tag": "0.7.1", + "commit": "e94e2fb2182a655b4a48ee6a0edd779cda37a14a" }, "_source": "git://github.com/summernote/angular-summernote.git", - "_target": "~0.3.2", + "_target": "~0.7.1", "_originalSource": "angular-summernote" } \ No newline at end of file diff --git a/vendor/assets/components/angular-summernote/CHANGELOG.md b/vendor/assets/components/angular-summernote/CHANGELOG.md index 0c90e9ab7..a5f97cfca 100644 --- a/vendor/assets/components/angular-summernote/CHANGELOG.md +++ b/vendor/assets/components/angular-summernote/CHANGELOG.md @@ -1,3 +1,46 @@ +# 0.7.1 (2016-01-22) +* Fix a bug that load 2 editor on IE(it is a workaround) + [#98](https://github.com/summernote/angular-summernote/issues/98) +* Fix a bug when content is empty + [#105](https://github.com/summernote/angular-summernote/pull/105) +* Support placeholder, min height and max height options + [#97](https://github.com/summernote/angular-summernote/pull/97), + [#104](https://github.com/summernote/angular-summernote/pull/104) +* Supoort on-media-delete callback + [#92](https://github.com/summernote/angular-summernote/issues/92) + +# 0.7.0 (2015-12-11) +* Make compatible with summernote v0.7.0 + +# 0.5.2 (2015-11-29) +* fix a broken ngModel binding with angular 1.3 + [#84](https://github.com/summernote/angular-summernote/issues/84) + +# 0.5.1 (2015-10-05) +* Support initial text from inner markup in directive + [#77](https://github.com/summernote/angular-summernote/issues/77) + +# 0.5.0 (2015-08-19) +* Support [AngularJS 1.4.x](http://angularjs.blogspot.kr/2015/05/angular-140-jaracimrman-existence.html) + +# 0.4.2 (2015-08-19) +* bug fixes + * fix "Maximum call stack size exceeded" error in airmode + [#62](https://github.com/summernote/angular-summernote/issues/62) + * clean ngModel when content is empty + [#53](https://github.com/summernote/angular-summernote/issues/53) + +# 0.4.0 (2015-05-25) +## Breaking changes +* Support Summernote v0.6.4+. It's not compatible with the version under v0.6.4. + If you use summernote v0.6.3-, use angular-summernote v0.3.2. +* Now, editor object exposed via `editor` attribute. + +## Features +* Support `ngModelOptions` +* Support `onToolbarClick` event +* Publish in npm registry + # 0.3.2 (2015-02-13) * bug fixes * fix to avoid inprog error with outer scope diff --git a/vendor/assets/components/angular-summernote/README.md b/vendor/assets/components/angular-summernote/README.md index 3f0f84dac..fa5e9d58f 100644 --- a/vendor/assets/components/angular-summernote/README.md +++ b/vendor/assets/components/angular-summernote/README.md @@ -2,7 +2,6 @@ *** -[![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/) [![Build Status](https://travis-ci.org/summernote/angular-summernote.png?branch=master)](https://travis-ci.org/summernote/angular-summernote) [![Dependency Status](https://gemnasium.com/summernote/angular-summernote.png)](https://gemnasium.com/summernote/angular-summernote) [![Coverage Status](https://coveralls.io/repos/summernote/angular-summernote/badge.png)](https://coveralls.io/r/summernote/angular-summernote) @@ -10,6 +9,13 @@ angular-summernote is just a directive to bind summmernote's all features. You can use summernote with angular way. +**Since v0.7.x, the version of angular-summernote follows the version of summernote. +So, angular-summernote v0.7.x are compatible with summernote v0.7.x and +and angular-summernote v0.8.x will be compatible with summernote v0.8.x. +Angular-summernote will match only `major.minor` with summernote. +Therefore, angular-summernote v0.7.0 will be compatible with summernote v0.7.0, v0.7.1 and +v0.7.2. Angular-summernote will release patch update, such as v0.7.1, if only angular-summernote has changed.** + ## Table of Contents - [Demo](#demo) @@ -25,7 +31,7 @@ You can use summernote with angular way. ## Demo -See at [JSFiddle](http://jsfiddle.net/outsider/n8dt4/158/embedded/result%2Chtml%2Cjs%2Ccss/) +See at [JSFiddle](http://jsfiddle.net/outsider/n8dt4/271/embedded/result%2Chtml%2Cjs%2Ccss/) or run example in projects(need to run `bower install` before run) ## Installation @@ -65,6 +71,12 @@ And when the scope is destroyed the directive will be destroyed. It will be initialized automatically. +If you put markups in the directive, the markups used as initial text. + +```html +This is initial text. +``` + ### Options summernote's options can be specified as attributes. @@ -86,6 +98,39 @@ summernote's options can be specified as attributes. ``` +If you use the `removeMedia` button in popover, like below: + +``` + +``` + +``` +function DemoController($scope) { + $scope.options = { + popover: { + image: [['remove', ['removeMedia']] ], + air: [['insert', ['picture']]] + } + }; + $scope.mediaDelete = function(target) { + console.log('media is delted:', target); + } +} +``` + +You can use the 'onMediaDelete` callback. The `target` object has information of the DOM that is removed like: + +``` +{ + tagName: "IMG", + attrs: { + data-filename: "image-name.jpg", + src: "http://path/to/image", + style: "width: 100px;" + } +} +``` + #### options object You can specify all options using ngModel in `config` attribute. @@ -138,6 +183,10 @@ function DemoController($scope) { } ``` +And you can use [ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions) +with Angular v1.3+. So, you can update ngModel when blur event emitted or with a debouncing delay +if you want. + ### Event Listeners event listeners can be registered as attribute as you want. @@ -154,8 +203,8 @@ function DemoController($scope) { }; $scope.keyup = function(e) { console.log('Key is released:', e.keyCode); } $scope.keydown = function(e) { console.log('Key is pressed:', e.keyCode); } - $scope.imageUpload = function(files, editor) { - console.log('image upload:', files, editor); + $scope.imageUpload = function(files) { + console.log('image upload:', files); console.log('image upload\'s editable:', $scope.editable); } } @@ -165,15 +214,20 @@ function DemoController($scope) { + on-image-upload="imageUpload(files)" editable="editable" editor="editor"> ``` If you use `$editable` object in `onImageUpload` or `onChange` (see [summernote's callback](http://summernote.org/#/features#callbacks)), -you should defined `editable` attribute and use it in `$scope`. +you should define `editable` attribute and use it in `$scope`. (Because [AngularJS 1.3.x restricts access to DOM nodes from within expressions](https://docs.angularjs.org/error/$parse/isecdom)) +Since summernote v0.6.4, APIs have been changed. So, If you use the verions, +`onImageUpload` is not return `editor` object anymore. If you want to user +`editor` object, you should define `editor` attribute and use it in `$scope`. +Futhermore, you can use summernote's APIs via the `editor` object. + ### i18n Support If you use i18n, you have to include language files. diff --git a/vendor/assets/components/angular-summernote/bower.json b/vendor/assets/components/angular-summernote/bower.json index 011fa0ddd..6db88541d 100644 --- a/vendor/assets/components/angular-summernote/bower.json +++ b/vendor/assets/components/angular-summernote/bower.json @@ -1,7 +1,7 @@ { "name": "angular-summernote", "description": "AngularJS directive to Summernote", - "version": "0.3.2", + "version": "0.7.1", "main": [ "./dist/angular-summernote.js" ], @@ -33,13 +33,15 @@ "url": "git://github.com/summernote/angular-summernote.git" }, "dependencies": { - "summernote": "~0.6.0", - "angular": "~1.3.0" + "summernote": "~0.7.0", + "angular": "~1.4.0" }, "devDependencies": { "angular-1.2": "https://raw.githubusercontent.com/angular/bower-angular/v1.2.26/angular.min.js", - "chai": "~1.10.0", - "angular-mocks": "~1.3.0", - "angular-mocks-1.2": "https://raw.githubusercontent.com/angular/bower-angular-mocks/v1.2.26/angular-mocks.js" + "angular-1.3": "https://raw.githubusercontent.com/angular/bower-angular/v1.3.17/angular.min.js", + "chai": "~2.3.0", + "angular-mocks": "~1.4.0", + "angular-mocks-1.2": "https://raw.githubusercontent.com/angular/bower-angular-mocks/v1.2.26/angular-mocks.js", + "angular-mocks-1.3": "https://raw.githubusercontent.com/angular/bower-angular-mocks/v1.3.17/angular-mocks.js" } } diff --git a/vendor/assets/components/angular-summernote/dist/angular-summernote.js b/vendor/assets/components/angular-summernote/dist/angular-summernote.js index a7f552f21..dfb426a6b 100644 --- a/vendor/assets/components/angular-summernote/dist/angular-summernote.js +++ b/vendor/assets/components/angular-summernote/dist/angular-summernote.js @@ -1,8 +1,4 @@ -/** - * Copyright (c) 2013 JeongHoon Byun aka "Outsider", - * Licensed under the MIT license. - * - */ +/* angular-summernote v0.7.1 | (c) 2016 JeongHoon Byun | MIT license */ /* global angular */ angular.module('summernote', []) @@ -12,7 +8,10 @@ angular.module('summernote', []) var currentElement, summernoteConfig = $scope.summernoteConfig || {}; - if (angular.isDefined($attrs.height)) { summernoteConfig.height = $attrs.height; } + if (angular.isDefined($attrs.height)) { summernoteConfig.height = +$attrs.height; } + if (angular.isDefined($attrs.minHeight)) { summernoteConfig.minHeight = +$attrs.minHeight; } + if (angular.isDefined($attrs.maxHeight)) { summernoteConfig.maxHeight = +$attrs.maxHeight; } + if (angular.isDefined($attrs.placeholder)) { summernoteConfig.placeholder = $attrs.placeholder; } if (angular.isDefined($attrs.focus)) { summernoteConfig.focus = true; } if (angular.isDefined($attrs.airmode)) { summernoteConfig.airMode = true; } if (angular.isDefined($attrs.lang)) { @@ -22,22 +21,34 @@ angular.module('summernote', []) summernoteConfig.lang = $attrs.lang; } - summernoteConfig.oninit = $scope.init; - summernoteConfig.onenter = function(evt) { $scope.enter({evt:evt}); }; - summernoteConfig.onfocus = function(evt) { $scope.focus({evt:evt}); }; - summernoteConfig.onblur = function(evt) { $scope.blur({evt:evt}); }; - summernoteConfig.onpaste = function(evt) { $scope.paste({evt:evt}); }; - summernoteConfig.onkeyup = function(evt) { $scope.keyup({evt:evt}); }; - summernoteConfig.onkeydown = function(evt) { $scope.keydown({evt:evt}); }; + var callbacks = {}; + callbacks.onInit = $scope.init; + callbacks.onEnter = function(evt) { $scope.enter({evt:evt}); }; + callbacks.onFocus = function(evt) { $scope.focus({evt:evt}); }; + callbacks.onPaste = function(evt) { $scope.paste({evt:evt}); }; + callbacks.onKeyup = function(evt) { $scope.keyup({evt:evt}); }; + callbacks.onKeydown = function(evt) { $scope.keydown({evt:evt}); }; if (angular.isDefined($attrs.onImageUpload)) { - summernoteConfig.onImageUpload = function(files, editor) { - $scope.imageUpload({files:files, editor:editor, editable: $scope.editable}); + callbacks.onImageUpload = function(files) { + $scope.imageUpload({files:files, editable: $scope.editable}); }; } + if (angular.isDefined($attrs.onMediaDelete)) { + callbacks.onMediaDelete = function(target) { + // make new object that has information of target to avoid error:isecdom + var removedMedia = {attrs: {}}; + removedMedia.tagName = target[0].tagName; + angular.forEach(target[0].attributes, function(attr) { + removedMedia.attrs[attr.name] = attr.value; + }); + $scope.mediaDelete({target: removedMedia}); + } + } this.activate = function(scope, element, ngModel) { var updateNgModel = function() { - var newValue = element.code(); + var newValue = element.summernote('code'); + if (element.summernote('isEmpty')) { newValue = ''; } if (ngModel && ngModel.$viewValue !== newValue) { $timeout(function() { ngModel.$setViewValue(newValue); @@ -45,11 +56,18 @@ angular.module('summernote', []) } }; - summernoteConfig.onChange = function(contents) { - updateNgModel(); + callbacks.onChange = function(contents) { + $timeout(function() { + if (element.summernote('isEmpty')) { contents = ''; } + updateNgModel(); + }, 0); $scope.change({contents:contents, editable: $scope.editable}); }; - + callbacks.onBlur = function(evt) { + (!summernoteConfig.airMode) && element.blur(); + $scope.blur({evt:evt}); + }; + summernoteConfig.callbacks = callbacks; element.summernote(summernoteConfig); var editor$ = element.next('.note-editor'), @@ -77,7 +95,11 @@ angular.module('summernote', []) if (ngModel) { ngModel.$render = function() { - element.code(ngModel.$viewValue || ''); + if (ngModel.$viewValue) { + element.summernote('code', ngModel.$viewValue); + } else { + element.summernote('empty'); + } }; } @@ -85,11 +107,14 @@ angular.module('summernote', []) if (angular.isDefined($attrs.editable)) { $scope.editable = editor$.find('.note-editable'); } + if (angular.isDefined($attrs.editor)) { + $scope.editor = element; + } currentElement = element; // use jquery Event binding instead $on('$destroy') to preserve options data of DOM element.on('$destroy', function() { - element.destroy(); + element.summernote('destroy'); $scope.summernoteDestroyed = true; }); }; @@ -97,7 +122,7 @@ angular.module('summernote', []) $scope.$on('$destroy', function () { // when destroying scope directly if (!$scope.summernoteDestroyed) { - currentElement.destroy(); + currentElement.summernote('destroy'); } }); }]) @@ -106,13 +131,14 @@ angular.module('summernote', []) return { restrict: 'EA', - transclude: true, + transclude: 'element', replace: true, - require: ['summernote', '^?ngModel'], + require: ['summernote', '?ngModel'], controller: 'SummernoteController', scope: { summernoteConfig: '=config', editable: '=', + editor: '=', init: '&onInit', enter: '&onEnter', focus: '&onFocus', @@ -121,13 +147,19 @@ angular.module('summernote', []) keyup: '&onKeyup', keydown: '&onKeydown', change: '&onChange', - imageUpload: '&onImageUpload' + imageUpload: '&onImageUpload', + mediaDelete: '&onMediaDelete' }, template: '
    ', - link: function(scope, element, attrs, ctrls) { + link: function(scope, element, attrs, ctrls, transclude) { var summernoteController = ctrls[0], ngModel = ctrls[1]; + transclude(scope, function(clone, scope) { + // to prevent binding to angular scope (It require `tranclude: 'element'`) + element.append(clone.html()); + }); + summernoteController.activate(scope, element, ngModel); } }; diff --git a/vendor/assets/components/angular-summernote/dist/angular-summernote.min.js b/vendor/assets/components/angular-summernote/dist/angular-summernote.min.js index f9f74a658..e4b185967 100644 --- a/vendor/assets/components/angular-summernote/dist/angular-summernote.min.js +++ b/vendor/assets/components/angular-summernote/dist/angular-summernote.min.js @@ -1,6 +1,2 @@ -/* - angular-summernote v0.3.2 - Copyright 2014 Jeonghoon Byun - License: MIT - */ -angular.module("summernote",[]).controller("SummernoteController",["$scope","$attrs","$timeout",function($scope,$attrs,$timeout){"use strict";var currentElement,summernoteConfig=$scope.summernoteConfig||{};if(angular.isDefined($attrs.height)&&(summernoteConfig.height=$attrs.height),angular.isDefined($attrs.focus)&&(summernoteConfig.focus=!0),angular.isDefined($attrs.airmode)&&(summernoteConfig.airMode=!0),angular.isDefined($attrs.lang)){if(!angular.isDefined($.summernote.lang[$attrs.lang]))throw new Error('"'+$attrs.lang+'" lang file must be exist.');summernoteConfig.lang=$attrs.lang}summernoteConfig.oninit=$scope.init,summernoteConfig.onenter=function(evt){$scope.enter({evt:evt})},summernoteConfig.onfocus=function(evt){$scope.focus({evt:evt})},summernoteConfig.onblur=function(evt){$scope.blur({evt:evt})},summernoteConfig.onpaste=function(evt){$scope.paste({evt:evt})},summernoteConfig.onkeyup=function(evt){$scope.keyup({evt:evt})},summernoteConfig.onkeydown=function(evt){$scope.keydown({evt:evt})},angular.isDefined($attrs.onImageUpload)&&(summernoteConfig.onImageUpload=function(files,editor){$scope.imageUpload({files:files,editor:editor,editable:$scope.editable})}),this.activate=function(scope,element,ngModel){var updateNgModel=function(){var newValue=element.code();ngModel&&ngModel.$viewValue!==newValue&&$timeout(function(){ngModel.$setViewValue(newValue)},0)};summernoteConfig.onChange=function(contents){updateNgModel(),$scope.change({contents:contents,editable:$scope.editable})},element.summernote(summernoteConfig);var unwatchNgModel,editor$=element.next(".note-editor");editor$.find(".note-toolbar").click(function(){updateNgModel(),editor$.hasClass("codeview")?(editor$.on("keyup",updateNgModel),ngModel&&(unwatchNgModel=scope.$watch(function(){return ngModel.$modelValue},function(newValue){editor$.find(".note-codable").val(newValue)}))):(editor$.off("keyup",updateNgModel),angular.isFunction(unwatchNgModel)&&unwatchNgModel())}),ngModel&&(ngModel.$render=function(){element.code(ngModel.$viewValue||"")}),angular.isDefined($attrs.editable)&&($scope.editable=editor$.find(".note-editable")),currentElement=element,element.on("$destroy",function(){element.destroy(),$scope.summernoteDestroyed=!0})},$scope.$on("$destroy",function(){$scope.summernoteDestroyed||currentElement.destroy()})}]).directive("summernote",[function(){"use strict";return{restrict:"EA",transclude:!0,replace:!0,require:["summernote","^?ngModel"],controller:"SummernoteController",scope:{summernoteConfig:"=config",editable:"=",init:"&onInit",enter:"&onEnter",focus:"&onFocus",blur:"&onBlur",paste:"&onPaste",keyup:"&onKeyup",keydown:"&onKeydown",change:"&onChange",imageUpload:"&onImageUpload"},template:'
    ',link:function(scope,element,attrs,ctrls){var summernoteController=ctrls[0],ngModel=ctrls[1];summernoteController.activate(scope,element,ngModel)}}}]); \ No newline at end of file +/* angular-summernote v0.7.1 | (c) 2016 JeongHoon Byun | MIT license */ +angular.module("summernote",[]).controller("SummernoteController",["$scope","$attrs","$timeout",function($scope,$attrs,$timeout){"use strict";var currentElement,summernoteConfig=$scope.summernoteConfig||{};if(angular.isDefined($attrs.height)&&(summernoteConfig.height=+$attrs.height),angular.isDefined($attrs.minHeight)&&(summernoteConfig.minHeight=+$attrs.minHeight),angular.isDefined($attrs.maxHeight)&&(summernoteConfig.maxHeight=+$attrs.maxHeight),angular.isDefined($attrs.placeholder)&&(summernoteConfig.placeholder=$attrs.placeholder),angular.isDefined($attrs.focus)&&(summernoteConfig.focus=!0),angular.isDefined($attrs.airmode)&&(summernoteConfig.airMode=!0),angular.isDefined($attrs.lang)){if(!angular.isDefined($.summernote.lang[$attrs.lang]))throw new Error('"'+$attrs.lang+'" lang file must be exist.');summernoteConfig.lang=$attrs.lang}var callbacks={};callbacks.onInit=$scope.init,callbacks.onEnter=function(evt){$scope.enter({evt:evt})},callbacks.onFocus=function(evt){$scope.focus({evt:evt})},callbacks.onPaste=function(evt){$scope.paste({evt:evt})},callbacks.onKeyup=function(evt){$scope.keyup({evt:evt})},callbacks.onKeydown=function(evt){$scope.keydown({evt:evt})},angular.isDefined($attrs.onImageUpload)&&(callbacks.onImageUpload=function(files){$scope.imageUpload({files:files,editable:$scope.editable})}),angular.isDefined($attrs.onMediaDelete)&&(callbacks.onMediaDelete=function(target){var removedMedia={attrs:{}};removedMedia.tagName=target[0].tagName,angular.forEach(target[0].attributes,function(attr){removedMedia.attrs[attr.name]=attr.value}),$scope.mediaDelete({target:removedMedia})}),this.activate=function(scope,element,ngModel){var updateNgModel=function(){var newValue=element.summernote("code");element.summernote("isEmpty")&&(newValue=""),ngModel&&ngModel.$viewValue!==newValue&&$timeout(function(){ngModel.$setViewValue(newValue)},0)};callbacks.onChange=function(contents){$timeout(function(){element.summernote("isEmpty")&&(contents=""),updateNgModel()},0),$scope.change({contents:contents,editable:$scope.editable})},callbacks.onBlur=function(evt){!summernoteConfig.airMode&&element.blur(),$scope.blur({evt:evt})},summernoteConfig.callbacks=callbacks,element.summernote(summernoteConfig);var unwatchNgModel,editor$=element.next(".note-editor");editor$.find(".note-toolbar").click(function(){updateNgModel(),editor$.hasClass("codeview")?(editor$.on("keyup",updateNgModel),ngModel&&(unwatchNgModel=scope.$watch(function(){return ngModel.$modelValue},function(newValue){editor$.find(".note-codable").val(newValue)}))):(editor$.off("keyup",updateNgModel),angular.isFunction(unwatchNgModel)&&unwatchNgModel())}),ngModel&&(ngModel.$render=function(){ngModel.$viewValue?element.summernote("code",ngModel.$viewValue):element.summernote("empty")}),angular.isDefined($attrs.editable)&&($scope.editable=editor$.find(".note-editable")),angular.isDefined($attrs.editor)&&($scope.editor=element),currentElement=element,element.on("$destroy",function(){element.summernote("destroy"),$scope.summernoteDestroyed=!0})},$scope.$on("$destroy",function(){$scope.summernoteDestroyed||currentElement.summernote("destroy")})}]).directive("summernote",[function(){"use strict";return{restrict:"EA",transclude:"element",replace:!0,require:["summernote","?ngModel"],controller:"SummernoteController",scope:{summernoteConfig:"=config",editable:"=",editor:"=",init:"&onInit",enter:"&onEnter",focus:"&onFocus",blur:"&onBlur",paste:"&onPaste",keyup:"&onKeyup",keydown:"&onKeydown",change:"&onChange",imageUpload:"&onImageUpload",mediaDelete:"&onMediaDelete"},template:'
    ',link:function(scope,element,attrs,ctrls,transclude){var summernoteController=ctrls[0],ngModel=ctrls[1];transclude(scope,function(clone,scope){element.append(clone.html())}),summernoteController.activate(scope,element,ngModel)}}}]); \ No newline at end of file diff --git a/vendor/assets/components/angular-summernote/gulpfile.js b/vendor/assets/components/angular-summernote/gulpfile.js new file mode 100644 index 000000000..7f2bc7409 --- /dev/null +++ b/vendor/assets/components/angular-summernote/gulpfile.js @@ -0,0 +1,107 @@ +var gulp = require('gulp'), + jshint = require('gulp-jshint'), + uglify = require('gulp-uglify'), + rename = require("gulp-rename"), + header = require("gulp-header"), + Server = require('karma').Server, + coveralls = require('gulp-coveralls'), + del = require('del'), + nugetpack = require('gulp-nuget-pack'), + pkg = require('./package.json'); + +var banner = '/* angular-summernote v<%=pkg.version%> | (c) 2016 JeongHoon Byun | MIT license */\n'; +var isAngular12 = isAngular13 = false; + +gulp.task('lint', function() { + return gulp.src(['./src/**/*.js', './test/**/*.test.js']) + .pipe(jshint()) + .pipe(jshint.reporter('jshint-stylish')); +}); + +gulp.task('copy', function() { + return gulp.src('./src/angular-summernote.js') + .pipe(header(banner, {pkg: pkg})) + .pipe(gulp.dest('dist')); +}); + +gulp.task('build', ['copy'], function() { + return gulp.src('./src/angular-summernote.js') + .pipe(uglify({mangle: false})) + .pipe(rename({extname: '.min.js'})) + .pipe(header(banner, {pkg: pkg})) + .pipe(gulp.dest('dist')); +}); + +gulp.task('karma', function (done) { + var configFile = '/test/karma.conf.js'; + if (isAngular12) { configFile = '/test/karma-angular-1-2-x.conf.js'; } + if (isAngular13) { configFile = '/test/karma-angular-1-3-x.conf.js'; } + + if (!process.env.CI) { + new Server({ + configFile: __dirname + configFile, + autoWatch: true + }, done).start(); + } else { + new Server({ + configFile: __dirname + configFile, + browsers: ['PhantomJS'], + singleRun: true + }, done).start(); + } +}); + +gulp.task('test', function() { + gulp.start('karma'); +}); + +gulp.task('test:angular12', function() { + isAngular12 = true; + gulp.start('karma'); +}); + +gulp.task('test:angular13', function() { + isAngular13 = true; + gulp.start('karma'); +}); + +gulp.task('test:coverage', function(done) { + var configFile = '/test/karma.conf.js'; + new Server({ + configFile: __dirname + configFile, + singleRun: true, + browsers: ['PhantomJS'], + reporters: ['progress', 'coverage'], + preprocessors: { '../**/src/**/*.js': 'coverage' }, + coverageReporter: { type: 'lcov', dir: '../coverage/' }, + plugins: [ 'karma-*' ] + }, done).start(); +}); + +gulp.task('clean:coverage', function () { + return del([ + 'coverage' + ]); +}); + +gulp.task('coveralls', ['clean:coverage', 'test:coverage'], function() { + return gulp.src('./coverage/**/lcov.info') + .pipe(coveralls()); +}); + +gulp.task('travis', ['test', 'test:angular12', 'test:angular13'], function() { +}); + +gulp.task('nuget-pack', function(done) { + nugetpack({ + id: "Angular.Summernote", + version: pkg.version, + authors: pkg.author.name, + description: pkg.description, + projectUrl: pkg.homepage, + licenseUrl: "https://github.com/summernote/angular-summernote/blob/master/LICENSE-MIT", + copyright: "MIT", + tags: pkg.keywords.join(' '), + outputDir: "out" + }, ['dist/*.js', 'README.md' ], done); +}); diff --git a/vendor/assets/components/angular-summernote/src/angular-summernote.js b/vendor/assets/components/angular-summernote/src/angular-summernote.js index a7f552f21..072ea7336 100644 --- a/vendor/assets/components/angular-summernote/src/angular-summernote.js +++ b/vendor/assets/components/angular-summernote/src/angular-summernote.js @@ -1,8 +1,3 @@ -/** - * Copyright (c) 2013 JeongHoon Byun aka "Outsider", - * Licensed under the MIT license. - * - */ /* global angular */ angular.module('summernote', []) @@ -12,7 +7,10 @@ angular.module('summernote', []) var currentElement, summernoteConfig = $scope.summernoteConfig || {}; - if (angular.isDefined($attrs.height)) { summernoteConfig.height = $attrs.height; } + if (angular.isDefined($attrs.height)) { summernoteConfig.height = +$attrs.height; } + if (angular.isDefined($attrs.minHeight)) { summernoteConfig.minHeight = +$attrs.minHeight; } + if (angular.isDefined($attrs.maxHeight)) { summernoteConfig.maxHeight = +$attrs.maxHeight; } + if (angular.isDefined($attrs.placeholder)) { summernoteConfig.placeholder = $attrs.placeholder; } if (angular.isDefined($attrs.focus)) { summernoteConfig.focus = true; } if (angular.isDefined($attrs.airmode)) { summernoteConfig.airMode = true; } if (angular.isDefined($attrs.lang)) { @@ -22,22 +20,34 @@ angular.module('summernote', []) summernoteConfig.lang = $attrs.lang; } - summernoteConfig.oninit = $scope.init; - summernoteConfig.onenter = function(evt) { $scope.enter({evt:evt}); }; - summernoteConfig.onfocus = function(evt) { $scope.focus({evt:evt}); }; - summernoteConfig.onblur = function(evt) { $scope.blur({evt:evt}); }; - summernoteConfig.onpaste = function(evt) { $scope.paste({evt:evt}); }; - summernoteConfig.onkeyup = function(evt) { $scope.keyup({evt:evt}); }; - summernoteConfig.onkeydown = function(evt) { $scope.keydown({evt:evt}); }; + var callbacks = {}; + callbacks.onInit = $scope.init; + callbacks.onEnter = function(evt) { $scope.enter({evt:evt}); }; + callbacks.onFocus = function(evt) { $scope.focus({evt:evt}); }; + callbacks.onPaste = function(evt) { $scope.paste({evt:evt}); }; + callbacks.onKeyup = function(evt) { $scope.keyup({evt:evt}); }; + callbacks.onKeydown = function(evt) { $scope.keydown({evt:evt}); }; if (angular.isDefined($attrs.onImageUpload)) { - summernoteConfig.onImageUpload = function(files, editor) { - $scope.imageUpload({files:files, editor:editor, editable: $scope.editable}); + callbacks.onImageUpload = function(files) { + $scope.imageUpload({files:files, editable: $scope.editable}); }; } + if (angular.isDefined($attrs.onMediaDelete)) { + callbacks.onMediaDelete = function(target) { + // make new object that has information of target to avoid error:isecdom + var removedMedia = {attrs: {}}; + removedMedia.tagName = target[0].tagName; + angular.forEach(target[0].attributes, function(attr) { + removedMedia.attrs[attr.name] = attr.value; + }); + $scope.mediaDelete({target: removedMedia}); + } + } this.activate = function(scope, element, ngModel) { var updateNgModel = function() { - var newValue = element.code(); + var newValue = element.summernote('code'); + if (element.summernote('isEmpty')) { newValue = ''; } if (ngModel && ngModel.$viewValue !== newValue) { $timeout(function() { ngModel.$setViewValue(newValue); @@ -45,11 +55,18 @@ angular.module('summernote', []) } }; - summernoteConfig.onChange = function(contents) { - updateNgModel(); + callbacks.onChange = function(contents) { + $timeout(function() { + if (element.summernote('isEmpty')) { contents = ''; } + updateNgModel(); + }, 0); $scope.change({contents:contents, editable: $scope.editable}); }; - + callbacks.onBlur = function(evt) { + (!summernoteConfig.airMode) && element.blur(); + $scope.blur({evt:evt}); + }; + summernoteConfig.callbacks = callbacks; element.summernote(summernoteConfig); var editor$ = element.next('.note-editor'), @@ -77,7 +94,11 @@ angular.module('summernote', []) if (ngModel) { ngModel.$render = function() { - element.code(ngModel.$viewValue || ''); + if (ngModel.$viewValue) { + element.summernote('code', ngModel.$viewValue); + } else { + element.summernote('empty'); + } }; } @@ -85,11 +106,14 @@ angular.module('summernote', []) if (angular.isDefined($attrs.editable)) { $scope.editable = editor$.find('.note-editable'); } + if (angular.isDefined($attrs.editor)) { + $scope.editor = element; + } currentElement = element; // use jquery Event binding instead $on('$destroy') to preserve options data of DOM element.on('$destroy', function() { - element.destroy(); + element.summernote('destroy'); $scope.summernoteDestroyed = true; }); }; @@ -97,7 +121,7 @@ angular.module('summernote', []) $scope.$on('$destroy', function () { // when destroying scope directly if (!$scope.summernoteDestroyed) { - currentElement.destroy(); + currentElement.summernote('destroy'); } }); }]) @@ -106,13 +130,14 @@ angular.module('summernote', []) return { restrict: 'EA', - transclude: true, + transclude: 'element', replace: true, - require: ['summernote', '^?ngModel'], + require: ['summernote', '?ngModel'], controller: 'SummernoteController', scope: { summernoteConfig: '=config', editable: '=', + editor: '=', init: '&onInit', enter: '&onEnter', focus: '&onFocus', @@ -121,13 +146,19 @@ angular.module('summernote', []) keyup: '&onKeyup', keydown: '&onKeydown', change: '&onChange', - imageUpload: '&onImageUpload' + imageUpload: '&onImageUpload', + mediaDelete: '&onMediaDelete' }, template: '
    ', - link: function(scope, element, attrs, ctrls) { + link: function(scope, element, attrs, ctrls, transclude) { var summernoteController = ctrls[0], ngModel = ctrls[1]; + transclude(scope, function(clone, scope) { + // to prevent binding to angular scope (It require `tranclude: 'element'`) + element.append(clone.html()); + }); + summernoteController.activate(scope, element, ngModel); } }; diff --git a/vendor/assets/components/angular-touch/.bower.json b/vendor/assets/components/angular-touch/.bower.json new file mode 100644 index 000000000..548b9420b --- /dev/null +++ b/vendor/assets/components/angular-touch/.bower.json @@ -0,0 +1,20 @@ +{ + "name": "angular-touch", + "version": "1.3.20", + "main": "./angular-touch.js", + "ignore": [], + "dependencies": { + "angular": "1.3.20" + }, + "homepage": "https://github.com/angular/bower-angular-touch", + "_release": "1.3.20", + "_resolution": { + "type": "version", + "tag": "v1.3.20", + "commit": "5c558d52f2267002cad19e33c3d353f9626a87ee" + }, + "_source": "git://github.com/angular/bower-angular-touch.git", + "_target": "1.3.20", + "_originalSource": "angular-touch", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-touch/README.md b/vendor/assets/components/angular-touch/README.md new file mode 100644 index 000000000..58933bab5 --- /dev/null +++ b/vendor/assets/components/angular-touch/README.md @@ -0,0 +1,68 @@ +# packaged angular-touch + +This repo is for distribution on `npm` and `bower`. The source for this module is in the +[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngTouch). +Please file issues and pull requests against that repo. + +## Install + +You can install this package either with `npm` or with `bower`. + +### npm + +```shell +npm install angular-touch +``` + +Then add `ngTouch` as a dependency for your app: + +```javascript +angular.module('myApp', [require('angular-touch')]); +``` + +### bower + +```shell +bower install angular-touch +``` + +Add a ` +``` + +Then add `ngTouch` as a dependency for your app: + +```javascript +angular.module('myApp', ['ngTouch']); +``` + +## Documentation + +Documentation is available on the +[AngularJS docs site](http://docs.angularjs.org/api/ngTouch). + +## License + +The MIT License + +Copyright (c) 2010-2015 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/assets/components/angular-touch/angular-touch.js b/vendor/assets/components/angular-touch/angular-touch.js new file mode 100644 index 000000000..e3118f675 --- /dev/null +++ b/vendor/assets/components/angular-touch/angular-touch.js @@ -0,0 +1,631 @@ +/** + * @license AngularJS v1.3.20 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/** + * @ngdoc module + * @name ngTouch + * @description + * + * # ngTouch + * + * The `ngTouch` module provides touch events and other helpers for touch-enabled devices. + * The implementation is based on jQuery Mobile touch event handling + * ([jquerymobile.com](http://jquerymobile.com/)). + * + * + * See {@link ngTouch.$swipe `$swipe`} for usage. + * + *
    + * + */ + +// define ngTouch module +/* global -ngTouch */ +var ngTouch = angular.module('ngTouch', []); + +function nodeName_(element) { + return angular.lowercase(element.nodeName || (element[0] && element[0].nodeName)); +} + +/* global ngTouch: false */ + + /** + * @ngdoc service + * @name $swipe + * + * @description + * The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe + * behavior, to make implementing swipe-related directives more convenient. + * + * Requires the {@link ngTouch `ngTouch`} module to be installed. + * + * `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`, and by + * `ngCarousel` in a separate component. + * + * # Usage + * The `$swipe` service is an object with a single method: `bind`. `bind` takes an element + * which is to be watched for swipes, and an object with four handler functions. See the + * documentation for `bind` below. + */ + +ngTouch.factory('$swipe', [function() { + // The total distance in any direction before we make the call on swipe vs. scroll. + var MOVE_BUFFER_RADIUS = 10; + + var POINTER_EVENTS = { + 'mouse': { + start: 'mousedown', + move: 'mousemove', + end: 'mouseup' + }, + 'touch': { + start: 'touchstart', + move: 'touchmove', + end: 'touchend', + cancel: 'touchcancel' + } + }; + + function getCoordinates(event) { + var originalEvent = event.originalEvent || event; + var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent]; + var e = (originalEvent.changedTouches && originalEvent.changedTouches[0]) || touches[0]; + + return { + x: e.clientX, + y: e.clientY + }; + } + + function getEvents(pointerTypes, eventType) { + var res = []; + angular.forEach(pointerTypes, function(pointerType) { + var eventName = POINTER_EVENTS[pointerType][eventType]; + if (eventName) { + res.push(eventName); + } + }); + return res.join(' '); + } + + return { + /** + * @ngdoc method + * @name $swipe#bind + * + * @description + * The main method of `$swipe`. It takes an element to be watched for swipe motions, and an + * object containing event handlers. + * The pointer types that should be used can be specified via the optional + * third argument, which is an array of strings `'mouse'` and `'touch'`. By default, + * `$swipe` will listen for `mouse` and `touch` events. + * + * The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end` + * receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }`. + * + * `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is + * watching for `touchmove` or `mousemove` events. These events are ignored until the total + * distance moved in either dimension exceeds a small threshold. + * + * Once this threshold is exceeded, either the horizontal or vertical delta is greater. + * - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow. + * - If the vertical distance is greater, this is a scroll, and we let the browser take over. + * A `cancel` event is sent. + * + * `move` is called on `mousemove` and `touchmove` after the above logic has determined that + * a swipe is in progress. + * + * `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`. + * + * `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling + * as described above. + * + */ + bind: function(element, eventHandlers, pointerTypes) { + // Absolute total movement, used to control swipe vs. scroll. + var totalX, totalY; + // Coordinates of the start position. + var startCoords; + // Last event's position. + var lastPos; + // Whether a swipe is active. + var active = false; + + pointerTypes = pointerTypes || ['mouse', 'touch']; + element.on(getEvents(pointerTypes, 'start'), function(event) { + startCoords = getCoordinates(event); + active = true; + totalX = 0; + totalY = 0; + lastPos = startCoords; + eventHandlers['start'] && eventHandlers['start'](startCoords, event); + }); + var events = getEvents(pointerTypes, 'cancel'); + if (events) { + element.on(events, function(event) { + active = false; + eventHandlers['cancel'] && eventHandlers['cancel'](event); + }); + } + + element.on(getEvents(pointerTypes, 'move'), function(event) { + if (!active) return; + + // Android will send a touchcancel if it thinks we're starting to scroll. + // So when the total distance (+ or - or both) exceeds 10px in either direction, + // we either: + // - On totalX > totalY, we send preventDefault() and treat this as a swipe. + // - On totalY > totalX, we let the browser handle it as a scroll. + + if (!startCoords) return; + var coords = getCoordinates(event); + + totalX += Math.abs(coords.x - lastPos.x); + totalY += Math.abs(coords.y - lastPos.y); + + lastPos = coords; + + if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) { + return; + } + + // One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll. + if (totalY > totalX) { + // Allow native scrolling to take over. + active = false; + eventHandlers['cancel'] && eventHandlers['cancel'](event); + return; + } else { + // Prevent the browser from scrolling. + event.preventDefault(); + eventHandlers['move'] && eventHandlers['move'](coords, event); + } + }); + + element.on(getEvents(pointerTypes, 'end'), function(event) { + if (!active) return; + active = false; + eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event); + }); + } + }; +}]); + +/* global ngTouch: false, + nodeName_: false +*/ + +/** + * @ngdoc directive + * @name ngClick + * + * @description + * A more powerful replacement for the default ngClick designed to be used on touchscreen + * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending + * the click event. This version handles them immediately, and then prevents the + * following click event from propagating. + * + * Requires the {@link ngTouch `ngTouch`} module to be installed. + * + * This directive can fall back to using an ordinary click event, and so works on desktop + * browsers as well as mobile. + * + * This directive also sets the CSS class `ng-click-active` while the element is being held + * down (by a mouse click or touch) so you can restyle the depressed element if you wish. + * + * @element ANY + * @param {expression} ngClick {@link guide/expression Expression} to evaluate + * upon tap. (Event object is available as `$event`) + * + * @example + + + + count: {{ count }} + + + angular.module('ngClickExample', ['ngTouch']); + + + */ + +ngTouch.config(['$provide', function($provide) { + $provide.decorator('ngClickDirective', ['$delegate', function($delegate) { + // drop the default ngClick directive + $delegate.shift(); + return $delegate; + }]); +}]); + +ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', + function($parse, $timeout, $rootElement) { + var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag. + var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers. + var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click + var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks. + + var ACTIVE_CLASS_NAME = 'ng-click-active'; + var lastPreventedTime; + var touchCoordinates; + var lastLabelClickCoordinates; + + + // TAP EVENTS AND GHOST CLICKS + // + // Why tap events? + // Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're + // double-tapping, and then fire a click event. + // + // This delay sucks and makes mobile apps feel unresponsive. + // So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when + // the user has tapped on something. + // + // What happens when the browser then generates a click event? + // The browser, of course, also detects the tap and fires a click after a delay. This results in + // tapping/clicking twice. We do "clickbusting" to prevent it. + // + // How does it work? + // We attach global touchstart and click handlers, that run during the capture (early) phase. + // So the sequence for a tap is: + // - global touchstart: Sets an "allowable region" at the point touched. + // - element's touchstart: Starts a touch + // (- touchmove or touchcancel ends the touch, no click follows) + // - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold + // too long) and fires the user's tap handler. The touchend also calls preventGhostClick(). + // - preventGhostClick() removes the allowable region the global touchstart created. + // - The browser generates a click event. + // - The global click handler catches the click, and checks whether it was in an allowable region. + // - If preventGhostClick was called, the region will have been removed, the click is busted. + // - If the region is still there, the click proceeds normally. Therefore clicks on links and + // other elements without ngTap on them work normally. + // + // This is an ugly, terrible hack! + // Yeah, tell me about it. The alternatives are using the slow click events, or making our users + // deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular + // encapsulates this ugly logic away from the user. + // + // Why not just put click handlers on the element? + // We do that too, just to be sure. If the tap event caused the DOM to change, + // it is possible another element is now in that position. To take account for these possibly + // distinct elements, the handlers are global and care only about coordinates. + + // Checks if the coordinates are close enough to be within the region. + function hit(x1, y1, x2, y2) { + return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD; + } + + // Checks a list of allowable regions against a click location. + // Returns true if the click should be allowed. + // Splices out the allowable region from the list after it has been used. + function checkAllowableRegions(touchCoordinates, x, y) { + for (var i = 0; i < touchCoordinates.length; i += 2) { + if (hit(touchCoordinates[i], touchCoordinates[i + 1], x, y)) { + touchCoordinates.splice(i, i + 2); + return true; // allowable region + } + } + return false; // No allowable region; bust it. + } + + // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick + // was called recently. + function onClick(event) { + if (Date.now() - lastPreventedTime > PREVENT_DURATION) { + return; // Too old. + } + + var touches = event.touches && event.touches.length ? event.touches : [event]; + var x = touches[0].clientX; + var y = touches[0].clientY; + // Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label + // and on the input element). Depending on the exact browser, this second click we don't want + // to bust has either (0,0), negative coordinates, or coordinates equal to triggering label + // click event + if (x < 1 && y < 1) { + return; // offscreen + } + if (lastLabelClickCoordinates && + lastLabelClickCoordinates[0] === x && lastLabelClickCoordinates[1] === y) { + return; // input click triggered by label click + } + // reset label click coordinates on first subsequent click + if (lastLabelClickCoordinates) { + lastLabelClickCoordinates = null; + } + // remember label click coordinates to prevent click busting of trigger click event on input + if (nodeName_(event.target) === 'label') { + lastLabelClickCoordinates = [x, y]; + } + + // Look for an allowable region containing this click. + // If we find one, that means it was created by touchstart and not removed by + // preventGhostClick, so we don't bust it. + if (checkAllowableRegions(touchCoordinates, x, y)) { + return; + } + + // If we didn't find an allowable region, bust the click. + event.stopPropagation(); + event.preventDefault(); + + // Blur focused form elements + event.target && event.target.blur && event.target.blur(); + } + + + // Global touchstart handler that creates an allowable region for a click event. + // This allowable region can be removed by preventGhostClick if we want to bust it. + function onTouchStart(event) { + var touches = event.touches && event.touches.length ? event.touches : [event]; + var x = touches[0].clientX; + var y = touches[0].clientY; + touchCoordinates.push(x, y); + + $timeout(function() { + // Remove the allowable region. + for (var i = 0; i < touchCoordinates.length; i += 2) { + if (touchCoordinates[i] == x && touchCoordinates[i + 1] == y) { + touchCoordinates.splice(i, i + 2); + return; + } + } + }, PREVENT_DURATION, false); + } + + // On the first call, attaches some event handlers. Then whenever it gets called, it creates a + // zone around the touchstart where clicks will get busted. + function preventGhostClick(x, y) { + if (!touchCoordinates) { + $rootElement[0].addEventListener('click', onClick, true); + $rootElement[0].addEventListener('touchstart', onTouchStart, true); + touchCoordinates = []; + } + + lastPreventedTime = Date.now(); + + checkAllowableRegions(touchCoordinates, x, y); + } + + // Actual linking function. + return function(scope, element, attr) { + var clickHandler = $parse(attr.ngClick), + tapping = false, + tapElement, // Used to blur the element after a tap. + startTime, // Used to check if the tap was held too long. + touchStartX, + touchStartY; + + function resetState() { + tapping = false; + element.removeClass(ACTIVE_CLASS_NAME); + } + + element.on('touchstart', function(event) { + tapping = true; + tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement. + // Hack for Safari, which can target text nodes instead of containers. + if (tapElement.nodeType == 3) { + tapElement = tapElement.parentNode; + } + + element.addClass(ACTIVE_CLASS_NAME); + + startTime = Date.now(); + + // Use jQuery originalEvent + var originalEvent = event.originalEvent || event; + var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent]; + var e = touches[0]; + touchStartX = e.clientX; + touchStartY = e.clientY; + }); + + element.on('touchmove', function(event) { + resetState(); + }); + + element.on('touchcancel', function(event) { + resetState(); + }); + + element.on('touchend', function(event) { + var diff = Date.now() - startTime; + + // Use jQuery originalEvent + var originalEvent = event.originalEvent || event; + var touches = (originalEvent.changedTouches && originalEvent.changedTouches.length) ? + originalEvent.changedTouches : + ((originalEvent.touches && originalEvent.touches.length) ? originalEvent.touches : [originalEvent]); + var e = touches[0]; + var x = e.clientX; + var y = e.clientY; + var dist = Math.sqrt(Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2)); + + if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) { + // Call preventGhostClick so the clickbuster will catch the corresponding click. + preventGhostClick(x, y); + + // Blur the focused element (the button, probably) before firing the callback. + // This doesn't work perfectly on Android Chrome, but seems to work elsewhere. + // I couldn't get anything to work reliably on Android Chrome. + if (tapElement) { + tapElement.blur(); + } + + if (!angular.isDefined(attr.disabled) || attr.disabled === false) { + element.triggerHandler('click', [event]); + } + } + + resetState(); + }); + + // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click + // something else nearby. + element.onclick = function(event) { }; + + // Actual click handler. + // There are three different kinds of clicks, only two of which reach this point. + // - On desktop browsers without touch events, their clicks will always come here. + // - On mobile browsers, the simulated "fast" click will call this. + // - But the browser's follow-up slow click will be "busted" before it reaches this handler. + // Therefore it's safe to use this directive on both mobile and desktop. + element.on('click', function(event, touchend) { + scope.$apply(function() { + clickHandler(scope, {$event: (touchend || event)}); + }); + }); + + element.on('mousedown', function(event) { + element.addClass(ACTIVE_CLASS_NAME); + }); + + element.on('mousemove mouseup', function(event) { + element.removeClass(ACTIVE_CLASS_NAME); + }); + + }; +}]); + +/* global ngTouch: false */ + +/** + * @ngdoc directive + * @name ngSwipeLeft + * + * @description + * Specify custom behavior when an element is swiped to the left on a touchscreen device. + * A leftward swipe is a quick, right-to-left slide of the finger. + * Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag + * too. + * + * To disable the mouse click and drag functionality, add `ng-swipe-disable-mouse` to + * the `ng-swipe-left` or `ng-swipe-right` DOM Element. + * + * Requires the {@link ngTouch `ngTouch`} module to be installed. + * + * @element ANY + * @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate + * upon left swipe. (Event object is available as `$event`) + * + * @example + + +
    + Some list content, like an email in the inbox +
    +
    + + +
    +
    + + angular.module('ngSwipeLeftExample', ['ngTouch']); + +
    + */ + +/** + * @ngdoc directive + * @name ngSwipeRight + * + * @description + * Specify custom behavior when an element is swiped to the right on a touchscreen device. + * A rightward swipe is a quick, left-to-right slide of the finger. + * Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag + * too. + * + * Requires the {@link ngTouch `ngTouch`} module to be installed. + * + * @element ANY + * @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate + * upon right swipe. (Event object is available as `$event`) + * + * @example + + +
    + Some list content, like an email in the inbox +
    +
    + + +
    +
    + + angular.module('ngSwipeRightExample', ['ngTouch']); + +
    + */ + +function makeSwipeDirective(directiveName, direction, eventName) { + ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) { + // The maximum vertical delta for a swipe should be less than 75px. + var MAX_VERTICAL_DISTANCE = 75; + // Vertical distance should not be more than a fraction of the horizontal distance. + var MAX_VERTICAL_RATIO = 0.3; + // At least a 30px lateral motion is necessary for a swipe. + var MIN_HORIZONTAL_DISTANCE = 30; + + return function(scope, element, attr) { + var swipeHandler = $parse(attr[directiveName]); + + var startCoords, valid; + + function validSwipe(coords) { + // Check that it's within the coordinates. + // Absolute vertical distance must be within tolerances. + // Horizontal distance, we take the current X - the starting X. + // This is negative for leftward swipes and positive for rightward swipes. + // After multiplying by the direction (-1 for left, +1 for right), legal swipes + // (ie. same direction as the directive wants) will have a positive delta and + // illegal ones a negative delta. + // Therefore this delta must be positive, and larger than the minimum. + if (!startCoords) return false; + var deltaY = Math.abs(coords.y - startCoords.y); + var deltaX = (coords.x - startCoords.x) * direction; + return valid && // Short circuit for already-invalidated swipes. + deltaY < MAX_VERTICAL_DISTANCE && + deltaX > 0 && + deltaX > MIN_HORIZONTAL_DISTANCE && + deltaY / deltaX < MAX_VERTICAL_RATIO; + } + + var pointerTypes = ['touch']; + if (!angular.isDefined(attr['ngSwipeDisableMouse'])) { + pointerTypes.push('mouse'); + } + $swipe.bind(element, { + 'start': function(coords, event) { + startCoords = coords; + valid = true; + }, + 'cancel': function(event) { + valid = false; + }, + 'end': function(coords, event) { + if (validSwipe(coords)) { + scope.$apply(function() { + element.triggerHandler(eventName); + swipeHandler(scope, {$event: event}); + }); + } + } + }, pointerTypes); + }; + }]); +} + +// Left is negative X-coordinate, right is positive. +makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft'); +makeSwipeDirective('ngSwipeRight', 1, 'swiperight'); + + + +})(window, window.angular); diff --git a/vendor/assets/components/angular-touch/angular-touch.min.js b/vendor/assets/components/angular-touch/angular-touch.min.js new file mode 100644 index 000000000..ebb7d4204 --- /dev/null +++ b/vendor/assets/components/angular-touch/angular-touch.min.js @@ -0,0 +1,13 @@ +/* + AngularJS v1.3.20 + (c) 2010-2014 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(y,s,z){'use strict';function t(f,k,p){n.directive(f,["$parse","$swipe",function(d,e){return function(l,m,g){function h(a){if(!b)return!1;var c=Math.abs(a.y-b.y);a=(a.x-b.x)*k;return q&&75>c&&0c/a}var c=d(g[f]),b,q,a=["touch"];s.isDefined(g.ngSwipeDisableMouse)||a.push("mouse");e.bind(m,{start:function(a,c){b=a;q=!0},cancel:function(a){q=!1},end:function(a,b){h(a)&&l.$apply(function(){m.triggerHandler(p);c(l,{$event:b})})}},a)}}])}var n=s.module("ngTouch",[]);n.factory("$swipe", +[function(){function f(d){d=d.originalEvent||d;var e=d.touches&&d.touches.length?d.touches:[d];d=d.changedTouches&&d.changedTouches[0]||e[0];return{x:d.clientX,y:d.clientY}}function k(d,e){var l=[];s.forEach(d,function(d){(d=p[d][e])&&l.push(d)});return l.join(" ")}var p={mouse:{start:"mousedown",move:"mousemove",end:"mouseup"},touch:{start:"touchstart",move:"touchmove",end:"touchend",cancel:"touchcancel"}};return{bind:function(d,e,l){var m,g,h,c,b=!1;l=l||["mouse","touch"];d.on(k(l,"start"),function(a){h= +f(a);b=!0;g=m=0;c=h;e.start&&e.start(h,a)});var q=k(l,"cancel");if(q)d.on(q,function(a){b=!1;e.cancel&&e.cancel(a)});d.on(k(l,"move"),function(a){if(b&&h){var d=f(a);m+=Math.abs(d.x-c.x);g+=Math.abs(d.y-c.y);c=d;10>m&&10>g||(g>m?(b=!1,e.cancel&&e.cancel(a)):(a.preventDefault(),e.move&&e.move(d,a)))}});d.on(k(l,"end"),function(a){b&&(b=!1,e.end&&e.end(f(a),a))})}}}]);n.config(["$provide",function(f){f.decorator("ngClickDirective",["$delegate",function(k){k.shift();return k}])}]);n.directive("ngClick", +["$parse","$timeout","$rootElement",function(f,k,p){function d(c,b,d){for(var a=0;aMath.abs(c[a]-b)&&25>Math.abs(e-g))return c.splice(a,a+2),!0}return!1}function e(c){if(!(2500e&&1>b||h&&h[0]===e&&h[1]===b)){h&&(h=null);var a=c.target;"label"===s.lowercase(a.nodeName||a[0]&&a[0].nodeName)&&(h=[e,b]);d(g,e,b)||(c.stopPropagation(),c.preventDefault(),c.target&& +c.target.blur&&c.target.blur())}}}function l(c){c=c.touches&&c.touches.length?c.touches:[c];var b=c[0].clientX,d=c[0].clientY;g.push(b,d);k(function(){for(var a=0;ak&&12>x&&(g||(p[0].addEventListener("click",e,!0),p[0].addEventListener("touchstart",l,!0),g=[]),m=Date.now(), +d(g,f,u),r&&r.blur(),s.isDefined(h.disabled)&&!1!==h.disabled||b.triggerHandler("click",[c]));a()});b.onclick=function(a){};b.on("click",function(a,b){c.$apply(function(){k(c,{$event:b||a})})});b.on("mousedown",function(a){b.addClass("ng-click-active")});b.on("mousemove mouseup",function(a){b.removeClass("ng-click-active")})}}]);t("ngSwipeLeft",-1,"swipeleft");t("ngSwipeRight",1,"swiperight")})(window,window.angular); +//# sourceMappingURL=angular-touch.min.js.map diff --git a/vendor/assets/components/angular-touch/angular-touch.min.js.map b/vendor/assets/components/angular-touch/angular-touch.min.js.map new file mode 100644 index 000000000..6ebf9161e --- /dev/null +++ b/vendor/assets/components/angular-touch/angular-touch.min.js.map @@ -0,0 +1,8 @@ +{ +"version":3, +"file":"angular-touch.min.js", +"lineCount":12, +"mappings":"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", +"sources":["angular-touch.js"], +"names":["window","angular","undefined","makeSwipeDirective","directiveName","direction","eventName","ngTouch","directive","$parse","$swipe","scope","element","attr","validSwipe","coords","startCoords","deltaY","Math","abs","y","deltaX","x","valid","MAX_VERTICAL_DISTANCE","MIN_HORIZONTAL_DISTANCE","MAX_VERTICAL_RATIO","swipeHandler","pointerTypes","isDefined","push","bind","start","event","cancel","end","$apply","triggerHandler","$event","module","factory","getCoordinates","originalEvent","touches","length","e","changedTouches","clientX","clientY","getEvents","eventType","res","forEach","pointerType","POINTER_EVENTS","join","move","eventHandlers","totalX","totalY","lastPos","active","on","events","MOVE_BUFFER_RADIUS","preventDefault","config","$provide","decorator","$delegate","shift","$timeout","$rootElement","checkAllowableRegions","touchCoordinates","i","CLICKBUSTER_THRESHOLD","x1","y1","y2","splice","onClick","PREVENT_DURATION","Date","now","lastPreventedTime","lastLabelClickCoordinates","target","lowercase","nodeName","stopPropagation","blur","onTouchStart","resetState","tapping","removeClass","ACTIVE_CLASS_NAME","clickHandler","ngClick","tapElement","startTime","touchStartX","touchStartY","srcElement","nodeType","parentNode","addClass","diff","dist","sqrt","pow","TAP_DURATION","MOVE_TOLERANCE","addEventListener","disabled","onclick","element.onclick","touchend"] +} diff --git a/vendor/assets/components/angular-touch/bower.json b/vendor/assets/components/angular-touch/bower.json new file mode 100644 index 000000000..71958b101 --- /dev/null +++ b/vendor/assets/components/angular-touch/bower.json @@ -0,0 +1,9 @@ +{ + "name": "angular-touch", + "version": "1.3.20", + "main": "./angular-touch.js", + "ignore": [], + "dependencies": { + "angular": "1.3.20" + } +} diff --git a/vendor/assets/components/angular-touch/index.js b/vendor/assets/components/angular-touch/index.js new file mode 100644 index 000000000..0478a404b --- /dev/null +++ b/vendor/assets/components/angular-touch/index.js @@ -0,0 +1,2 @@ +require('./angular-touch'); +module.exports = 'ngTouch'; diff --git a/vendor/assets/components/angular-touch/package.json b/vendor/assets/components/angular-touch/package.json new file mode 100644 index 000000000..c4ef0fba6 --- /dev/null +++ b/vendor/assets/components/angular-touch/package.json @@ -0,0 +1,26 @@ +{ + "name": "angular-touch", + "version": "1.3.20", + "description": "AngularJS module for touch events and helpers for touch-enabled devices", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/angular/angular.js.git" + }, + "keywords": [ + "angular", + "framework", + "browser", + "touch", + "client-side" + ], + "author": "Angular Core Team ", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/angular.js/issues" + }, + "homepage": "http://angularjs.org" +} diff --git a/vendor/assets/components/angular-translate-interpolation-messageformat/.bower.json b/vendor/assets/components/angular-translate-interpolation-messageformat/.bower.json new file mode 100644 index 000000000..2ad3163dc --- /dev/null +++ b/vendor/assets/components/angular-translate-interpolation-messageformat/.bower.json @@ -0,0 +1,24 @@ +{ + "name": "angular-translate-interpolation-messageformat", + "description": "A plugin for Angular Translate", + "version": "2.8.1", + "main": "./angular-translate-interpolation-messageformat.js", + "ignore": [], + "author": "Pascal Precht", + "license": "MIT", + "dependencies": { + "angular-translate": "~2.8.1", + "messageformat": "~0.1.6" + }, + "homepage": "https://github.com/PascalPrecht/bower-angular-translate-interpolation-messageformat", + "_release": "2.8.1", + "_resolution": { + "type": "version", + "tag": "2.8.1", + "commit": "9f6f943b7a1a1ce248b172984e922e7157ca57cd" + }, + "_source": "git://github.com/PascalPrecht/bower-angular-translate-interpolation-messageformat.git", + "_target": "~2.8.1", + "_originalSource": "angular-translate-interpolation-messageformat", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-translate-interpolation-messageformat/README.md b/vendor/assets/components/angular-translate-interpolation-messageformat/README.md new file mode 100644 index 000000000..eeaf5eed4 --- /dev/null +++ b/vendor/assets/components/angular-translate-interpolation-messageformat/README.md @@ -0,0 +1,29 @@ +# angular-translate-interpolation-messageformat (bower shadow repository) + +This is the _Bower shadow_ repository for *angular-translate-interpolation-messageformat*. + +## Bugs and issues + +Please file any issues and bugs in our main repository at [angular-translate/angular-translate](https://github.com/angular-translate/angular-translate/issues). + +## Usage + +### via Bower + +```bash +$ bower install angular-translate-interpolation-messageformat +``` + +### via NPM + +```bash +$ npm install angular-translate-interpolation-messageformat +``` + +### via cdnjs + +Please have a look at https://cdnjs.com/libraries/angular-translate-interpolation-messageformat for specific versions. + +## License + +Licensed under MIT. See more details at [angular-translate/angular-translate](https://github.com/angular-translate/angular-translate). diff --git a/vendor/assets/components/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.js b/vendor/assets/components/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.js new file mode 100644 index 000000000..238c9ee57 --- /dev/null +++ b/vendor/assets/components/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.js @@ -0,0 +1,157 @@ +/*! + * angular-translate - v2.8.1 - 2015-10-01 + * + * Copyright (c) 2015 The angular-translate team, Pascal Precht; Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module unless amdModuleId is set + define(["messageformat"], function (a0) { + return (factory(a0)); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(require("messageformat")); + } else { + factory(MessageFormat); + } +}(this, function (MessageFormat) { + +angular.module('pascalprecht.translate') + +/** + * @ngdoc property + * @name pascalprecht.translate.TRANSLATE_MF_INTERPOLATION_CACHE + * @requires TRANSLATE_MF_INTERPOLATION_CACHE + * + * @description + * Uses MessageFormat.js to interpolate strings against some values. + */ +.constant('TRANSLATE_MF_INTERPOLATION_CACHE', '$translateMessageFormatInterpolation') + +/** + * @ngdoc object + * @name pascalprecht.translate.$translateMessageFormatInterpolation + * @requires pascalprecht.translate.TRANSLATE_MF_INTERPOLATION_CACHE + * + * @description + * Uses MessageFormat.js to interpolate strings against some values. + * + * Be aware to configure a proper sanitization strategy. + * + * See also: + * * {@link pascalprecht.translate.$translateSanitization} + * * {@link https://github.com/SlexAxton/messageformat.js} + * + * @return {object} $translateMessageFormatInterpolation Interpolator service + */ +.factory('$translateMessageFormatInterpolation', $translateMessageFormatInterpolation); + +function $translateMessageFormatInterpolation($translateSanitization, $cacheFactory, TRANSLATE_MF_INTERPOLATION_CACHE) { + + 'use strict'; + + var $translateInterpolator = {}, + $cache = $cacheFactory.get(TRANSLATE_MF_INTERPOLATION_CACHE), + // instantiate with default locale (which is 'en') + $mf = new MessageFormat('en'), + $identifier = 'messageformat'; + + if (!$cache) { + // create cache if it doesn't exist already + $cache = $cacheFactory(TRANSLATE_MF_INTERPOLATION_CACHE); + } + + $cache.put('en', $mf); + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateMessageFormatInterpolation#setLocale + * @methodOf pascalprecht.translate.$translateMessageFormatInterpolation + * + * @description + * Sets current locale (this is currently not use in this interpolation). + * + * @param {string} locale Language key or locale. + */ + $translateInterpolator.setLocale = function (locale) { + $mf = $cache.get(locale); + if (!$mf) { + $mf = new MessageFormat(locale); + $cache.put(locale, $mf); + } + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateMessageFormatInterpolation#getInterpolationIdentifier + * @methodOf pascalprecht.translate.$translateMessageFormatInterpolation + * + * @description + * Returns an identifier for this interpolation service. + * + * @returns {string} $identifier + */ + $translateInterpolator.getInterpolationIdentifier = function () { + return $identifier; + }; + + /** + * @deprecated will be removed in 3.0 + * @see {@link pascalprecht.translate.$translateSanitization} + */ + $translateInterpolator.useSanitizeValueStrategy = function (value) { + $translateSanitization.useStrategy(value); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateMessageFormatInterpolation#interpolate + * @methodOf pascalprecht.translate.$translateMessageFormatInterpolation + * + * @description + * Interpolates given string against given interpolate params using MessageFormat.js. + * + * @returns {string} interpolated string. + */ + $translateInterpolator.interpolate = function (string, interpolationParams) { + interpolationParams = interpolationParams || {}; + interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params'); + + var interpolatedText = $cache.get(string + angular.toJson(interpolationParams)); + + // if given string wasn't interpolated yet, we do so now and never have to do it again + if (!interpolatedText) { + + // Ensure explicit type if possible + // MessageFormat checks the actual type (i.e. for amount based conditions) + for (var key in interpolationParams) { + if (interpolationParams.hasOwnProperty(key)) { + // ensure number + var number = parseInt(interpolationParams[key], 10); + if (angular.isNumber(number) && ('' + number) === interpolationParams[key]) { + interpolationParams[key] = number; + } + } + } + + interpolatedText = $mf.compile(string)(interpolationParams); + interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text'); + + $cache.put(string + angular.toJson(interpolationParams), interpolatedText); + } + + return interpolatedText; + }; + + return $translateInterpolator; +} +$translateMessageFormatInterpolation.$inject = ['$translateSanitization', '$cacheFactory', 'TRANSLATE_MF_INTERPOLATION_CACHE']; + +$translateMessageFormatInterpolation.displayName = '$translateMessageFormatInterpolation'; +return 'pascalprecht.translate'; + +})); diff --git a/vendor/assets/components/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.min.js b/vendor/assets/components/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.min.js new file mode 100644 index 000000000..503353d22 --- /dev/null +++ b/vendor/assets/components/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.min.js @@ -0,0 +1,6 @@ +/*! + * angular-translate - v2.8.1 - 2015-10-01 + * + * Copyright (c) 2015 The angular-translate team, Pascal Precht; Licensed MIT + */ +!function(a,b){"function"==typeof define&&define.amd?define(["messageformat"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("messageformat")):b(MessageFormat)}(this,function(a){function b(b,c,d){"use strict";var e={},f=c.get(d),g=new a("en"),h="messageformat";return f||(f=c(d)),f.put("en",g),e.setLocale=function(b){g=f.get(b),g||(g=new a(b),f.put(b,g))},e.getInterpolationIdentifier=function(){return h},e.useSanitizeValueStrategy=function(a){return b.useStrategy(a),this},e.interpolate=function(a,c){c=c||{},c=b.sanitize(c,"params");var d=f.get(a+angular.toJson(c));if(!d){for(var e in c)if(c.hasOwnProperty(e)){var h=parseInt(c[e],10);angular.isNumber(h)&&""+h===c[e]&&(c[e]=h)}d=g.compile(a)(c),d=b.sanitize(d,"text"),f.put(a+angular.toJson(c),d)}return d},e}return angular.module("pascalprecht.translate").constant("TRANSLATE_MF_INTERPOLATION_CACHE","$translateMessageFormatInterpolation").factory("$translateMessageFormatInterpolation",b),b.$inject=["$translateSanitization","$cacheFactory","TRANSLATE_MF_INTERPOLATION_CACHE"],b.displayName="$translateMessageFormatInterpolation","pascalprecht.translate"}); \ No newline at end of file diff --git a/vendor/assets/components/angular-translate-interpolation-messageformat/bower.json b/vendor/assets/components/angular-translate-interpolation-messageformat/bower.json new file mode 100644 index 000000000..517cf93ce --- /dev/null +++ b/vendor/assets/components/angular-translate-interpolation-messageformat/bower.json @@ -0,0 +1,13 @@ +{ + "name": "angular-translate-interpolation-messageformat", + "description": "A plugin for Angular Translate", + "version": "2.8.1", + "main": "./angular-translate-interpolation-messageformat.js", + "ignore": [], + "author": "Pascal Precht", + "license": "MIT", + "dependencies": { + "angular-translate": "~2.8.1", + "messageformat": "~0.1.6" + } +} diff --git a/vendor/assets/components/angular-translate-interpolation-messageformat/package.json b/vendor/assets/components/angular-translate-interpolation-messageformat/package.json new file mode 100644 index 000000000..b35559dd1 --- /dev/null +++ b/vendor/assets/components/angular-translate-interpolation-messageformat/package.json @@ -0,0 +1,25 @@ +{ + "name": "angular-translate-interpolation-messageformat", + "version": "2.8.1", + "description": "Uses MessageFormat.js to interpolate strings against some values.", + "main": "angular-translate-interpolation-messageformat.js", + "repository": { + "type": "git", + "url": "https://github.com/angular-translate/bower-angular-translate-interpolation-messageformat.git" + }, + "keywords": [ + "angular", + "translate", + "messageformat" + ], + "author": "Pascal Precht", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular-translate/angular-translate/issues" + }, + "homepage": "https://angular-translate.github.io", + "dependencies": { + "angular-translate": "~2.8.1", + "messageformat": "~0.1.6" + } +} diff --git a/vendor/assets/components/angular-translate-loader-partial/.bower.json b/vendor/assets/components/angular-translate-loader-partial/.bower.json new file mode 100644 index 000000000..da25b11ff --- /dev/null +++ b/vendor/assets/components/angular-translate-loader-partial/.bower.json @@ -0,0 +1,23 @@ +{ + "name": "angular-translate-loader-partial", + "description": "A plugin for Angular Translate", + "version": "2.8.1", + "main": "./angular-translate-loader-partial.js", + "ignore": [], + "author": "Pascal Precht", + "license": "MIT", + "dependencies": { + "angular-translate": "~2.8.1" + }, + "homepage": "https://github.com/PascalPrecht/bower-angular-translate-loader-partial", + "_release": "2.8.1", + "_resolution": { + "type": "version", + "tag": "2.8.1", + "commit": "bd53eb3a8f0bd39f575f4ec024ff2a8e40253a4d" + }, + "_source": "git://github.com/PascalPrecht/bower-angular-translate-loader-partial.git", + "_target": "~2.8.1", + "_originalSource": "angular-translate-loader-partial", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-translate-loader-partial/README.md b/vendor/assets/components/angular-translate-loader-partial/README.md new file mode 100644 index 000000000..29e6cd013 --- /dev/null +++ b/vendor/assets/components/angular-translate-loader-partial/README.md @@ -0,0 +1,29 @@ +# angular-translate-loader-partial (bower shadow repository) + +This is the _Bower shadow_ repository for *angular-translate-loader-partial*. + +## Bugs and issues + +Please file any issues and bugs in our main repository at [angular-translate/angular-translate](https://github.com/angular-translate/angular-translate/issues). + +## Usage + +### via Bower + +```bash +$ bower install angular-translate-loader-partial +``` + +### via NPM + +```bash +$ npm install angular-translate-loader-partial +``` + +### via cdnjs + +Please have a look at https://cdnjs.com/libraries/angular-translate-loader-partial for specific versions. + +## License + +Licensed under MIT. See more details at [angular-translate/angular-translate](https://github.com/angular-translate/angular-translate). diff --git a/vendor/assets/components/angular-translate-loader-partial/angular-translate-loader-partial.js b/vendor/assets/components/angular-translate-loader-partial/angular-translate-loader-partial.js new file mode 100644 index 000000000..ac639cf0c --- /dev/null +++ b/vendor/assets/components/angular-translate-loader-partial/angular-translate-loader-partial.js @@ -0,0 +1,522 @@ +/*! + * angular-translate - v2.8.1 - 2015-10-01 + * + * Copyright (c) 2015 The angular-translate team, Pascal Precht; Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module unless amdModuleId is set + define([], function () { + return (factory()); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + factory(); + } +}(this, function () { + +angular.module('pascalprecht.translate') +/** + * @ngdoc object + * @name pascalprecht.translate.$translatePartialLoaderProvider + * + * @description + * By using a $translatePartialLoaderProvider you can configure a list of a needed + * translation parts directly during the configuration phase of your application's + * lifetime. All parts you add by using this provider would be loaded by + * angular-translate at the startup as soon as possible. + */ + .provider('$translatePartialLoader', $translatePartialLoader); + +function $translatePartialLoader() { + + 'use strict'; + + /** + * @constructor + * @name Part + * + * @description + * Represents Part object to add and set parts at runtime. + */ + function Part(name, priority) { + this.name = name; + this.isActive = true; + this.tables = {}; + this.priority = priority || 0; + } + + /** + * @name parseUrl + * @method + * + * @description + * Returns a parsed url template string and replaces given target lang + * and part name it. + * + * @param {string|function} urlTemplate - Either a string containing an url pattern (with + * '{part}' and '{lang}') or a function(part, lang) + * returning a string. + * @param {string} targetLang - Language key for language to be used. + * @return {string} Parsed url template string + */ + Part.prototype.parseUrl = function(urlTemplate, targetLang) { + if (angular.isFunction(urlTemplate)) { + return urlTemplate(this.name, targetLang); + } + return urlTemplate.replace(/\{part\}/g, this.name).replace(/\{lang\}/g, targetLang); + }; + + Part.prototype.getTable = function(lang, $q, $http, $httpOptions, urlTemplate, errorHandler) { + + if (!this.tables[lang]) { + var self = this; + + return $http(angular.extend({ + method : 'GET', + url: this.parseUrl(urlTemplate, lang) + }, $httpOptions)) + .then(function(result){ + self.tables[lang] = result.data; + return result.data; + }, function() { + if (errorHandler) { + return errorHandler(self.name, lang) + .then(function(data) { + self.tables[lang] = data; + return data; + }, function() { + return $q.reject(self.name); + }); + } else { + return $q.reject(self.name); + } + }); + + } else { + return $q.when(this.tables[lang]); + } + }; + + var parts = {}; + + function hasPart(name) { + return Object.prototype.hasOwnProperty.call(parts, name); + } + + function isStringValid(str) { + return angular.isString(str) && str !== ''; + } + + function isPartAvailable(name) { + if (!isStringValid(name)) { + throw new TypeError('Invalid type of a first argument, a non-empty string expected.'); + } + + return (hasPart(name) && parts[name].isActive); + } + + function deepExtend(dst, src) { + for (var property in src) { + if (src[property] && src[property].constructor && + src[property].constructor === Object) { + dst[property] = dst[property] || {}; + deepExtend(dst[property], src[property]); + } else { + dst[property] = src[property]; + } + } + return dst; + } + + function getPrioritizedParts() { + var prioritizedParts = []; + for(var part in parts) { + if (parts[part].isActive) { + prioritizedParts.push(parts[part]); + } + } + prioritizedParts.sort(function (a, b) { + return a.priority - b.priority; + }); + return prioritizedParts; + } + + + /** + * @ngdoc function + * @name pascalprecht.translate.$translatePartialLoaderProvider#addPart + * @methodOf pascalprecht.translate.$translatePartialLoaderProvider + * + * @description + * Registers a new part of the translation table to be loaded once the + * `angular-translate` gets into runtime phase. It does not actually load any + * translation data, but only registers a part to be loaded in the future. + * + * @param {string} name A name of the part to add + * @param {int} [priority=0] Sets the load priority of this part. + * + * @returns {object} $translatePartialLoaderProvider, so this method is chainable + * @throws {TypeError} The method could throw a **TypeError** if you pass the param + * of the wrong type. Please, note that the `name` param has to be a + * non-empty **string**. + */ + this.addPart = function(name, priority) { + if (!isStringValid(name)) { + throw new TypeError('Couldn\'t add part, part name has to be a string!'); + } + + if (!hasPart(name)) { + parts[name] = new Part(name, priority); + } + parts[name].isActive = true; + + return this; + }; + + /** + * @ngdocs function + * @name pascalprecht.translate.$translatePartialLoaderProvider#setPart + * @methodOf pascalprecht.translate.$translatePartialLoaderProvider + * + * @description + * Sets a translation table to the specified part. This method does not make the + * specified part available, but only avoids loading this part from the server. + * + * @param {string} lang A language of the given translation table + * @param {string} part A name of the target part + * @param {object} table A translation table to set to the specified part + * + * @return {object} $translatePartialLoaderProvider, so this method is chainable + * @throws {TypeError} The method could throw a **TypeError** if you pass params + * of the wrong type. Please, note that the `lang` and `part` params have to be a + * non-empty **string**s and the `table` param has to be an object. + */ + this.setPart = function (lang, part, table) { + if (!isStringValid(lang)) { + throw new TypeError('Couldn\'t set part.`lang` parameter has to be a string!'); + } + if (!isStringValid(part)) { + throw new TypeError('Couldn\'t set part.`part` parameter has to be a string!'); + } + if (typeof table !== 'object' || table === null) { + throw new TypeError('Couldn\'t set part. `table` parameter has to be an object!'); + } + + if (!hasPart(part)) { + parts[part] = new Part(part); + parts[part].isActive = false; + } + + parts[part].tables[lang] = table; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translatePartialLoaderProvider#deletePart + * @methodOf pascalprecht.translate.$translatePartialLoaderProvider + * + * @description + * Removes the previously added part of the translation data. So, `angular-translate` will not + * load it at the startup. + * + * @param {string} name A name of the part to delete + * + * @returns {object} $translatePartialLoaderProvider, so this method is chainable + * + * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong + * type. Please, note that the `name` param has to be a non-empty **string**. + */ + this.deletePart = function (name) { + if (!isStringValid(name)) { + throw new TypeError('Couldn\'t delete part, first arg has to be string.'); + } + + if (hasPart(name)) { + parts[name].isActive = false; + } + + return this; + }; + + + /** + * @ngdoc function + * @name pascalprecht.translate.$translatePartialLoaderProvider#isPartAvailable + * @methodOf pascalprecht.translate.$translatePartialLoaderProvider + * + * @description + * Checks if the specific part is available. A part becomes available after it was added by the + * `addPart` method. Available parts would be loaded from the server once the `angular-translate` + * asks the loader to that. + * + * @param {string} name A name of the part to check + * + * @returns {boolean} Returns **true** if the part is available now and **false** if not. + * + * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong + * type. Please, note that the `name` param has to be a non-empty **string**. + */ + this.isPartAvailable = isPartAvailable; + + /** + * @ngdoc object + * @name pascalprecht.translate.$translatePartialLoader + * + * @requires $q + * @requires $http + * @requires $injector + * @requires $rootScope + * @requires $translate + * + * @description + * + * @param {object} options Options object + * + * @throws {TypeError} + */ + this.$get = ['$rootScope', '$injector', '$q', '$http', + function($rootScope, $injector, $q, $http) { + + /** + * @ngdoc event + * @name pascalprecht.translate.$translatePartialLoader#$translatePartialLoaderStructureChanged + * @eventOf pascalprecht.translate.$translatePartialLoader + * @eventType broadcast on root scope + * + * @description + * A $translatePartialLoaderStructureChanged event is called when a state of the loader was + * changed somehow. It could mean either some part is added or some part is deleted. Anyway when + * you get this event the translation table is not longer current and has to be updated. + * + * @param {string} name A name of the part which is a reason why the event was fired + */ + + var service = function(options) { + if (!isStringValid(options.key)) { + throw new TypeError('Unable to load data, a key is not a non-empty string.'); + } + + if (!isStringValid(options.urlTemplate) && !angular.isFunction(options.urlTemplate)) { + throw new TypeError('Unable to load data, a urlTemplate is not a non-empty string or not a function.'); + } + + var errorHandler = options.loadFailureHandler; + if (errorHandler !== undefined) { + if (!angular.isString(errorHandler)) { + throw new Error('Unable to load data, a loadFailureHandler is not a string.'); + } else { + errorHandler = $injector.get(errorHandler); + } + } + + var loaders = [], + deferred = $q.defer(), + prioritizedParts = getPrioritizedParts(); + + angular.forEach(prioritizedParts, function(part) { + loaders.push( + part.getTable(options.key, $q, $http, options.$http, options.urlTemplate, errorHandler) + ); + part.urlTemplate = options.urlTemplate; + }); + + $q.all(loaders) + .then(function() { + var table = {}; + angular.forEach(prioritizedParts, function(part) { + deepExtend(table, part.tables[options.key]); + }); + deferred.resolve(table); + }, function() { + deferred.reject(options.key); + } + ); + + return deferred.promise; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translatePartialLoader#addPart + * @methodOf pascalprecht.translate.$translatePartialLoader + * + * @description + * Registers a new part of the translation table. This method does not actually perform any xhr + * requests to get translation data. The new parts will be loaded in order of priority from the server next time + * `angular-translate` asks the loader to load translations. + * + * @param {string} name A name of the part to add + * @param {int} [priority=0] Sets the load priority of this part. + * + * @returns {object} $translatePartialLoader, so this method is chainable + * + * @fires {$translatePartialLoaderStructureChanged} The $translatePartialLoaderStructureChanged + * event would be fired by this method in case the new part affected somehow on the loaders + * state. This way it means that there are a new translation data available to be loaded from + * the server. + * + * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong + * type. Please, note that the `name` param has to be a non-empty **string**. + */ + service.addPart = function(name, priority) { + if (!isStringValid(name)) { + throw new TypeError('Couldn\'t add part, first arg has to be a string'); + } + + if (!hasPart(name)) { + parts[name] = new Part(name, priority); + $rootScope.$emit('$translatePartialLoaderStructureChanged', name); + } else if (!parts[name].isActive) { + parts[name].isActive = true; + $rootScope.$emit('$translatePartialLoaderStructureChanged', name); + } + + return service; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translatePartialLoader#deletePart + * @methodOf pascalprecht.translate.$translatePartialLoader + * + * @description + * Deletes the previously added part of the translation data. The target part could be deleted + * either logically or physically. When the data is deleted logically it is not actually deleted + * from the browser, but the loader marks it as not active and prevents it from affecting on the + * translations. If the deleted in such way part is added again, the loader will use the + * previously loaded data rather than loading it from the server once more time. But if the data + * is deleted physically, the loader will completely remove all information about it. So in case + * of recycling this part will be loaded from the server again. + * + * @param {string} name A name of the part to delete + * @param {boolean=} [removeData=false] An indicator if the loader has to remove a loaded + * translation data physically. If the `removeData` if set to **false** the loaded data will not be + * deleted physically and might be reused in the future to prevent an additional xhr requests. + * + * @returns {object} $translatePartialLoader, so this method is chainable + * + * @fires {$translatePartialLoaderStructureChanged} The $translatePartialLoaderStructureChanged + * event would be fired by this method in case a part deletion process affects somehow on the + * loaders state. This way it means that some part of the translation data is now deprecated and + * the translation table has to be recompiled with the remaining translation parts. + * + * @throws {TypeError} The method could throw a **TypeError** if you pass some param of the + * wrong type. Please, note that the `name` param has to be a non-empty **string** and + * the `removeData` param has to be either **undefined** or **boolean**. + */ + service.deletePart = function(name, removeData) { + if (!isStringValid(name)) { + throw new TypeError('Couldn\'t delete part, first arg has to be string'); + } + + if (removeData === undefined) { + removeData = false; + } else if (typeof removeData !== 'boolean') { + throw new TypeError('Invalid type of a second argument, a boolean expected.'); + } + + if (hasPart(name)) { + var wasActive = parts[name].isActive; + if (removeData) { + var $translate = $injector.get('$translate'); + var cache = $translate.loaderCache(); + if (typeof(cache) === 'string') { + // getting on-demand instance of loader + cache = $injector.get(cache); + } + // Purging items from cache... + if (typeof(cache) === 'object') { + angular.forEach(parts[name].tables, function(value, key) { + cache.remove(parts[name].parseUrl(parts[name].urlTemplate, key)); + }); + } + delete parts[name]; + } else { + parts[name].isActive = false; + } + if (wasActive) { + $rootScope.$emit('$translatePartialLoaderStructureChanged', name); + } + } + + return service; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translatePartialLoader#isPartLoaded + * @methodOf pascalprecht.translate.$translatePartialLoader + * + * @description + * Checks if the registered translation part is loaded into the translation table. + * + * @param {string} name A name of the part + * @param {string} lang A key of the language + * + * @returns {boolean} Returns **true** if the translation of the part is loaded to the translation table and **false** if not. + * + * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong + * type. Please, note that the `name` and `lang` params have to be non-empty **string**. + */ + service.isPartLoaded = function(name, lang) { + return angular.isDefined(parts[name]) && angular.isDefined(parts[name].tables[lang]); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translatePartialLoader#getRegisteredParts + * @methodOf pascalprecht.translate.$translatePartialLoader + * + * @description + * Gets names of the parts that were added with the `addPart`. + * + * @returns {array} Returns array of registered parts, if none were registered then an empty array is returned. + */ + service.getRegisteredParts = function() { + var registeredParts = []; + angular.forEach(parts, function(p){ + if(p.isActive) { + registeredParts.push(p.name); + } + }); + return registeredParts; + }; + + + + /** + * @ngdoc function + * @name pascalprecht.translate.$translatePartialLoader#isPartAvailable + * @methodOf pascalprecht.translate.$translatePartialLoader + * + * @description + * Checks if a target translation part is available. The part becomes available just after it was + * added by the `addPart` method. Part's availability does not mean that it was loaded from the + * server, but only that it was added to the loader. The available part might be loaded next + * time the loader is called. + * + * @param {string} name A name of the part to delete + * + * @returns {boolean} Returns **true** if the part is available now and **false** if not. + * + * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong + * type. Please, note that the `name` param has to be a non-empty **string**. + */ + service.isPartAvailable = isPartAvailable; + + return service; + + }]; + +} + +$translatePartialLoader.displayName = '$translatePartialLoader'; +return 'pascalprecht.translate'; + +})); diff --git a/vendor/assets/components/angular-translate-loader-partial/angular-translate-loader-partial.min.js b/vendor/assets/components/angular-translate-loader-partial/angular-translate-loader-partial.min.js new file mode 100644 index 000000000..eabea6732 --- /dev/null +++ b/vendor/assets/components/angular-translate-loader-partial/angular-translate-loader-partial.min.js @@ -0,0 +1,6 @@ +/*! + * angular-translate - v2.8.1 - 2015-10-01 + * + * Copyright (c) 2015 The angular-translate team, Pascal Precht; Licensed MIT + */ +!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(){"use strict";function a(a,b){this.name=a,this.isActive=!0,this.tables={},this.priority=b||0}function b(a){return Object.prototype.hasOwnProperty.call(g,a)}function c(a){return angular.isString(a)&&""!==a}function d(a){if(!c(a))throw new TypeError("Invalid type of a first argument, a non-empty string expected.");return b(a)&&g[a].isActive}function e(a,b){for(var c in b)b[c]&&b[c].constructor&&b[c].constructor===Object?(a[c]=a[c]||{},e(a[c],b[c])):a[c]=b[c];return a}function f(){var a=[];for(var b in g)g[b].isActive&&a.push(g[b]);return a.sort(function(a,b){return a.priority-b.priority}),a}a.prototype.parseUrl=function(a,b){return angular.isFunction(a)?a(this.name,b):a.replace(/\{part\}/g,this.name).replace(/\{lang\}/g,b)},a.prototype.getTable=function(a,b,c,d,e,f){if(this.tables[a])return b.when(this.tables[a]);var g=this;return c(angular.extend({method:"GET",url:this.parseUrl(e,a)},d)).then(function(b){return g.tables[a]=b.data,b.data},function(){return f?f(g.name,a).then(function(b){return g.tables[a]=b,b},function(){return b.reject(g.name)}):b.reject(g.name)})};var g={};this.addPart=function(d,e){if(!c(d))throw new TypeError("Couldn't add part, part name has to be a string!");return b(d)||(g[d]=new a(d,e)),g[d].isActive=!0,this},this.setPart=function(d,e,f){if(!c(d))throw new TypeError("Couldn't set part.`lang` parameter has to be a string!");if(!c(e))throw new TypeError("Couldn't set part.`part` parameter has to be a string!");if("object"!=typeof f||null===f)throw new TypeError("Couldn't set part. `table` parameter has to be an object!");return b(e)||(g[e]=new a(e),g[e].isActive=!1),g[e].tables[d]=f,this},this.deletePart=function(a){if(!c(a))throw new TypeError("Couldn't delete part, first arg has to be string.");return b(a)&&(g[a].isActive=!1),this},this.isPartAvailable=d,this.$get=["$rootScope","$injector","$q","$http",function(h,i,j,k){var l=function(a){if(!c(a.key))throw new TypeError("Unable to load data, a key is not a non-empty string.");if(!c(a.urlTemplate)&&!angular.isFunction(a.urlTemplate))throw new TypeError("Unable to load data, a urlTemplate is not a non-empty string or not a function.");var b=a.loadFailureHandler;if(void 0!==b){if(!angular.isString(b))throw new Error("Unable to load data, a loadFailureHandler is not a string.");b=i.get(b)}var d=[],g=j.defer(),h=f();return angular.forEach(h,function(c){d.push(c.getTable(a.key,j,k,a.$http,a.urlTemplate,b)),c.urlTemplate=a.urlTemplate}),j.all(d).then(function(){var b={};angular.forEach(h,function(c){e(b,c.tables[a.key])}),g.resolve(b)},function(){g.reject(a.key)}),g.promise};return l.addPart=function(d,e){if(!c(d))throw new TypeError("Couldn't add part, first arg has to be a string");return b(d)?g[d].isActive||(g[d].isActive=!0,h.$emit("$translatePartialLoaderStructureChanged",d)):(g[d]=new a(d,e),h.$emit("$translatePartialLoaderStructureChanged",d)),l},l.deletePart=function(a,d){if(!c(a))throw new TypeError("Couldn't delete part, first arg has to be string");if(void 0===d)d=!1;else if("boolean"!=typeof d)throw new TypeError("Invalid type of a second argument, a boolean expected.");if(b(a)){var e=g[a].isActive;if(d){var f=i.get("$translate"),j=f.loaderCache();"string"==typeof j&&(j=i.get(j)),"object"==typeof j&&angular.forEach(g[a].tables,function(b,c){j.remove(g[a].parseUrl(g[a].urlTemplate,c))}),delete g[a]}else g[a].isActive=!1;e&&h.$emit("$translatePartialLoaderStructureChanged",a)}return l},l.isPartLoaded=function(a,b){return angular.isDefined(g[a])&&angular.isDefined(g[a].tables[b])},l.getRegisteredParts=function(){var a=[];return angular.forEach(g,function(b){b.isActive&&a.push(b.name)}),a},l.isPartAvailable=d,l}]}return angular.module("pascalprecht.translate").provider("$translatePartialLoader",a),a.displayName="$translatePartialLoader","pascalprecht.translate"}); \ No newline at end of file diff --git a/vendor/assets/components/angular-translate-loader-partial/bower.json b/vendor/assets/components/angular-translate-loader-partial/bower.json new file mode 100644 index 000000000..989656edc --- /dev/null +++ b/vendor/assets/components/angular-translate-loader-partial/bower.json @@ -0,0 +1,12 @@ +{ + "name": "angular-translate-loader-partial", + "description": "A plugin for Angular Translate", + "version": "2.8.1", + "main": "./angular-translate-loader-partial.js", + "ignore": [], + "author": "Pascal Precht", + "license": "MIT", + "dependencies": { + "angular-translate": "~2.8.1" + } +} diff --git a/vendor/assets/components/angular-translate-loader-partial/package.json b/vendor/assets/components/angular-translate-loader-partial/package.json new file mode 100644 index 000000000..686f4d9bb --- /dev/null +++ b/vendor/assets/components/angular-translate-loader-partial/package.json @@ -0,0 +1,24 @@ +{ + "name": "angular-translate-loader-partial", + "version": "2.8.1", + "description": "angular-translate-loader-partial", + "main": "angular-translate-loader-partial.js", + "repository": { + "type": "git", + "url": "https://github.com/angular-translate/bower-angular-translate-loader-partial.git" + }, + "keywords": [ + "angular", + "translate", + "loader" + ], + "author": "Pascal Precht", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular-translate/angular-translate/issues" + }, + "homepage": "https://angular-translate.github.io", + "dependencies": { + "angular-translate": "~2.8.1" + } +} diff --git a/vendor/assets/components/angular-translate/.bower.json b/vendor/assets/components/angular-translate/.bower.json new file mode 100644 index 000000000..33909e1ac --- /dev/null +++ b/vendor/assets/components/angular-translate/.bower.json @@ -0,0 +1,23 @@ +{ + "name": "angular-translate", + "description": "A translation module for AngularJS", + "version": "2.8.1", + "main": "./angular-translate.js", + "ignore": [], + "author": "Pascal Precht", + "license": "MIT", + "dependencies": { + "angular": ">=1.2.26 <=1.5" + }, + "homepage": "https://github.com/PascalPrecht/bower-angular-translate", + "_release": "2.8.1", + "_resolution": { + "type": "version", + "tag": "2.8.1", + "commit": "a2c7685d47f9e3b28903129f514e55723f7b5577" + }, + "_source": "git://github.com/PascalPrecht/bower-angular-translate.git", + "_target": "~2.8.1", + "_originalSource": "angular-translate", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-translate/README.md b/vendor/assets/components/angular-translate/README.md new file mode 100644 index 000000000..4991a9999 --- /dev/null +++ b/vendor/assets/components/angular-translate/README.md @@ -0,0 +1,23 @@ +# angular-translate (bower shadow repository) + +This is the _Bower shadow_ repository for *angular-translate*. + +## Bugs and issues + +Please file any issues and bugs in our main repository at [angular-translate/angular-translate](https://github.com/angular-translate/angular-translate/issues). + +## Usage + +### via Bower + +```bash +$ bower install angular-translate +``` + +### via cdnjs + +Please have a look at https://cdnjs.com/libraries/angular-translate for specific versions. + +## License + +Licensed under MIT. See more details at [angular-translate/angular-translate](https://github.com/angular-translate/angular-translate). diff --git a/vendor/assets/components/angular-translate/angular-translate.js b/vendor/assets/components/angular-translate/angular-translate.js new file mode 100644 index 000000000..5c2271bbe --- /dev/null +++ b/vendor/assets/components/angular-translate/angular-translate.js @@ -0,0 +1,3138 @@ +/*! + * angular-translate - v2.8.1 - 2015-10-01 + * + * Copyright (c) 2015 The angular-translate team, Pascal Precht; Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module unless amdModuleId is set + define([], function () { + return (factory()); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + factory(); + } +}(this, function () { + +/** + * @ngdoc overview + * @name pascalprecht.translate + * + * @description + * The main module which holds everything together. + */ +angular.module('pascalprecht.translate', ['ng']) + .run(runTranslate); + +function runTranslate($translate) { + + 'use strict'; + + var key = $translate.storageKey(), + storage = $translate.storage(); + + var fallbackFromIncorrectStorageValue = function () { + var preferred = $translate.preferredLanguage(); + if (angular.isString(preferred)) { + $translate.use(preferred); + // $translate.use() will also remember the language. + // So, we don't need to call storage.put() here. + } else { + storage.put(key, $translate.use()); + } + }; + + fallbackFromIncorrectStorageValue.displayName = 'fallbackFromIncorrectStorageValue'; + + if (storage) { + if (!storage.get(key)) { + fallbackFromIncorrectStorageValue(); + } else { + $translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue); + } + } else if (angular.isString($translate.preferredLanguage())) { + $translate.use($translate.preferredLanguage()); + } +} +runTranslate.$inject = ['$translate']; + +runTranslate.displayName = 'runTranslate'; + +/** + * @ngdoc object + * @name pascalprecht.translate.$translateSanitizationProvider + * + * @description + * + * Configurations for $translateSanitization + */ +angular.module('pascalprecht.translate').provider('$translateSanitization', $translateSanitizationProvider); + +function $translateSanitizationProvider () { + + 'use strict'; + + var $sanitize, + currentStrategy = null, // TODO change to either 'sanitize', 'escape' or ['sanitize', 'escapeParameters'] in 3.0. + hasConfiguredStrategy = false, + hasShownNoStrategyConfiguredWarning = false, + strategies; + + /** + * Definition of a sanitization strategy function + * @callback StrategyFunction + * @param {string|object} value - value to be sanitized (either a string or an interpolated value map) + * @param {string} mode - either 'text' for a string (translation) or 'params' for the interpolated params + * @return {string|object} + */ + + /** + * @ngdoc property + * @name strategies + * @propertyOf pascalprecht.translate.$translateSanitizationProvider + * + * @description + * Following strategies are built-in: + *
    + *
    sanitize
    + *
    Sanitizes HTML in the translation text using $sanitize
    + *
    escape
    + *
    Escapes HTML in the translation
    + *
    sanitizeParameters
    + *
    Sanitizes HTML in the values of the interpolation parameters using $sanitize
    + *
    escapeParameters
    + *
    Escapes HTML in the values of the interpolation parameters
    + *
    escaped
    + *
    Support legacy strategy name 'escaped' for backwards compatibility (will be removed in 3.0)
    + *
    + * + */ + + strategies = { + sanitize: function (value, mode) { + if (mode === 'text') { + value = htmlSanitizeValue(value); + } + return value; + }, + escape: function (value, mode) { + if (mode === 'text') { + value = htmlEscapeValue(value); + } + return value; + }, + sanitizeParameters: function (value, mode) { + if (mode === 'params') { + value = mapInterpolationParameters(value, htmlSanitizeValue); + } + return value; + }, + escapeParameters: function (value, mode) { + if (mode === 'params') { + value = mapInterpolationParameters(value, htmlEscapeValue); + } + return value; + } + }; + // Support legacy strategy name 'escaped' for backwards compatibility. + // TODO should be removed in 3.0 + strategies.escaped = strategies.escapeParameters; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateSanitizationProvider#addStrategy + * @methodOf pascalprecht.translate.$translateSanitizationProvider + * + * @description + * Adds a sanitization strategy to the list of known strategies. + * + * @param {string} strategyName - unique key for a strategy + * @param {StrategyFunction} strategyFunction - strategy function + * @returns {object} this + */ + this.addStrategy = function (strategyName, strategyFunction) { + strategies[strategyName] = strategyFunction; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateSanitizationProvider#removeStrategy + * @methodOf pascalprecht.translate.$translateSanitizationProvider + * + * @description + * Removes a sanitization strategy from the list of known strategies. + * + * @param {string} strategyName - unique key for a strategy + * @returns {object} this + */ + this.removeStrategy = function (strategyName) { + delete strategies[strategyName]; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateSanitizationProvider#useStrategy + * @methodOf pascalprecht.translate.$translateSanitizationProvider + * + * @description + * Selects a sanitization strategy. When an array is provided the strategies will be executed in order. + * + * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions. + * @returns {object} this + */ + this.useStrategy = function (strategy) { + hasConfiguredStrategy = true; + currentStrategy = strategy; + return this; + }; + + /** + * @ngdoc object + * @name pascalprecht.translate.$translateSanitization + * @requires $injector + * @requires $log + * + * @description + * Sanitizes interpolation parameters and translated texts. + * + */ + this.$get = ['$injector', '$log', function ($injector, $log) { + + var cachedStrategyMap = {}; + + var applyStrategies = function (value, mode, selectedStrategies) { + angular.forEach(selectedStrategies, function (selectedStrategy) { + if (angular.isFunction(selectedStrategy)) { + value = selectedStrategy(value, mode); + } else if (angular.isFunction(strategies[selectedStrategy])) { + value = strategies[selectedStrategy](value, mode); + } else if (angular.isString(strategies[selectedStrategy])) { + if (!cachedStrategyMap[strategies[selectedStrategy]]) { + try { + cachedStrategyMap[strategies[selectedStrategy]] = $injector.get(strategies[selectedStrategy]); + } catch (e) { + cachedStrategyMap[strategies[selectedStrategy]] = function() {}; + throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\''); + } + } + value = cachedStrategyMap[strategies[selectedStrategy]](value, mode); + } else { + throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\''); + } + }); + return value; + }; + + // TODO: should be removed in 3.0 + var showNoStrategyConfiguredWarning = function () { + if (!hasConfiguredStrategy && !hasShownNoStrategyConfiguredWarning) { + $log.warn('pascalprecht.translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details.'); + hasShownNoStrategyConfiguredWarning = true; + } + }; + + if ($injector.has('$sanitize')) { + $sanitize = $injector.get('$sanitize'); + } + + return { + /** + * @ngdoc function + * @name pascalprecht.translate.$translateSanitization#useStrategy + * @methodOf pascalprecht.translate.$translateSanitization + * + * @description + * Selects a sanitization strategy. When an array is provided the strategies will be executed in order. + * + * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions. + */ + useStrategy: (function (self) { + return function (strategy) { + self.useStrategy(strategy); + }; + })(this), + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateSanitization#sanitize + * @methodOf pascalprecht.translate.$translateSanitization + * + * @description + * Sanitizes a value. + * + * @param {string|object} value The value which should be sanitized. + * @param {string} mode The current sanitization mode, either 'params' or 'text'. + * @param {string|StrategyFunction|array} [strategy] Optional custom strategy which should be used instead of the currently selected strategy. + * @returns {string|object} sanitized value + */ + sanitize: function (value, mode, strategy) { + if (!currentStrategy) { + showNoStrategyConfiguredWarning(); + } + + if (arguments.length < 3) { + strategy = currentStrategy; + } + + if (!strategy) { + return value; + } + + var selectedStrategies = angular.isArray(strategy) ? strategy : [strategy]; + return applyStrategies(value, mode, selectedStrategies); + } + }; + }]; + + var htmlEscapeValue = function (value) { + var element = angular.element('
    '); + element.text(value); // not chainable, see #1044 + return element.html(); + }; + + var htmlSanitizeValue = function (value) { + if (!$sanitize) { + throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as \'escape\'.'); + } + return $sanitize(value); + }; + + var mapInterpolationParameters = function (value, iteratee) { + if (angular.isObject(value)) { + var result = angular.isArray(value) ? [] : {}; + + angular.forEach(value, function (propertyValue, propertyKey) { + result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee); + }); + + return result; + } else if (angular.isNumber(value)) { + return value; + } else { + return iteratee(value); + } + }; +} + +/** + * @ngdoc object + * @name pascalprecht.translate.$translateProvider + * @description + * + * $translateProvider allows developers to register translation-tables, asynchronous loaders + * and similar to configure translation behavior directly inside of a module. + * + */ +angular.module('pascalprecht.translate') +.constant('pascalprechtTranslateOverrider', {}) +.provider('$translate', $translate); + +function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvider, pascalprechtTranslateOverrider) { + + 'use strict'; + + var $translationTable = {}, + $preferredLanguage, + $availableLanguageKeys = [], + $languageKeyAliases, + $fallbackLanguage, + $fallbackWasString, + $uses, + $nextLang, + $storageFactory, + $storageKey = $STORAGE_KEY, + $storagePrefix, + $missingTranslationHandlerFactory, + $interpolationFactory, + $interpolatorFactories = [], + $loaderFactory, + $cloakClassName = 'translate-cloak', + $loaderOptions, + $notFoundIndicatorLeft, + $notFoundIndicatorRight, + $postCompilingEnabled = false, + $forceAsyncReloadEnabled = false, + $nestedObjectDelimeter = '.', + $isReady = false, + loaderCache, + directivePriority = 0, + statefulFilter = true, + uniformLanguageTagResolver = 'default', + languageTagResolver = { + 'default': function (tag) { + return (tag || '').split('-').join('_'); + }, + java: function (tag) { + var temp = (tag || '').split('-').join('_'); + var parts = temp.split('_'); + return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp; + }, + bcp47: function (tag) { + var temp = (tag || '').split('_').join('-'); + var parts = temp.split('-'); + return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp; + } + }; + + var version = '2.8.1'; + + // tries to determine the browsers language + var getFirstBrowserLanguage = function () { + + // internal purpose only + if (angular.isFunction(pascalprechtTranslateOverrider.getLocale)) { + return pascalprechtTranslateOverrider.getLocale(); + } + + var nav = $windowProvider.$get().navigator, + browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'], + i, + language; + + // support for HTML 5.1 "navigator.languages" + if (angular.isArray(nav.languages)) { + for (i = 0; i < nav.languages.length; i++) { + language = nav.languages[i]; + if (language && language.length) { + return language; + } + } + } + + // support for other well known properties in browsers + for (i = 0; i < browserLanguagePropertyKeys.length; i++) { + language = nav[browserLanguagePropertyKeys[i]]; + if (language && language.length) { + return language; + } + } + + return null; + }; + getFirstBrowserLanguage.displayName = 'angular-translate/service: getFirstBrowserLanguage'; + + // tries to determine the browsers locale + var getLocale = function () { + var locale = getFirstBrowserLanguage() || ''; + if (languageTagResolver[uniformLanguageTagResolver]) { + locale = languageTagResolver[uniformLanguageTagResolver](locale); + } + return locale; + }; + getLocale.displayName = 'angular-translate/service: getLocale'; + + /** + * @name indexOf + * @private + * + * @description + * indexOf polyfill. Kinda sorta. + * + * @param {array} array Array to search in. + * @param {string} searchElement Element to search for. + * + * @returns {int} Index of search element. + */ + var indexOf = function(array, searchElement) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === searchElement) { + return i; + } + } + return -1; + }; + + /** + * @name trim + * @private + * + * @description + * trim polyfill + * + * @returns {string} The string stripped of whitespace from both ends + */ + var trim = function() { + return this.toString().replace(/^\s+|\s+$/g, ''); + }; + + var negotiateLocale = function (preferred) { + + var avail = [], + locale = angular.lowercase(preferred), + i = 0, + n = $availableLanguageKeys.length; + + for (; i < n; i++) { + avail.push(angular.lowercase($availableLanguageKeys[i])); + } + + if (indexOf(avail, locale) > -1) { + return preferred; + } + + if ($languageKeyAliases) { + var alias; + for (var langKeyAlias in $languageKeyAliases) { + var hasWildcardKey = false; + var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) && + angular.lowercase(langKeyAlias) === angular.lowercase(preferred); + + if (langKeyAlias.slice(-1) === '*') { + hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length-1); + } + if (hasExactKey || hasWildcardKey) { + alias = $languageKeyAliases[langKeyAlias]; + if (indexOf(avail, angular.lowercase(alias)) > -1) { + return alias; + } + } + } + } + + if (preferred) { + var parts = preferred.split('_'); + + if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) { + return parts[0]; + } + } + + // If everything fails, just return the preferred, unchanged. + return preferred; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#translations + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Registers a new translation table for specific language key. + * + * To register a translation table for specific language, pass a defined language + * key as first parameter. + * + *
    +   *  // register translation table for language: 'de_DE'
    +   *  $translateProvider.translations('de_DE', {
    +   *    'GREETING': 'Hallo Welt!'
    +   *  });
    +   *
    +   *  // register another one
    +   *  $translateProvider.translations('en_US', {
    +   *    'GREETING': 'Hello world!'
    +   *  });
    +   * 
    + * + * When registering multiple translation tables for for the same language key, + * the actual translation table gets extended. This allows you to define module + * specific translation which only get added, once a specific module is loaded in + * your app. + * + * Invoking this method with no arguments returns the translation table which was + * registered with no language key. Invoking it with a language key returns the + * related translation table. + * + * @param {string} key A language key. + * @param {object} translationTable A plain old JavaScript object that represents a translation table. + * + */ + var translations = function (langKey, translationTable) { + + if (!langKey && !translationTable) { + return $translationTable; + } + + if (langKey && !translationTable) { + if (angular.isString(langKey)) { + return $translationTable[langKey]; + } + } else { + if (!angular.isObject($translationTable[langKey])) { + $translationTable[langKey] = {}; + } + angular.extend($translationTable[langKey], flatObject(translationTable)); + } + return this; + }; + + this.translations = translations; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#cloakClassName + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * + * Let's you change the class name for `translate-cloak` directive. + * Default class name is `translate-cloak`. + * + * @param {string} name translate-cloak class name + */ + this.cloakClassName = function (name) { + if (!name) { + return $cloakClassName; + } + $cloakClassName = name; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#nestedObjectDelimeter + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * + * Let's you change the delimiter for namespaced translations. + * Default delimiter is `.`. + * + * @param {string} delimiter namespace separator + */ + this.nestedObjectDelimeter = function (delimiter) { + if (!delimiter) { + return $nestedObjectDelimeter; + } + $nestedObjectDelimeter = delimiter; + return this; + }; + + /** + * @name flatObject + * @private + * + * @description + * Flats an object. This function is used to flatten given translation data with + * namespaces, so they are later accessible via dot notation. + */ + var flatObject = function (data, path, result, prevKey) { + var key, keyWithPath, keyWithShortPath, val; + + if (!path) { + path = []; + } + if (!result) { + result = {}; + } + for (key in data) { + if (!Object.prototype.hasOwnProperty.call(data, key)) { + continue; + } + val = data[key]; + if (angular.isObject(val)) { + flatObject(val, path.concat(key), result, key); + } else { + keyWithPath = path.length ? ('' + path.join($nestedObjectDelimeter) + $nestedObjectDelimeter + key) : key; + if(path.length && key === prevKey){ + // Create shortcut path (foo.bar == foo.bar.bar) + keyWithShortPath = '' + path.join($nestedObjectDelimeter); + // Link it to original path + result[keyWithShortPath] = '@:' + keyWithPath; + } + result[keyWithPath] = val; + } + } + return result; + }; + flatObject.displayName = 'flatObject'; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#addInterpolation + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Adds interpolation services to angular-translate, so it can manage them. + * + * @param {object} factory Interpolation service factory + */ + this.addInterpolation = function (factory) { + $interpolatorFactories.push(factory); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useMessageFormatInterpolation + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use interpolation functionality of messageformat.js. + * This is useful when having high level pluralization and gender selection. + */ + this.useMessageFormatInterpolation = function () { + return this.useInterpolation('$translateMessageFormatInterpolation'); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useInterpolation + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate which interpolation style to use as default, application-wide. + * Simply pass a factory/service name. The interpolation service has to implement + * the correct interface. + * + * @param {string} factory Interpolation service name. + */ + this.useInterpolation = function (factory) { + $interpolationFactory = factory; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useSanitizeStrategy + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Simply sets a sanitation strategy type. + * + * @param {string} value Strategy type. + */ + this.useSanitizeValueStrategy = function (value) { + $translateSanitizationProvider.useStrategy(value); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#preferredLanguage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells the module which of the registered translation tables to use for translation + * at initial startup by passing a language key. Similar to `$translateProvider#use` + * only that it says which language to **prefer**. + * + * @param {string} langKey A language key. + */ + this.preferredLanguage = function(langKey) { + if (langKey) { + setupPreferredLanguage(langKey); + return this; + } + return $preferredLanguage; + }; + var setupPreferredLanguage = function (langKey) { + if (langKey) { + $preferredLanguage = langKey; + } + return $preferredLanguage; + }; + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicator + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Sets an indicator which is used when a translation isn't found. E.g. when + * setting the indicator as 'X' and one tries to translate a translation id + * called `NOT_FOUND`, this will result in `X NOT_FOUND X`. + * + * Internally this methods sets a left indicator and a right indicator using + * `$translateProvider.translationNotFoundIndicatorLeft()` and + * `$translateProvider.translationNotFoundIndicatorRight()`. + * + * **Note**: These methods automatically add a whitespace between the indicators + * and the translation id. + * + * @param {string} indicator An indicator, could be any string. + */ + this.translationNotFoundIndicator = function (indicator) { + this.translationNotFoundIndicatorLeft(indicator); + this.translationNotFoundIndicatorRight(indicator); + return this; + }; + + /** + * ngdoc function + * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Sets an indicator which is used when a translation isn't found left to the + * translation id. + * + * @param {string} indicator An indicator. + */ + this.translationNotFoundIndicatorLeft = function (indicator) { + if (!indicator) { + return $notFoundIndicatorLeft; + } + $notFoundIndicatorLeft = indicator; + return this; + }; + + /** + * ngdoc function + * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Sets an indicator which is used when a translation isn't found right to the + * translation id. + * + * @param {string} indicator An indicator. + */ + this.translationNotFoundIndicatorRight = function (indicator) { + if (!indicator) { + return $notFoundIndicatorRight; + } + $notFoundIndicatorRight = indicator; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#fallbackLanguage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells the module which of the registered translation tables to use when missing translations + * at initial startup by passing a language key. Similar to `$translateProvider#use` + * only that it says which language to **fallback**. + * + * @param {string||array} langKey A language key. + * + */ + this.fallbackLanguage = function (langKey) { + fallbackStack(langKey); + return this; + }; + + var fallbackStack = function (langKey) { + if (langKey) { + if (angular.isString(langKey)) { + $fallbackWasString = true; + $fallbackLanguage = [ langKey ]; + } else if (angular.isArray(langKey)) { + $fallbackWasString = false; + $fallbackLanguage = langKey; + } + if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) { + $fallbackLanguage.push($preferredLanguage); + } + + return this; + } else { + if ($fallbackWasString) { + return $fallbackLanguage[0]; + } else { + return $fallbackLanguage; + } + } + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#use + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Set which translation table to use for translation by given language key. When + * trying to 'use' a language which isn't provided, it'll throw an error. + * + * You actually don't have to use this method since `$translateProvider#preferredLanguage` + * does the job too. + * + * @param {string} langKey A language key. + */ + this.use = function (langKey) { + if (langKey) { + if (!$translationTable[langKey] && (!$loaderFactory)) { + // only throw an error, when not loading translation data asynchronously + throw new Error('$translateProvider couldn\'t find translationTable for langKey: \'' + langKey + '\''); + } + $uses = langKey; + return this; + } + return $uses; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#storageKey + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells the module which key must represent the choosed language by a user in the storage. + * + * @param {string} key A key for the storage. + */ + var storageKey = function(key) { + if (!key) { + if ($storagePrefix) { + return $storagePrefix + $storageKey; + } + return $storageKey; + } + $storageKey = key; + return this; + }; + + this.storageKey = storageKey; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useUrlLoader + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use `$translateUrlLoader` extension service as loader. + * + * @param {string} url Url + * @param {Object=} options Optional configuration object + */ + this.useUrlLoader = function (url, options) { + return this.useLoader('$translateUrlLoader', angular.extend({ url: url }, options)); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader. + * + * @param {Object=} options Optional configuration object + */ + this.useStaticFilesLoader = function (options) { + return this.useLoader('$translateStaticFilesLoader', options); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useLoader + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use any other service as loader. + * + * @param {string} loaderFactory Factory name to use + * @param {Object=} options Optional configuration object + */ + this.useLoader = function (loaderFactory, options) { + $loaderFactory = loaderFactory; + $loaderOptions = options || {}; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useLocalStorage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use `$translateLocalStorage` service as storage layer. + * + */ + this.useLocalStorage = function () { + return this.useStorage('$translateLocalStorage'); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useCookieStorage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use `$translateCookieStorage` service as storage layer. + */ + this.useCookieStorage = function () { + return this.useStorage('$translateCookieStorage'); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useStorage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use custom service as storage layer. + */ + this.useStorage = function (storageFactory) { + $storageFactory = storageFactory; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#storagePrefix + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Sets prefix for storage key. + * + * @param {string} prefix Storage key prefix + */ + this.storagePrefix = function (prefix) { + if (!prefix) { + return prefix; + } + $storagePrefix = prefix; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandlerLog + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to use built-in log handler when trying to translate + * a translation Id which doesn't exist. + * + * This is actually a shortcut method for `useMissingTranslationHandler()`. + * + */ + this.useMissingTranslationHandlerLog = function () { + return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog'); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandler + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Expects a factory name which later gets instantiated with `$injector`. + * This method can be used to tell angular-translate to use a custom + * missingTranslationHandler. Just build a factory which returns a function + * and expects a translation id as argument. + * + * Example: + *
    +   *  app.config(function ($translateProvider) {
    +   *    $translateProvider.useMissingTranslationHandler('customHandler');
    +   *  });
    +   *
    +   *  app.factory('customHandler', function (dep1, dep2) {
    +   *    return function (translationId) {
    +   *      // something with translationId and dep1 and dep2
    +   *    };
    +   *  });
    +   * 
    + * + * @param {string} factory Factory name + */ + this.useMissingTranslationHandler = function (factory) { + $missingTranslationHandlerFactory = factory; + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#usePostCompiling + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * If post compiling is enabled, all translated values will be processed + * again with AngularJS' $compile. + * + * Example: + *
    +   *  app.config(function ($translateProvider) {
    +   *    $translateProvider.usePostCompiling(true);
    +   *  });
    +   * 
    + * + * @param {string} factory Factory name + */ + this.usePostCompiling = function (value) { + $postCompilingEnabled = !(!value); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#forceAsyncReload + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * If force async reload is enabled, async loader will always be called + * even if $translationTable already contains the language key, adding + * possible new entries to the $translationTable. + * + * Example: + *
    +   *  app.config(function ($translateProvider) {
    +   *    $translateProvider.forceAsyncReload(true);
    +   *  });
    +   * 
    + * + * @param {boolean} value - valid values are true or false + */ + this.forceAsyncReload = function (value) { + $forceAsyncReloadEnabled = !(!value); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#uniformLanguageTag + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate which language tag should be used as a result when determining + * the current browser language. + * + * This setting must be set before invoking {@link pascalprecht.translate.$translateProvider#methods_determinePreferredLanguage determinePreferredLanguage()}. + * + *
    +   * $translateProvider
    +   *   .uniformLanguageTag('bcp47')
    +   *   .determinePreferredLanguage()
    +   * 
    + * + * The resolver currently supports: + * * default + * (traditionally: hyphens will be converted into underscores, i.e. en-US => en_US) + * en-US => en_US + * en_US => en_US + * en-us => en_us + * * java + * like default, but the second part will be always in uppercase + * en-US => en_US + * en_US => en_US + * en-us => en_US + * * BCP 47 (RFC 4646 & 4647) + * en-US => en-US + * en_US => en-US + * en-us => en-US + * + * See also: + * * http://en.wikipedia.org/wiki/IETF_language_tag + * * http://www.w3.org/International/core/langtags/ + * * http://tools.ietf.org/html/bcp47 + * + * @param {string|object} options - options (or standard) + * @param {string} options.standard - valid values are 'default', 'bcp47', 'java' + */ + this.uniformLanguageTag = function (options) { + + if (!options) { + options = {}; + } else if (angular.isString(options)) { + options = { + standard: options + }; + } + + uniformLanguageTagResolver = options.standard; + + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#determinePreferredLanguage + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Tells angular-translate to try to determine on its own which language key + * to set as preferred language. When `fn` is given, angular-translate uses it + * to determine a language key, otherwise it uses the built-in `getLocale()` + * method. + * + * The `getLocale()` returns a language key in the format `[lang]_[country]` or + * `[lang]` depending on what the browser provides. + * + * Use this method at your own risk, since not all browsers return a valid + * locale (see {@link pascalprecht.translate.$translateProvider#methods_uniformLanguageTag uniformLanguageTag()}). + * + * @param {Function=} fn Function to determine a browser's locale + */ + this.determinePreferredLanguage = function (fn) { + + var locale = (fn && angular.isFunction(fn)) ? fn() : getLocale(); + + if (!$availableLanguageKeys.length) { + $preferredLanguage = locale; + } else { + $preferredLanguage = negotiateLocale(locale); + } + + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#registerAvailableLanguageKeys + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Registers a set of language keys the app will work with. Use this method in + * combination with + * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}. + * When available languages keys are registered, angular-translate + * tries to find the best fitting language key depending on the browsers locale, + * considering your language key convention. + * + * @param {object} languageKeys Array of language keys the your app will use + * @param {object=} aliases Alias map. + */ + this.registerAvailableLanguageKeys = function (languageKeys, aliases) { + if (languageKeys) { + $availableLanguageKeys = languageKeys; + if (aliases) { + $languageKeyAliases = aliases; + } + return this; + } + return $availableLanguageKeys; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#useLoaderCache + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Registers a cache for internal $http based loaders. + * {@link pascalprecht.translate.$translationCache $translationCache}. + * When false the cache will be disabled (default). When true or undefined + * the cache will be a default (see $cacheFactory). When an object it will + * be treat as a cache object itself: the usage is $http({cache: cache}) + * + * @param {object} cache boolean, string or cache-object + */ + this.useLoaderCache = function (cache) { + if (cache === false) { + // disable cache + loaderCache = undefined; + } else if (cache === true) { + // enable cache using AJS defaults + loaderCache = true; + } else if (typeof(cache) === 'undefined') { + // enable cache using default + loaderCache = '$translationCache'; + } else if (cache) { + // enable cache using given one (see $cacheFactory) + loaderCache = cache; + } + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#directivePriority + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Sets the default priority of the translate directive. The standard value is `0`. + * Calling this function without an argument will return the current value. + * + * @param {number} priority for the translate-directive + */ + this.directivePriority = function (priority) { + if (priority === undefined) { + // getter + return directivePriority; + } else { + // setter with chaining + directivePriority = priority; + return this; + } + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateProvider#statefulFilter + * @methodOf pascalprecht.translate.$translateProvider + * + * @description + * Since AngularJS 1.3, filters which are not stateless (depending at the scope) + * have to explicit define this behavior. + * Sets whether the translate filter should be stateful or stateless. The standard value is `true` + * meaning being stateful. + * Calling this function without an argument will return the current value. + * + * @param {boolean} state - defines the state of the filter + */ + this.statefulFilter = function (state) { + if (state === undefined) { + // getter + return statefulFilter; + } else { + // setter with chaining + statefulFilter = state; + return this; + } + }; + + /** + * @ngdoc object + * @name pascalprecht.translate.$translate + * @requires $interpolate + * @requires $log + * @requires $rootScope + * @requires $q + * + * @description + * The `$translate` service is the actual core of angular-translate. It expects a translation id + * and optional interpolate parameters to translate contents. + * + *
    +   *  $translate('HEADLINE_TEXT').then(function (translation) {
    +   *    $scope.translatedText = translation;
    +   *  });
    +   * 
    + * + * @param {string|array} translationId A token which represents a translation id + * This can be optionally an array of translation ids which + * results that the function returns an object where each key + * is the translation id and the value the translation. + * @param {object=} interpolateParams An object hash for dynamic values + * @param {string} interpolationId The id of the interpolation to use + * @returns {object} promise + */ + this.$get = [ + '$log', + '$injector', + '$rootScope', + '$q', + function ($log, $injector, $rootScope, $q) { + + var Storage, + defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'), + pendingLoader = false, + interpolatorHashMap = {}, + langPromises = {}, + fallbackIndex, + startFallbackIteration; + + var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText) { + + // Duck detection: If the first argument is an array, a bunch of translations was requested. + // The result is an object. + if (angular.isArray(translationId)) { + // Inspired by Q.allSettled by Kris Kowal + // https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563 + // This transforms all promises regardless resolved or rejected + var translateAll = function (translationIds) { + var results = {}; // storing the actual results + var promises = []; // promises to wait for + // Wraps the promise a) being always resolved and b) storing the link id->value + var translate = function (translationId) { + var deferred = $q.defer(); + var regardless = function (value) { + results[translationId] = value; + deferred.resolve([translationId, value]); + }; + // we don't care whether the promise was resolved or rejected; just store the values + $translate(translationId, interpolateParams, interpolationId, defaultTranslationText).then(regardless, regardless); + return deferred.promise; + }; + for (var i = 0, c = translationIds.length; i < c; i++) { + promises.push(translate(translationIds[i])); + } + // wait for all (including storing to results) + return $q.all(promises).then(function () { + // return the results + return results; + }); + }; + return translateAll(translationId); + } + + var deferred = $q.defer(); + + // trim off any whitespace + if (translationId) { + translationId = trim.apply(translationId); + } + + var promiseToWaitFor = (function () { + var promise = $preferredLanguage ? + langPromises[$preferredLanguage] : + langPromises[$uses]; + + fallbackIndex = 0; + + if ($storageFactory && !promise) { + // looks like there's no pending promise for $preferredLanguage or + // $uses. Maybe there's one pending for a language that comes from + // storage. + var langKey = Storage.get($storageKey); + promise = langPromises[langKey]; + + if ($fallbackLanguage && $fallbackLanguage.length) { + var index = indexOf($fallbackLanguage, langKey); + // maybe the language from storage is also defined as fallback language + // we increase the fallback language index to not search in that language + // as fallback, since it's probably the first used language + // in that case the index starts after the first element + fallbackIndex = (index === 0) ? 1 : 0; + + // but we can make sure to ALWAYS fallback to preferred language at least + if (indexOf($fallbackLanguage, $preferredLanguage) < 0) { + $fallbackLanguage.push($preferredLanguage); + } + } + } + return promise; + }()); + + if (!promiseToWaitFor) { + // no promise to wait for? okay. Then there's no loader registered + // nor is a one pending for language that comes from storage. + // We can just translate. + determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject); + } else { + var promiseResolved = function () { + determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject); + }; + promiseResolved.displayName = 'promiseResolved'; + + promiseToWaitFor['finally'](promiseResolved, deferred.reject); + } + return deferred.promise; + }; + + /** + * @name applyNotFoundIndicators + * @private + * + * @description + * Applies not fount indicators to given translation id, if needed. + * This function gets only executed, if a translation id doesn't exist, + * which is why a translation id is expected as argument. + * + * @param {string} translationId Translation id. + * @returns {string} Same as given translation id but applied with not found + * indicators. + */ + var applyNotFoundIndicators = function (translationId) { + // applying notFoundIndicators + if ($notFoundIndicatorLeft) { + translationId = [$notFoundIndicatorLeft, translationId].join(' '); + } + if ($notFoundIndicatorRight) { + translationId = [translationId, $notFoundIndicatorRight].join(' '); + } + return translationId; + }; + + /** + * @name useLanguage + * @private + * + * @description + * Makes actual use of a language by setting a given language key as used + * language and informs registered interpolators to also use the given + * key as locale. + * + * @param {key} Locale key. + */ + var useLanguage = function (key) { + $uses = key; + + // make sure to store new language key before triggering success event + if ($storageFactory) { + Storage.put($translate.storageKey(), $uses); + } + + $rootScope.$emit('$translateChangeSuccess', {language: key}); + + // inform default interpolator + defaultInterpolator.setLocale($uses); + + var eachInterpolator = function (interpolator, id) { + interpolatorHashMap[id].setLocale($uses); + }; + eachInterpolator.displayName = 'eachInterpolatorLocaleSetter'; + + // inform all others too! + angular.forEach(interpolatorHashMap, eachInterpolator); + $rootScope.$emit('$translateChangeEnd', {language: key}); + }; + + /** + * @name loadAsync + * @private + * + * @description + * Kicks of registered async loader using `$injector` and applies existing + * loader options. When resolved, it updates translation tables accordingly + * or rejects with given language key. + * + * @param {string} key Language key. + * @return {Promise} A promise. + */ + var loadAsync = function (key) { + if (!key) { + throw 'No language key specified for loading.'; + } + + var deferred = $q.defer(); + + $rootScope.$emit('$translateLoadingStart', {language: key}); + pendingLoader = true; + + var cache = loaderCache; + if (typeof(cache) === 'string') { + // getting on-demand instance of loader + cache = $injector.get(cache); + } + + var loaderOptions = angular.extend({}, $loaderOptions, { + key: key, + $http: angular.extend({}, { + cache: cache + }, $loaderOptions.$http) + }); + + var onLoaderSuccess = function (data) { + var translationTable = {}; + $rootScope.$emit('$translateLoadingSuccess', {language: key}); + + if (angular.isArray(data)) { + angular.forEach(data, function (table) { + angular.extend(translationTable, flatObject(table)); + }); + } else { + angular.extend(translationTable, flatObject(data)); + } + pendingLoader = false; + deferred.resolve({ + key: key, + table: translationTable + }); + $rootScope.$emit('$translateLoadingEnd', {language: key}); + }; + onLoaderSuccess.displayName = 'onLoaderSuccess'; + + var onLoaderError = function (key) { + $rootScope.$emit('$translateLoadingError', {language: key}); + deferred.reject(key); + $rootScope.$emit('$translateLoadingEnd', {language: key}); + }; + onLoaderError.displayName = 'onLoaderError'; + + $injector.get($loaderFactory)(loaderOptions) + .then(onLoaderSuccess, onLoaderError); + + return deferred.promise; + }; + + if ($storageFactory) { + Storage = $injector.get($storageFactory); + + if (!Storage.get || !Storage.put) { + throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!'); + } + } + + // if we have additional interpolations that were added via + // $translateProvider.addInterpolation(), we have to map'em + if ($interpolatorFactories.length) { + var eachInterpolationFactory = function (interpolatorFactory) { + var interpolator = $injector.get(interpolatorFactory); + // setting initial locale for each interpolation service + interpolator.setLocale($preferredLanguage || $uses); + // make'em recognizable through id + interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator; + }; + eachInterpolationFactory.displayName = 'interpolationFactoryAdder'; + + angular.forEach($interpolatorFactories, eachInterpolationFactory); + } + + /** + * @name getTranslationTable + * @private + * + * @description + * Returns a promise that resolves to the translation table + * or is rejected if an error occurred. + * + * @param langKey + * @returns {Q.promise} + */ + var getTranslationTable = function (langKey) { + var deferred = $q.defer(); + if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) { + deferred.resolve($translationTable[langKey]); + } else if (langPromises[langKey]) { + var onResolve = function (data) { + translations(data.key, data.table); + deferred.resolve(data.table); + }; + onResolve.displayName = 'translationTableResolver'; + langPromises[langKey].then(onResolve, deferred.reject); + } else { + deferred.reject(); + } + return deferred.promise; + }; + + /** + * @name getFallbackTranslation + * @private + * + * @description + * Returns a promise that will resolve to the translation + * or be rejected if no translation was found for the language. + * This function is currently only used for fallback language translation. + * + * @param langKey The language to translate to. + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {Q.promise} + */ + var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) { + var deferred = $q.defer(); + + var onResolve = function (translationTable) { + if (Object.prototype.hasOwnProperty.call(translationTable, translationId)) { + Interpolator.setLocale(langKey); + var translation = translationTable[translationId]; + if (translation.substr(0, 2) === '@:') { + getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator) + .then(deferred.resolve, deferred.reject); + } else { + deferred.resolve(Interpolator.interpolate(translationTable[translationId], interpolateParams)); + } + Interpolator.setLocale($uses); + } else { + deferred.reject(); + } + }; + onResolve.displayName = 'fallbackTranslationResolver'; + + getTranslationTable(langKey).then(onResolve, deferred.reject); + + return deferred.promise; + }; + + /** + * @name getFallbackTranslationInstant + * @private + * + * @description + * Returns a translation + * This function is currently only used for fallback language translation. + * + * @param langKey The language to translate to. + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {string} translation + */ + var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) { + var result, translationTable = $translationTable[langKey]; + + if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) { + Interpolator.setLocale(langKey); + result = Interpolator.interpolate(translationTable[translationId], interpolateParams); + if (result.substr(0, 2) === '@:') { + return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator); + } + Interpolator.setLocale($uses); + } + + return result; + }; + + + /** + * @name translateByHandler + * @private + * + * Translate by missing translation handler. + * + * @param translationId + * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is + * absent + */ + var translateByHandler = function (translationId, interpolateParams) { + // If we have a handler factory - we might also call it here to determine if it provides + // a default text for a translationid that can't be found anywhere in our tables + if ($missingTranslationHandlerFactory) { + var resultString = $injector.get($missingTranslationHandlerFactory)(translationId, $uses, interpolateParams); + if (resultString !== undefined) { + return resultString; + } else { + return translationId; + } + } else { + return translationId; + } + }; + + /** + * @name resolveForFallbackLanguage + * @private + * + * Recursive helper function for fallbackTranslation that will sequentially look + * for a translation in the fallbackLanguages starting with fallbackLanguageIndex. + * + * @param fallbackLanguageIndex + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {Q.promise} Promise that will resolve to the translation. + */ + var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText) { + var deferred = $q.defer(); + + if (fallbackLanguageIndex < $fallbackLanguage.length) { + var langKey = $fallbackLanguage[fallbackLanguageIndex]; + getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).then( + deferred.resolve, + function () { + // Look in the next fallback language for a translation. + // It delays the resolving by passing another promise to resolve. + resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText).then(deferred.resolve); + } + ); + } else { + // No translation found in any fallback language + // if a default translation text is set in the directive, then return this as a result + if (defaultTranslationText) { + deferred.resolve(defaultTranslationText); + } else { + // if no default translation is set and an error handler is defined, send it to the handler + // and then return the result + deferred.resolve(translateByHandler(translationId, interpolateParams)); + } + } + return deferred.promise; + }; + + /** + * @name resolveForFallbackLanguageInstant + * @private + * + * Recursive helper function for fallbackTranslation that will sequentially look + * for a translation in the fallbackLanguages starting with fallbackLanguageIndex. + * + * @param fallbackLanguageIndex + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {string} translation + */ + var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) { + var result; + + if (fallbackLanguageIndex < $fallbackLanguage.length) { + var langKey = $fallbackLanguage[fallbackLanguageIndex]; + result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator); + if (!result) { + result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator); + } + } + return result; + }; + + /** + * Translates with the usage of the fallback languages. + * + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {Q.promise} Promise, that resolves to the translation. + */ + var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText) { + // Start with the fallbackLanguage with index 0 + return resolveForFallbackLanguage((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText); + }; + + /** + * Translates with the usage of the fallback languages. + * + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {String} translation + */ + var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator) { + // Start with the fallbackLanguage with index 0 + return resolveForFallbackLanguageInstant((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator); + }; + + var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText) { + + var deferred = $q.defer(); + + var table = $uses ? $translationTable[$uses] : $translationTable, + Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator; + + // if the translation id exists, we can just interpolate it + if (table && Object.prototype.hasOwnProperty.call(table, translationId)) { + var translation = table[translationId]; + + // If using link, rerun $translate with linked translationId and return it + if (translation.substr(0, 2) === '@:') { + + $translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText) + .then(deferred.resolve, deferred.reject); + } else { + deferred.resolve(Interpolator.interpolate(translation, interpolateParams)); + } + } else { + var missingTranslationHandlerTranslation; + // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise + if ($missingTranslationHandlerFactory && !pendingLoader) { + missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams); + } + + // since we couldn't translate the inital requested translation id, + // we try it now with one or more fallback languages, if fallback language(s) is + // configured. + if ($uses && $fallbackLanguage && $fallbackLanguage.length) { + fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText) + .then(function (translation) { + deferred.resolve(translation); + }, function (_translationId) { + deferred.reject(applyNotFoundIndicators(_translationId)); + }); + } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) { + // looks like the requested translation id doesn't exists. + // Now, if there is a registered handler for missing translations and no + // asyncLoader is pending, we execute the handler + if (defaultTranslationText) { + deferred.resolve(defaultTranslationText); + } else { + deferred.resolve(missingTranslationHandlerTranslation); + } + } else { + if (defaultTranslationText) { + deferred.resolve(defaultTranslationText); + } else { + deferred.reject(applyNotFoundIndicators(translationId)); + } + } + } + return deferred.promise; + }; + + var determineTranslationInstant = function (translationId, interpolateParams, interpolationId) { + + var result, table = $uses ? $translationTable[$uses] : $translationTable, + Interpolator = defaultInterpolator; + + // if the interpolation id exists use custom interpolator + if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) { + Interpolator = interpolatorHashMap[interpolationId]; + } + + // if the translation id exists, we can just interpolate it + if (table && Object.prototype.hasOwnProperty.call(table, translationId)) { + var translation = table[translationId]; + + // If using link, rerun $translate with linked translationId and return it + if (translation.substr(0, 2) === '@:') { + result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId); + } else { + result = Interpolator.interpolate(translation, interpolateParams); + } + } else { + var missingTranslationHandlerTranslation; + // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise + if ($missingTranslationHandlerFactory && !pendingLoader) { + missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams); + } + + // since we couldn't translate the inital requested translation id, + // we try it now with one or more fallback languages, if fallback language(s) is + // configured. + if ($uses && $fallbackLanguage && $fallbackLanguage.length) { + fallbackIndex = 0; + result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator); + } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) { + // looks like the requested translation id doesn't exists. + // Now, if there is a registered handler for missing translations and no + // asyncLoader is pending, we execute the handler + result = missingTranslationHandlerTranslation; + } else { + result = applyNotFoundIndicators(translationId); + } + } + + return result; + }; + + var clearNextLangAndPromise = function(key) { + if ($nextLang === key) { + $nextLang = undefined; + } + langPromises[key] = undefined; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#preferredLanguage + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the language key for the preferred language. + * + * @param {string} langKey language String or Array to be used as preferredLanguage (changing at runtime) + * + * @return {string} preferred language key + */ + $translate.preferredLanguage = function (langKey) { + if(langKey) { + setupPreferredLanguage(langKey); + } + return $preferredLanguage; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#cloakClassName + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the configured class name for `translate-cloak` directive. + * + * @return {string} cloakClassName + */ + $translate.cloakClassName = function () { + return $cloakClassName; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#nestedObjectDelimeter + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the configured delimiter for nested namespaces. + * + * @return {string} nestedObjectDelimeter + */ + $translate.nestedObjectDelimeter = function () { + return $nestedObjectDelimeter; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#fallbackLanguage + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the language key for the fallback languages or sets a new fallback stack. + * + * @param {string=} langKey language String or Array of fallback languages to be used (to change stack at runtime) + * + * @return {string||array} fallback language key + */ + $translate.fallbackLanguage = function (langKey) { + if (langKey !== undefined && langKey !== null) { + fallbackStack(langKey); + + // as we might have an async loader initiated and a new translation language might have been defined + // we need to add the promise to the stack also. So - iterate. + if ($loaderFactory) { + if ($fallbackLanguage && $fallbackLanguage.length) { + for (var i = 0, len = $fallbackLanguage.length; i < len; i++) { + if (!langPromises[$fallbackLanguage[i]]) { + langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]); + } + } + } + } + $translate.use($translate.use()); + } + if ($fallbackWasString) { + return $fallbackLanguage[0]; + } else { + return $fallbackLanguage; + } + + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#useFallbackLanguage + * @methodOf pascalprecht.translate.$translate + * + * @description + * Sets the first key of the fallback language stack to be used for translation. + * Therefore all languages in the fallback array BEFORE this key will be skipped! + * + * @param {string=} langKey Contains the langKey the iteration shall start with. Set to false if you want to + * get back to the whole stack + */ + $translate.useFallbackLanguage = function (langKey) { + if (langKey !== undefined && langKey !== null) { + if (!langKey) { + startFallbackIteration = 0; + } else { + var langKeyPosition = indexOf($fallbackLanguage, langKey); + if (langKeyPosition > -1) { + startFallbackIteration = langKeyPosition; + } + } + + } + + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#proposedLanguage + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the language key of language that is currently loaded asynchronously. + * + * @return {string} language key + */ + $translate.proposedLanguage = function () { + return $nextLang; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#storage + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns registered storage. + * + * @return {object} Storage + */ + $translate.storage = function () { + return Storage; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#use + * @methodOf pascalprecht.translate.$translate + * + * @description + * Tells angular-translate which language to use by given language key. This method is + * used to change language at runtime. It also takes care of storing the language + * key in a configured store to let your app remember the choosed language. + * + * When trying to 'use' a language which isn't available it tries to load it + * asynchronously with registered loaders. + * + * Returns promise object with loaded language file data or string of the currently used language. + * + * If no or a falsy key is given it returns the currently used language key. + * The returned string will be ```undefined``` if setting up $translate hasn't finished. + * @example + * $translate.use("en_US").then(function(data){ + * $scope.text = $translate("HELLO"); + * }); + * + * @param {string} [key] Language key + * @return {object|string} Promise with loaded language data or the language key if a falsy param was given. + */ + $translate.use = function (key) { + if (!key) { + return $uses; + } + + var deferred = $q.defer(); + + $rootScope.$emit('$translateChangeStart', {language: key}); + + // Try to get the aliased language key + var aliasedKey = negotiateLocale(key); + if (aliasedKey) { + key = aliasedKey; + } + + // if there isn't a translation table for the language we've requested, + // we load it asynchronously + if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) { + $nextLang = key; + langPromises[key] = loadAsync(key).then(function (translation) { + translations(translation.key, translation.table); + deferred.resolve(translation.key); + if ($nextLang === key) { + useLanguage(translation.key); + } + return translation; + }, function (key) { + $rootScope.$emit('$translateChangeError', {language: key}); + deferred.reject(key); + $rootScope.$emit('$translateChangeEnd', {language: key}); + return $q.reject(key); + }); + langPromises[key]['finally'](function () { + clearNextLangAndPromise(key); + }); + } else if ($nextLang === key && langPromises[key]) { + // we are already loading this asynchronously + // resolve our new deferred when the old langPromise is resolved + langPromises[key].then(function (translation) { + deferred.resolve(translation.key); + return translation; + }, function (key) { + deferred.reject(key); + return $q.reject(key); + }); + } else { + deferred.resolve(key); + useLanguage(key); + } + + return deferred.promise; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#storageKey + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the key for the storage. + * + * @return {string} storage key + */ + $translate.storageKey = function () { + return storageKey(); + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#isPostCompilingEnabled + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether post compiling is enabled or not + * + * @return {bool} storage key + */ + $translate.isPostCompilingEnabled = function () { + return $postCompilingEnabled; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#isForceAsyncReloadEnabled + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether force async reload is enabled or not + * + * @return {boolean} forceAsyncReload value + */ + $translate.isForceAsyncReloadEnabled = function () { + return $forceAsyncReloadEnabled; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#refresh + * @methodOf pascalprecht.translate.$translate + * + * @description + * Refreshes a translation table pointed by the given langKey. If langKey is not specified, + * the module will drop all existent translation tables and load new version of those which + * are currently in use. + * + * Refresh means that the module will drop target translation table and try to load it again. + * + * In case there are no loaders registered the refresh() method will throw an Error. + * + * If the module is able to refresh translation tables refresh() method will broadcast + * $translateRefreshStart and $translateRefreshEnd events. + * + * @example + * // this will drop all currently existent translation tables and reload those which are + * // currently in use + * $translate.refresh(); + * // this will refresh a translation table for the en_US language + * $translate.refresh('en_US'); + * + * @param {string} langKey A language key of the table, which has to be refreshed + * + * @return {promise} Promise, which will be resolved in case a translation tables refreshing + * process is finished successfully, and reject if not. + */ + $translate.refresh = function (langKey) { + if (!$loaderFactory) { + throw new Error('Couldn\'t refresh translation table, no loader registered!'); + } + + var deferred = $q.defer(); + + function resolve() { + deferred.resolve(); + $rootScope.$emit('$translateRefreshEnd', {language: langKey}); + } + + function reject() { + deferred.reject(); + $rootScope.$emit('$translateRefreshEnd', {language: langKey}); + } + + $rootScope.$emit('$translateRefreshStart', {language: langKey}); + + if (!langKey) { + // if there's no language key specified we refresh ALL THE THINGS! + var tables = [], loadingKeys = {}; + + // reload registered fallback languages + if ($fallbackLanguage && $fallbackLanguage.length) { + for (var i = 0, len = $fallbackLanguage.length; i < len; i++) { + tables.push(loadAsync($fallbackLanguage[i])); + loadingKeys[$fallbackLanguage[i]] = true; + } + } + + // reload currently used language + if ($uses && !loadingKeys[$uses]) { + tables.push(loadAsync($uses)); + } + + var allTranslationsLoaded = function (tableData) { + $translationTable = {}; + angular.forEach(tableData, function (data) { + translations(data.key, data.table); + }); + if ($uses) { + useLanguage($uses); + } + resolve(); + }; + allTranslationsLoaded.displayName = 'refreshPostProcessor'; + + $q.all(tables).then(allTranslationsLoaded, reject); + + } else if ($translationTable[langKey]) { + + var oneTranslationsLoaded = function (data) { + translations(data.key, data.table); + if (langKey === $uses) { + useLanguage($uses); + } + resolve(); + }; + oneTranslationsLoaded.displayName = 'refreshPostProcessor'; + + loadAsync(langKey).then(oneTranslationsLoaded, reject); + + } else { + reject(); + } + return deferred.promise; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#instant + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns a translation instantly from the internal state of loaded translation. All rules + * regarding the current language, the preferred language of even fallback languages will be + * used except any promise handling. If a language was not found, an asynchronous loading + * will be invoked in the background. + * + * @param {string|array} translationId A token which represents a translation id + * This can be optionally an array of translation ids which + * results that the function's promise returns an object where + * each key is the translation id and the value the translation. + * @param {object} interpolateParams Params + * @param {string} interpolationId The id of the interpolation to use + * + * @return {string|object} translation + */ + $translate.instant = function (translationId, interpolateParams, interpolationId) { + + // Detect undefined and null values to shorten the execution and prevent exceptions + if (translationId === null || angular.isUndefined(translationId)) { + return translationId; + } + + // Duck detection: If the first argument is an array, a bunch of translations was requested. + // The result is an object. + if (angular.isArray(translationId)) { + var results = {}; + for (var i = 0, c = translationId.length; i < c; i++) { + results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId); + } + return results; + } + + // We discarded unacceptable values. So we just need to verify if translationId is empty String + if (angular.isString(translationId) && translationId.length < 1) { + return translationId; + } + + // trim off any whitespace + if (translationId) { + translationId = trim.apply(translationId); + } + + var result, possibleLangKeys = []; + if ($preferredLanguage) { + possibleLangKeys.push($preferredLanguage); + } + if ($uses) { + possibleLangKeys.push($uses); + } + if ($fallbackLanguage && $fallbackLanguage.length) { + possibleLangKeys = possibleLangKeys.concat($fallbackLanguage); + } + for (var j = 0, d = possibleLangKeys.length; j < d; j++) { + var possibleLangKey = possibleLangKeys[j]; + if ($translationTable[possibleLangKey]) { + if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') { + result = determineTranslationInstant(translationId, interpolateParams, interpolationId); + } else if ($notFoundIndicatorLeft || $notFoundIndicatorRight) { + result = applyNotFoundIndicators(translationId); + } + } + if (typeof result !== 'undefined') { + break; + } + } + + if (!result && result !== '') { + // Return translation of default interpolator if not found anything. + result = defaultInterpolator.interpolate(translationId, interpolateParams); + if ($missingTranslationHandlerFactory && !pendingLoader) { + result = translateByHandler(translationId, interpolateParams); + } + } + + return result; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#versionInfo + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the current version information for the angular-translate library + * + * @return {string} angular-translate version + */ + $translate.versionInfo = function () { + return version; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#loaderCache + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns the defined loaderCache. + * + * @return {boolean|string|object} current value of loaderCache + */ + $translate.loaderCache = function () { + return loaderCache; + }; + + // internal purpose only + $translate.directivePriority = function () { + return directivePriority; + }; + + // internal purpose only + $translate.statefulFilter = function () { + return statefulFilter; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#isReady + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether the service is "ready" to translate (i.e. loading 1st language). + * + * See also {@link pascalprecht.translate.$translate#methods_onReady onReady()}. + * + * @return {boolean} current value of ready + */ + $translate.isReady = function () { + return $isReady; + }; + + var $onReadyDeferred = $q.defer(); + $onReadyDeferred.promise.then(function () { + $isReady = true; + }); + + /** + * @ngdoc function + * @name pascalprecht.translate.$translate#onReady + * @methodOf pascalprecht.translate.$translate + * + * @description + * Returns whether the service is "ready" to translate (i.e. loading 1st language). + * + * See also {@link pascalprecht.translate.$translate#methods_isReady isReady()}. + * + * @param {Function=} fn Function to invoke when service is ready + * @return {object} Promise resolved when service is ready + */ + $translate.onReady = function (fn) { + var deferred = $q.defer(); + if (angular.isFunction(fn)) { + deferred.promise.then(fn); + } + if ($isReady) { + deferred.resolve(); + } else { + $onReadyDeferred.promise.then(deferred.resolve); + } + return deferred.promise; + }; + + // Whenever $translateReady is being fired, this will ensure the state of $isReady + var globalOnReadyListener = $rootScope.$on('$translateReady', function () { + $onReadyDeferred.resolve(); + globalOnReadyListener(); // one time only + globalOnReadyListener = null; + }); + var globalOnChangeListener = $rootScope.$on('$translateChangeEnd', function () { + $onReadyDeferred.resolve(); + globalOnChangeListener(); // one time only + globalOnChangeListener = null; + }); + + if ($loaderFactory) { + + // If at least one async loader is defined and there are no + // (default) translations available we should try to load them. + if (angular.equals($translationTable, {})) { + if ($translate.use()) { + $translate.use($translate.use()); + } + } + + // Also, if there are any fallback language registered, we start + // loading them asynchronously as soon as we can. + if ($fallbackLanguage && $fallbackLanguage.length) { + var processAsyncResult = function (translation) { + translations(translation.key, translation.table); + $rootScope.$emit('$translateChangeEnd', { language: translation.key }); + return translation; + }; + for (var i = 0, len = $fallbackLanguage.length; i < len; i++) { + var fallbackLanguageId = $fallbackLanguage[i]; + if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) { + langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult); + } + } + } + } else { + $rootScope.$emit('$translateReady', { language: $translate.use() }); + } + + return $translate; + } + ]; +} +$translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider']; + +$translate.displayName = 'displayName'; + +/** + * @ngdoc object + * @name pascalprecht.translate.$translateDefaultInterpolation + * @requires $interpolate + * + * @description + * Uses angular's `$interpolate` services to interpolate strings against some values. + * + * Be aware to configure a proper sanitization strategy. + * + * See also: + * * {@link pascalprecht.translate.$translateSanitization} + * + * @return {object} $translateDefaultInterpolation Interpolator service + */ +angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', $translateDefaultInterpolation); + +function $translateDefaultInterpolation ($interpolate, $translateSanitization) { + + 'use strict'; + + var $translateInterpolator = {}, + $locale, + $identifier = 'default'; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateDefaultInterpolation#setLocale + * @methodOf pascalprecht.translate.$translateDefaultInterpolation + * + * @description + * Sets current locale (this is currently not use in this interpolation). + * + * @param {string} locale Language key or locale. + */ + $translateInterpolator.setLocale = function (locale) { + $locale = locale; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateDefaultInterpolation#getInterpolationIdentifier + * @methodOf pascalprecht.translate.$translateDefaultInterpolation + * + * @description + * Returns an identifier for this interpolation service. + * + * @returns {string} $identifier + */ + $translateInterpolator.getInterpolationIdentifier = function () { + return $identifier; + }; + + /** + * @deprecated will be removed in 3.0 + * @see {@link pascalprecht.translate.$translateSanitization} + */ + $translateInterpolator.useSanitizeValueStrategy = function (value) { + $translateSanitization.useStrategy(value); + return this; + }; + + /** + * @ngdoc function + * @name pascalprecht.translate.$translateDefaultInterpolation#interpolate + * @methodOf pascalprecht.translate.$translateDefaultInterpolation + * + * @description + * Interpolates given string agains given interpolate params using angulars + * `$interpolate` service. + * + * @returns {string} interpolated string. + */ + $translateInterpolator.interpolate = function (string, interpolationParams) { + interpolationParams = interpolationParams || {}; + interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params'); + + var interpolatedText = $interpolate(string)(interpolationParams); + interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text'); + + return interpolatedText; + }; + + return $translateInterpolator; +} +$translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization']; + +$translateDefaultInterpolation.displayName = '$translateDefaultInterpolation'; + +angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY'); + +angular.module('pascalprecht.translate') +/** + * @ngdoc directive + * @name pascalprecht.translate.directive:translate + * @requires $compile + * @requires $filter + * @requires $interpolate + * @restrict A + * + * @description + * Translates given translation id either through attribute or DOM content. + * Internally it uses `translate` filter to translate translation id. It possible to + * pass an optional `translate-values` object literal as string into translation id. + * + * @param {string=} translate Translation id which could be either string or interpolated string. + * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object. + * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute. + * @param {string=} translate-default will be used unless translation was successful + * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translateProvider#methods_usePostCompiling} + * + * @example + + +
    + +
    
    +        
    TRANSLATION_ID
    +
    
    +        
    
    +        
    {{translationId}}
    +
    
    +        
    WITH_VALUES
    +
    
    +        
    WITH_VALUES
    +
    
    +
    +      
    +
    + + angular.module('ngView', ['pascalprecht.translate']) + + .config(function ($translateProvider) { + + $translateProvider.translations('en',{ + 'TRANSLATION_ID': 'Hello there!', + 'WITH_VALUES': 'The following value is dynamic: {{value}}' + }).preferredLanguage('en'); + + }); + + angular.module('ngView').controller('TranslateCtrl', function ($scope) { + $scope.translationId = 'TRANSLATION_ID'; + + $scope.values = { + value: 78 + }; + }); + + + it('should translate', function () { + inject(function ($rootScope, $compile) { + $rootScope.translationId = 'TRANSLATION_ID'; + + element = $compile('

    ')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('Hello there!'); + + element = $compile('

    ')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('Hello there!'); + + element = $compile('

    TRANSLATION_ID

    ')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('Hello there!'); + + element = $compile('

    {{translationId}}

    ')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('Hello there!'); + + element = $compile('

    ')($rootScope); + $rootScope.$digest(); + expect(element.attr('title')).toBe('Hello there!'); + }); + }); +
    +
    + */ +.directive('translate', translateDirective); +function translateDirective($translate, $q, $interpolate, $compile, $parse, $rootScope) { + + 'use strict'; + + /** + * @name trim + * @private + * + * @description + * trim polyfill + * + * @returns {string} The string stripped of whitespace from both ends + */ + var trim = function() { + return this.toString().replace(/^\s+|\s+$/g, ''); + }; + + return { + restrict: 'AE', + scope: true, + priority: $translate.directivePriority(), + compile: function (tElement, tAttr) { + + var translateValuesExist = (tAttr.translateValues) ? + tAttr.translateValues : undefined; + + var translateInterpolation = (tAttr.translateInterpolation) ? + tAttr.translateInterpolation : undefined; + + var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i); + + var interpolateRegExp = '^(.*)(' + $interpolate.startSymbol() + '.*' + $interpolate.endSymbol() + ')(.*)', + watcherRegExp = '^(.*)' + $interpolate.startSymbol() + '(.*)' + $interpolate.endSymbol() + '(.*)'; + + return function linkFn(scope, iElement, iAttr) { + + scope.interpolateParams = {}; + scope.preText = ''; + scope.postText = ''; + scope.translateNamespace = getTranslateNamespace(scope); + var translationIds = {}; + + var initInterpolationParams = function (interpolateParams, iAttr, tAttr) { + // initial setup + if (iAttr.translateValues) { + angular.extend(interpolateParams, $parse(iAttr.translateValues)(scope.$parent)); + } + // initially fetch all attributes if existing and fill the params + if (translateValueExist) { + for (var attr in tAttr) { + if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') { + var attributeName = angular.lowercase(attr.substr(14, 1)) + attr.substr(15); + interpolateParams[attributeName] = tAttr[attr]; + } + } + } + }; + + // Ensures any change of the attribute "translate" containing the id will + // be re-stored to the scope's "translationId". + // If the attribute has no content, the element's text value (white spaces trimmed off) will be used. + var observeElementTranslation = function (translationId) { + + // Remove any old watcher + if (angular.isFunction(observeElementTranslation._unwatchOld)) { + observeElementTranslation._unwatchOld(); + observeElementTranslation._unwatchOld = undefined; + } + + if (angular.equals(translationId , '') || !angular.isDefined(translationId)) { + var iElementText = trim.apply(iElement.text()); + + // Resolve translation id by inner html if required + var interpolateMatches = iElementText.match(interpolateRegExp); + // Interpolate translation id if required + if (angular.isArray(interpolateMatches)) { + scope.preText = interpolateMatches[1]; + scope.postText = interpolateMatches[3]; + translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent); + var watcherMatches = iElementText.match(watcherRegExp); + if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) { + observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) { + translationIds.translate = newValue; + updateTranslations(); + }); + } + } else { + translationIds.translate = iElementText; + } + } else { + translationIds.translate = translationId; + } + updateTranslations(); + }; + + var observeAttributeTranslation = function (translateAttr) { + iAttr.$observe(translateAttr, function (translationId) { + translationIds[translateAttr] = translationId; + updateTranslations(); + }); + }; + + // initial setup with values + initInterpolationParams(scope.interpolateParams, iAttr, tAttr); + + var firstAttributeChangedEvent = true; + iAttr.$observe('translate', function (translationId) { + if (typeof translationId === 'undefined') { + // case of element "xyz" + observeElementTranslation(''); + } else { + // case of regular attribute + if (translationId !== '' || !firstAttributeChangedEvent) { + translationIds.translate = translationId; + updateTranslations(); + } + } + firstAttributeChangedEvent = false; + }); + + for (var translateAttr in iAttr) { + if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr') { + observeAttributeTranslation(translateAttr); + } + } + + iAttr.$observe('translateDefault', function (value) { + scope.defaultText = value; + }); + + if (translateValuesExist) { + iAttr.$observe('translateValues', function (interpolateParams) { + if (interpolateParams) { + scope.$parent.$watch(function () { + angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent)); + }); + } + }); + } + + if (translateValueExist) { + var observeValueAttribute = function (attrName) { + iAttr.$observe(attrName, function (value) { + var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15); + scope.interpolateParams[attributeName] = value; + }); + }; + for (var attr in iAttr) { + if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') { + observeValueAttribute(attr); + } + } + } + + // Master update function + var updateTranslations = function () { + for (var key in translationIds) { + + if (translationIds.hasOwnProperty(key) && translationIds[key] !== undefined) { + updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText, scope.translateNamespace); + } + } + }; + + // Put translation processing function outside loop + var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText, translateNamespace) { + if (translationId) { + // if translation id starts with '.' and translateNamespace given, prepend namespace + if (translateNamespace && translationId.charAt(0) === '.') { + translationId = translateNamespace + translationId; + } + + $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText) + .then(function (translation) { + applyTranslation(translation, scope, true, translateAttr); + }, function (translationId) { + applyTranslation(translationId, scope, false, translateAttr); + }); + } else { + // as an empty string cannot be translated, we can solve this using successful=false + applyTranslation(translationId, scope, false, translateAttr); + } + }; + + var applyTranslation = function (value, scope, successful, translateAttr) { + if (translateAttr === 'translate') { + // default translate into innerHTML + if (!successful && typeof scope.defaultText !== 'undefined') { + value = scope.defaultText; + } + iElement.empty().append(scope.preText + value + scope.postText); + var globallyEnabled = $translate.isPostCompilingEnabled(); + var locallyDefined = typeof tAttr.translateCompile !== 'undefined'; + var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false'; + if ((globallyEnabled && !locallyDefined) || locallyEnabled) { + $compile(iElement.contents())(scope); + } + } else { + // translate attribute + if (!successful && typeof scope.defaultText !== 'undefined') { + value = scope.defaultText; + } + var attributeName = iAttr.$attr[translateAttr]; + if (attributeName.substr(0, 5) === 'data-') { + // ensure html5 data prefix is stripped + attributeName = attributeName.substr(5); + } + attributeName = attributeName.substr(15); + iElement.attr(attributeName, value); + } + }; + + if (translateValuesExist || translateValueExist || iAttr.translateDefault) { + scope.$watch('interpolateParams', updateTranslations, true); + } + + // Ensures the text will be refreshed after the current language was changed + // w/ $translate.use(...) + var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations); + + // ensure translation will be looked up at least one + if (iElement.text().length) { + if (iAttr.translate) { + observeElementTranslation(iAttr.translate); + } else { + observeElementTranslation(''); + } + } else if (iAttr.translate) { + // ensure attribute will be not skipped + observeElementTranslation(iAttr.translate); + } + updateTranslations(); + scope.$on('$destroy', unbind); + }; + } + }; +} +translateDirective.$inject = ['$translate', '$q', '$interpolate', '$compile', '$parse', '$rootScope']; + +/** + * Returns the scope's namespace. + * @private + * @param scope + * @returns {string} + */ +function getTranslateNamespace(scope) { + 'use strict'; + if (scope.translateNamespace) { + return scope.translateNamespace; + } + if (scope.$parent) { + return getTranslateNamespace(scope.$parent); + } +} + +translateDirective.displayName = 'translateDirective'; + +angular.module('pascalprecht.translate') +/** + * @ngdoc directive + * @name pascalprecht.translate.directive:translateCloak + * @requires $rootScope + * @requires $translate + * @restrict A + * + * $description + * Adds a `translate-cloak` class name to the given element where this directive + * is applied initially and removes it, once a loader has finished loading. + * + * This directive can be used to prevent initial flickering when loading translation + * data asynchronously. + * + * The class name is defined in + * {@link pascalprecht.translate.$translateProvider#cloakClassName $translate.cloakClassName()}. + * + * @param {string=} translate-cloak If a translationId is provided, it will be used for showing + * or hiding the cloak. Basically it relies on the translation + * resolve. + */ +.directive('translateCloak', translateCloakDirective); + +function translateCloakDirective($translate) { + + 'use strict'; + + return { + compile: function (tElement) { + var applyCloak = function () { + tElement.addClass($translate.cloakClassName()); + }, + removeCloak = function () { + tElement.removeClass($translate.cloakClassName()); + }; + $translate.onReady(function () { + removeCloak(); + }); + applyCloak(); + + return function linkFn(scope, iElement, iAttr) { + // Register a watcher for the defined translation allowing a fine tuned cloak + if (iAttr.translateCloak && iAttr.translateCloak.length) { + iAttr.$observe('translateCloak', function (translationId) { + $translate(translationId).then(removeCloak, applyCloak); + }); + } + }; + } + }; +} +translateCloakDirective.$inject = ['$translate']; + +translateCloakDirective.displayName = 'translateCloakDirective'; + +angular.module('pascalprecht.translate') +/** + * @ngdoc directive + * @name pascalprecht.translate.directive:translateNamespace + * @restrict A + * + * @description + * Translates given translation id either through attribute or DOM content. + * Internally it uses `translate` filter to translate translation id. It possible to + * pass an optional `translate-values` object literal as string into translation id. + * + * @param {string=} translate namespace name which could be either string or interpolated string. + * + * @example + + +
    + +
    +

    .HEADERS.TITLE

    +

    .HEADERS.WELCOME

    +
    + +
    +

    .TITLE

    +

    .WELCOME

    +
    + +
    +
    + + angular.module('ngView', ['pascalprecht.translate']) + + .config(function ($translateProvider) { + + $translateProvider.translations('en',{ + 'TRANSLATION_ID': 'Hello there!', + 'CONTENT': { + 'HEADERS': { + TITLE: 'Title' + } + }, + 'CONTENT.HEADERS.WELCOME': 'Welcome' + }).preferredLanguage('en'); + + }); + + +
    + */ +.directive('translateNamespace', translateNamespaceDirective); + +function translateNamespaceDirective() { + + 'use strict'; + + return { + restrict: 'A', + scope: true, + compile: function () { + return { + pre: function (scope, iElement, iAttrs) { + scope.translateNamespace = getTranslateNamespace(scope); + + if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') { + scope.translateNamespace += iAttrs.translateNamespace; + } else { + scope.translateNamespace = iAttrs.translateNamespace; + } + } + }; + } + }; +} + +/** + * Returns the scope's namespace. + * @private + * @param scope + * @returns {string} + */ +function getTranslateNamespace(scope) { + 'use strict'; + if (scope.translateNamespace) { + return scope.translateNamespace; + } + if (scope.$parent) { + return getTranslateNamespace(scope.$parent); + } +} + +translateNamespaceDirective.displayName = 'translateNamespaceDirective'; + +angular.module('pascalprecht.translate') +/** + * @ngdoc filter + * @name pascalprecht.translate.filter:translate + * @requires $parse + * @requires pascalprecht.translate.$translate + * @function + * + * @description + * Uses `$translate` service to translate contents. Accepts interpolate parameters + * to pass dynamized values though translation. + * + * @param {string} translationId A translation id to be translated. + * @param {*=} interpolateParams Optional object literal (as hash or string) to pass values into translation. + * + * @returns {string} Translated text. + * + * @example + + +
    + +
    {{ 'TRANSLATION_ID' | translate }}
    +
    {{ translationId | translate }}
    +
    {{ 'WITH_VALUES' | translate:'{value: 5}' }}
    +
    {{ 'WITH_VALUES' | translate:values }}
    + +
    +
    + + angular.module('ngView', ['pascalprecht.translate']) + + .config(function ($translateProvider) { + + $translateProvider.translations('en', { + 'TRANSLATION_ID': 'Hello there!', + 'WITH_VALUES': 'The following value is dynamic: {{value}}' + }); + $translateProvider.preferredLanguage('en'); + + }); + + angular.module('ngView').controller('TranslateCtrl', function ($scope) { + $scope.translationId = 'TRANSLATION_ID'; + + $scope.values = { + value: 78 + }; + }); + +
    + */ +.filter('translate', translateFilterFactory); + +function translateFilterFactory($parse, $translate) { + + 'use strict'; + + var translateFilter = function (translationId, interpolateParams, interpolation) { + + if (!angular.isObject(interpolateParams)) { + interpolateParams = $parse(interpolateParams)(this); + } + + return $translate.instant(translationId, interpolateParams, interpolation); + }; + + if ($translate.statefulFilter()) { + translateFilter.$stateful = true; + } + + return translateFilter; +} +translateFilterFactory.$inject = ['$parse', '$translate']; + +translateFilterFactory.displayName = 'translateFilterFactory'; + +angular.module('pascalprecht.translate') + +/** + * @ngdoc object + * @name pascalprecht.translate.$translationCache + * @requires $cacheFactory + * + * @description + * The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You + * can load translation tables directly into the cache by consuming the + * `$translationCache` service directly. + * + * @return {object} $cacheFactory object. + */ + .factory('$translationCache', $translationCache); + +function $translationCache($cacheFactory) { + + 'use strict'; + + return $cacheFactory('translations'); +} +$translationCache.$inject = ['$cacheFactory']; + +$translationCache.displayName = '$translationCache'; +return 'pascalprecht.translate'; + +})); diff --git a/vendor/assets/components/angular-translate/angular-translate.min.js b/vendor/assets/components/angular-translate/angular-translate.min.js new file mode 100644 index 000000000..6db6b84b2 --- /dev/null +++ b/vendor/assets/components/angular-translate/angular-translate.min.js @@ -0,0 +1,6 @@ +/*! + * angular-translate - v2.8.1 - 2015-10-01 + * + * Copyright (c) 2015 The angular-translate team, Pascal Precht; Licensed MIT + */ +!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a){"use strict";var b=a.storageKey(),c=a.storage(),d=function(){var d=a.preferredLanguage();angular.isString(d)?a.use(d):c.put(b,a.use())};d.displayName="fallbackFromIncorrectStorageValue",c?c.get(b)?a.use(c.get(b))["catch"](d):d():angular.isString(a.preferredLanguage())&&a.use(a.preferredLanguage())}function b(){"use strict";var a,b,c=null,d=!1,e=!1;b={sanitize:function(a,b){return"text"===b&&(a=g(a)),a},escape:function(a,b){return"text"===b&&(a=f(a)),a},sanitizeParameters:function(a,b){return"params"===b&&(a=h(a,g)),a},escapeParameters:function(a,b){return"params"===b&&(a=h(a,f)),a}},b.escaped=b.escapeParameters,this.addStrategy=function(a,c){return b[a]=c,this},this.removeStrategy=function(a){return delete b[a],this},this.useStrategy=function(a){return d=!0,c=a,this},this.$get=["$injector","$log",function(f,g){var h={},i=function(a,c,d){return angular.forEach(d,function(d){if(angular.isFunction(d))a=d(a,c);else if(angular.isFunction(b[d]))a=b[d](a,c);else{if(!angular.isString(b[d]))throw new Error("pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: '"+d+"'");if(!h[b[d]])try{h[b[d]]=f.get(b[d])}catch(e){throw h[b[d]]=function(){},new Error("pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: '"+d+"'")}a=h[b[d]](a,c)}}),a},j=function(){d||e||(g.warn("pascalprecht.translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details."),e=!0)};return f.has("$sanitize")&&(a=f.get("$sanitize")),{useStrategy:function(a){return function(b){a.useStrategy(b)}}(this),sanitize:function(a,b,d){if(c||j(),arguments.length<3&&(d=c),!d)return a;var e=angular.isArray(d)?d:[d];return i(a,b,e)}}}];var f=function(a){var b=angular.element("
    ");return b.text(a),b.html()},g=function(b){if(!a)throw new Error("pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as 'escape'.");return a(b)},h=function(a,b){if(angular.isObject(a)){var c=angular.isArray(a)?[]:{};return angular.forEach(a,function(a,d){c[d]=h(a,b)}),c}return angular.isNumber(a)?a:b(a)}}function c(a,b,c,d){"use strict";var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t={},u=[],v=a,w=[],x="translate-cloak",y=!1,z=!1,A=".",B=!1,C=0,D=!0,E="default",F={"default":function(a){return(a||"").split("-").join("_")},java:function(a){var b=(a||"").split("-").join("_"),c=b.split("_");return c.length>1?c[0].toLowerCase()+"_"+c[1].toUpperCase():b},bcp47:function(a){var b=(a||"").split("_").join("-"),c=b.split("-");return c.length>1?c[0].toLowerCase()+"-"+c[1].toUpperCase():b}},G="2.8.1",H=function(){if(angular.isFunction(d.getLocale))return d.getLocale();var a,c,e=b.$get().navigator,f=["language","browserLanguage","systemLanguage","userLanguage"];if(angular.isArray(e.languages))for(a=0;ac;c++)if(a[c]===b)return c;return-1},K=function(){return this.toString().replace(/^\s+|\s+$/g,"")},L=function(a){for(var b=[],c=angular.lowercase(a),d=0,e=u.length;e>d;d++)b.push(angular.lowercase(u[d]));if(J(b,c)>-1)return a;if(f){var g;for(var h in f){var i=!1,j=Object.prototype.hasOwnProperty.call(f,h)&&angular.lowercase(h)===angular.lowercase(a);if("*"===h.slice(-1)&&(i=h.slice(0,-1)===a.slice(0,h.length-1)),(j||i)&&(g=f[h],J(b,angular.lowercase(g))>-1))return g}}if(a){var k=a.split("_");if(k.length>1&&J(b,angular.lowercase(k[0]))>-1)return k[0]}return a},M=function(a,b){if(!a&&!b)return t;if(a&&!b){if(angular.isString(a))return t[a]}else angular.isObject(t[a])||(t[a]={}),angular.extend(t[a],N(b));return this};this.translations=M,this.cloakClassName=function(a){return a?(x=a,this):x},this.nestedObjectDelimeter=function(a){return a?(A=a,this):A};var N=function(a,b,c,d){var e,f,g,h;b||(b=[]),c||(c={});for(e in a)Object.prototype.hasOwnProperty.call(a,e)&&(h=a[e],angular.isObject(h)?N(h,b.concat(e),c,e):(f=b.length?""+b.join(A)+A+e:e,b.length&&e===d&&(g=""+b.join(A),c[g]="@:"+f),c[f]=h));return c};N.displayName="flatObject",this.addInterpolation=function(a){return w.push(a),this},this.useMessageFormatInterpolation=function(){return this.useInterpolation("$translateMessageFormatInterpolation")},this.useInterpolation=function(a){return n=a,this},this.useSanitizeValueStrategy=function(a){return c.useStrategy(a),this},this.preferredLanguage=function(a){return a?(O(a),this):e};var O=function(a){return a&&(e=a),e};this.translationNotFoundIndicator=function(a){return this.translationNotFoundIndicatorLeft(a),this.translationNotFoundIndicatorRight(a),this},this.translationNotFoundIndicatorLeft=function(a){return a?(q=a,this):q},this.translationNotFoundIndicatorRight=function(a){return a?(r=a,this):r},this.fallbackLanguage=function(a){return P(a),this};var P=function(a){return a?(angular.isString(a)?(h=!0,g=[a]):angular.isArray(a)&&(h=!1,g=a),angular.isString(e)&&J(g,e)<0&&g.push(e),this):h?g[0]:g};this.use=function(a){if(a){if(!t[a]&&!o)throw new Error("$translateProvider couldn't find translationTable for langKey: '"+a+"'");return i=a,this}return i};var Q=function(a){return a?(v=a,this):l?l+v:v};this.storageKey=Q,this.useUrlLoader=function(a,b){return this.useLoader("$translateUrlLoader",angular.extend({url:a},b))},this.useStaticFilesLoader=function(a){return this.useLoader("$translateStaticFilesLoader",a)},this.useLoader=function(a,b){return o=a,p=b||{},this},this.useLocalStorage=function(){return this.useStorage("$translateLocalStorage")},this.useCookieStorage=function(){return this.useStorage("$translateCookieStorage")},this.useStorage=function(a){return k=a,this},this.storagePrefix=function(a){return a?(l=a,this):a},this.useMissingTranslationHandlerLog=function(){return this.useMissingTranslationHandler("$translateMissingTranslationHandlerLog")},this.useMissingTranslationHandler=function(a){return m=a,this},this.usePostCompiling=function(a){return y=!!a,this},this.forceAsyncReload=function(a){return z=!!a,this},this.uniformLanguageTag=function(a){return a?angular.isString(a)&&(a={standard:a}):a={},E=a.standard,this},this.determinePreferredLanguage=function(a){var b=a&&angular.isFunction(a)?a():I();return e=u.length?L(b):b,this},this.registerAvailableLanguageKeys=function(a,b){return a?(u=a,b&&(f=b),this):u},this.useLoaderCache=function(a){return a===!1?s=void 0:a===!0?s=!0:"undefined"==typeof a?s="$translationCache":a&&(s=a),this},this.directivePriority=function(a){return void 0===a?C:(C=a,this)},this.statefulFilter=function(a){return void 0===a?D:(D=a,this)},this.$get=["$log","$injector","$rootScope","$q",function(a,b,c,d){var f,l,u,E=b.get(n||"$translateDefaultInterpolation"),F=!1,H={},I={},R=function(a,b,c,h){if(angular.isArray(a)){var j=function(a){for(var e={},f=[],g=function(a){var f=d.defer(),g=function(b){e[a]=b,f.resolve([a,b])};return R(a,b,c,h).then(g,g),f.promise},i=0,j=a.length;j>i;i++)f.push(g(a[i]));return d.all(f).then(function(){return e})};return j(a)}var m=d.defer();a&&(a=K.apply(a));var n=function(){var a=e?I[e]:I[i];if(l=0,k&&!a){var b=f.get(v);if(a=I[b],g&&g.length){var c=J(g,b);l=0===c?1:0,J(g,e)<0&&g.push(e)}}return a}();if(n){var o=function(){ca(a,b,c,h).then(m.resolve,m.reject)};o.displayName="promiseResolved",n["finally"](o,m.reject)}else ca(a,b,c,h).then(m.resolve,m.reject);return m.promise},S=function(a){return q&&(a=[q,a].join(" ")),r&&(a=[a,r].join(" ")),a},T=function(a){i=a,k&&f.put(R.storageKey(),i),c.$emit("$translateChangeSuccess",{language:a}),E.setLocale(i);var b=function(a,b){H[b].setLocale(i)};b.displayName="eachInterpolatorLocaleSetter",angular.forEach(H,b),c.$emit("$translateChangeEnd",{language:a})},U=function(a){if(!a)throw"No language key specified for loading.";var e=d.defer();c.$emit("$translateLoadingStart",{language:a}),F=!0;var f=s;"string"==typeof f&&(f=b.get(f));var g=angular.extend({},p,{key:a,$http:angular.extend({},{cache:f},p.$http)}),h=function(b){var d={};c.$emit("$translateLoadingSuccess",{language:a}),angular.isArray(b)?angular.forEach(b,function(a){angular.extend(d,N(a))}):angular.extend(d,N(b)),F=!1,e.resolve({key:a,table:d}),c.$emit("$translateLoadingEnd",{language:a})};h.displayName="onLoaderSuccess";var i=function(a){c.$emit("$translateLoadingError",{language:a}),e.reject(a),c.$emit("$translateLoadingEnd",{language:a})};return i.displayName="onLoaderError",b.get(o)(g).then(h,i),e.promise};if(k&&(f=b.get(k),!f.get||!f.put))throw new Error("Couldn't use storage '"+k+"', missing get() or put() method!");if(w.length){var V=function(a){var c=b.get(a);c.setLocale(e||i),H[c.getInterpolationIdentifier()]=c};V.displayName="interpolationFactoryAdder",angular.forEach(w,V)}var W=function(a){var b=d.defer();if(Object.prototype.hasOwnProperty.call(t,a))b.resolve(t[a]);else if(I[a]){var c=function(a){M(a.key,a.table),b.resolve(a.table)};c.displayName="translationTableResolver",I[a].then(c,b.reject)}else b.reject();return b.promise},X=function(a,b,c,e){var f=d.defer(),g=function(d){if(Object.prototype.hasOwnProperty.call(d,b)){e.setLocale(a);var g=d[b];"@:"===g.substr(0,2)?X(a,g.substr(2),c,e).then(f.resolve,f.reject):f.resolve(e.interpolate(d[b],c)),e.setLocale(i)}else f.reject()};return g.displayName="fallbackTranslationResolver",W(a).then(g,f.reject),f.promise},Y=function(a,b,c,d){var e,f=t[a];if(f&&Object.prototype.hasOwnProperty.call(f,b)){if(d.setLocale(a),e=d.interpolate(f[b],c),"@:"===e.substr(0,2))return Y(a,e.substr(2),c,d);d.setLocale(i)}return e},Z=function(a,c){if(m){var d=b.get(m)(a,i,c);return void 0!==d?d:a}return a},$=function(a,b,c,e,f){var h=d.defer();if(a0?u:l,a,b,c,d)},ba=function(a,b,c){return _(u>0?u:l,a,b,c)},ca=function(a,b,c,e){var f=d.defer(),h=i?t[i]:t,j=c?H[c]:E;if(h&&Object.prototype.hasOwnProperty.call(h,a)){var k=h[a];"@:"===k.substr(0,2)?R(k.substr(2),b,c,e).then(f.resolve,f.reject):f.resolve(j.interpolate(k,b))}else{var l;m&&!F&&(l=Z(a,b)),i&&g&&g.length?aa(a,b,j,e).then(function(a){f.resolve(a)},function(a){f.reject(S(a))}):m&&!F&&l?e?f.resolve(e):f.resolve(l):e?f.resolve(e):f.reject(S(a))}return f.promise},da=function(a,b,c){var d,e=i?t[i]:t,f=E;if(H&&Object.prototype.hasOwnProperty.call(H,c)&&(f=H[c]),e&&Object.prototype.hasOwnProperty.call(e,a)){var h=e[a];d="@:"===h.substr(0,2)?da(h.substr(2),b,c):f.interpolate(h,b)}else{var j;m&&!F&&(j=Z(a,b)),i&&g&&g.length?(l=0,d=ba(a,b,f)):d=m&&!F&&j?j:S(a)}return d},ea=function(a){j===a&&(j=void 0),I[a]=void 0};R.preferredLanguage=function(a){return a&&O(a),e},R.cloakClassName=function(){return x},R.nestedObjectDelimeter=function(){return A},R.fallbackLanguage=function(a){if(void 0!==a&&null!==a){if(P(a),o&&g&&g.length)for(var b=0,c=g.length;c>b;b++)I[g[b]]||(I[g[b]]=U(g[b]));R.use(R.use())}return h?g[0]:g},R.useFallbackLanguage=function(a){if(void 0!==a&&null!==a)if(a){var b=J(g,a);b>-1&&(u=b)}else u=0},R.proposedLanguage=function(){return j},R.storage=function(){return f},R.use=function(a){if(!a)return i;var b=d.defer();c.$emit("$translateChangeStart",{language:a});var e=L(a);return e&&(a=e),!z&&t[a]||!o||I[a]?j===a&&I[a]?I[a].then(function(a){return b.resolve(a.key),a},function(a){return b.reject(a),d.reject(a)}):(b.resolve(a),T(a)):(j=a,I[a]=U(a).then(function(c){return M(c.key,c.table),b.resolve(c.key),j===a&&T(c.key),c},function(a){return c.$emit("$translateChangeError",{language:a}),b.reject(a),c.$emit("$translateChangeEnd",{language:a}),d.reject(a)}),I[a]["finally"](function(){ea(a)})),b.promise},R.storageKey=function(){return Q()},R.isPostCompilingEnabled=function(){return y},R.isForceAsyncReloadEnabled=function(){return z},R.refresh=function(a){function b(){f.resolve(),c.$emit("$translateRefreshEnd",{language:a})}function e(){f.reject(),c.$emit("$translateRefreshEnd",{language:a})}if(!o)throw new Error("Couldn't refresh translation table, no loader registered!");var f=d.defer();if(c.$emit("$translateRefreshStart",{language:a}),a)if(t[a]){var h=function(c){M(c.key,c.table),a===i&&T(i),b()};h.displayName="refreshPostProcessor",U(a).then(h,e)}else e();else{var j=[],k={};if(g&&g.length)for(var l=0,m=g.length;m>l;l++)j.push(U(g[l])),k[g[l]]=!0;i&&!k[i]&&j.push(U(i));var n=function(a){t={},angular.forEach(a,function(a){M(a.key,a.table)}),i&&T(i),b()};n.displayName="refreshPostProcessor",d.all(j).then(n,e)}return f.promise},R.instant=function(a,b,c){if(null===a||angular.isUndefined(a))return a;if(angular.isArray(a)){for(var d={},f=0,h=a.length;h>f;f++)d[a[f]]=R.instant(a[f],b,c);return d}if(angular.isString(a)&&a.length<1)return a;a&&(a=K.apply(a));var j,k=[];e&&k.push(e),i&&k.push(i),g&&g.length&&(k=k.concat(g));for(var l=0,n=k.length;n>l;l++){var o=k[l];if(t[o]&&("undefined"!=typeof t[o][a]?j=da(a,b,c):(q||r)&&(j=S(a))),"undefined"!=typeof j)break}return j||""===j||(j=E.interpolate(a,b),m&&!F&&(j=Z(a,b))),j},R.versionInfo=function(){return G},R.loaderCache=function(){return s},R.directivePriority=function(){return C},R.statefulFilter=function(){return D},R.isReady=function(){return B};var fa=d.defer();fa.promise.then(function(){B=!0}),R.onReady=function(a){var b=d.defer();return angular.isFunction(a)&&b.promise.then(a),B?b.resolve():fa.promise.then(b.resolve),b.promise};var ga=c.$on("$translateReady",function(){fa.resolve(),ga(),ga=null}),ha=c.$on("$translateChangeEnd",function(){fa.resolve(),ha(),ha=null});if(o){if(angular.equals(t,{})&&R.use()&&R.use(R.use()),g&&g.length)for(var ia=function(a){return M(a.key,a.table),c.$emit("$translateChangeEnd",{language:a.key}),a},ja=0,ka=g.length;ka>ja;ja++){var la=g[ja];(z||!t[la])&&(I[la]=U(la).then(ia))}}else c.$emit("$translateReady",{language:R.use()});return R}]}function d(a,b){"use strict";var c,d={},e="default";return d.setLocale=function(a){c=a},d.getInterpolationIdentifier=function(){return e},d.useSanitizeValueStrategy=function(a){return b.useStrategy(a),this},d.interpolate=function(c,d){d=d||{},d=b.sanitize(d,"params");var e=a(c)(d);return e=b.sanitize(e,"text")},d}function e(a,b,c,d,e,g){"use strict";var h=function(){return this.toString().replace(/^\s+|\s+$/g,"")};return{restrict:"AE",scope:!0,priority:a.directivePriority(),compile:function(b,i){var j=i.translateValues?i.translateValues:void 0,k=i.translateInterpolation?i.translateInterpolation:void 0,l=b[0].outerHTML.match(/translate-value-+/i),m="^(.*)("+c.startSymbol()+".*"+c.endSymbol()+")(.*)",n="^(.*)"+c.startSymbol()+"(.*)"+c.endSymbol()+"(.*)";return function(b,o,p){b.interpolateParams={},b.preText="",b.postText="",b.translateNamespace=f(b);var q={},r=function(a,c,d){if(c.translateValues&&angular.extend(a,e(c.translateValues)(b.$parent)),l)for(var f in d)if(Object.prototype.hasOwnProperty.call(c,f)&&"translateValue"===f.substr(0,14)&&"translateValues"!==f){var g=angular.lowercase(f.substr(14,1))+f.substr(15);a[g]=d[f]}},s=function(a){if(angular.isFunction(s._unwatchOld)&&(s._unwatchOld(),s._unwatchOld=void 0),angular.equals(a,"")||!angular.isDefined(a)){var d=h.apply(o.text()),e=d.match(m);if(angular.isArray(e)){b.preText=e[1],b.postText=e[3],q.translate=c(e[2])(b.$parent);var f=d.match(n);angular.isArray(f)&&f[2]&&f[2].length&&(s._unwatchOld=b.$watch(f[2],function(a){q.translate=a,y()}))}else q.translate=d}else q.translate=a;y()},t=function(a){p.$observe(a,function(b){q[a]=b,y()})};r(b.interpolateParams,p,i);var u=!0;p.$observe("translate",function(a){"undefined"==typeof a?s(""):""===a&&u||(q.translate=a,y()),u=!1});for(var v in p)p.hasOwnProperty(v)&&"translateAttr"===v.substr(0,13)&&t(v);if(p.$observe("translateDefault",function(a){b.defaultText=a}),j&&p.$observe("translateValues",function(a){a&&b.$parent.$watch(function(){angular.extend(b.interpolateParams,e(a)(b.$parent))})}),l){var w=function(a){p.$observe(a,function(c){var d=angular.lowercase(a.substr(14,1))+a.substr(15);b.interpolateParams[d]=c})};for(var x in p)Object.prototype.hasOwnProperty.call(p,x)&&"translateValue"===x.substr(0,14)&&"translateValues"!==x&&w(x)}var y=function(){for(var a in q)q.hasOwnProperty(a)&&void 0!==q[a]&&z(a,q[a],b,b.interpolateParams,b.defaultText,b.translateNamespace)},z=function(b,c,d,e,f,g){c?(g&&"."===c.charAt(0)&&(c=g+c),a(c,e,k,f).then(function(a){A(a,d,!0,b)},function(a){A(a,d,!1,b)})):A(c,d,!1,b)},A=function(b,c,e,f){if("translate"===f){e||"undefined"==typeof c.defaultText||(b=c.defaultText),o.empty().append(c.preText+b+c.postText);var g=a.isPostCompilingEnabled(),h="undefined"!=typeof i.translateCompile,j=h&&"false"!==i.translateCompile;(g&&!h||j)&&d(o.contents())(c)}else{e||"undefined"==typeof c.defaultText||(b=c.defaultText);var k=p.$attr[f];"data-"===k.substr(0,5)&&(k=k.substr(5)),k=k.substr(15),o.attr(k,b)}};(j||l||p.translateDefault)&&b.$watch("interpolateParams",y,!0);var B=g.$on("$translateChangeSuccess",y);o.text().length?s(p.translate?p.translate:""):p.translate&&s(p.translate),y(),b.$on("$destroy",B)}}}}function f(a){"use strict";return a.translateNamespace?a.translateNamespace:a.$parent?f(a.$parent):void 0}function g(a){"use strict";return{compile:function(b){var c=function(){b.addClass(a.cloakClassName())},d=function(){b.removeClass(a.cloakClassName())};return a.onReady(function(){d()}),c(),function(b,e,f){f.translateCloak&&f.translateCloak.length&&f.$observe("translateCloak",function(b){a(b).then(d,c)})}}}}function h(){"use strict";return{restrict:"A",scope:!0,compile:function(){return{pre:function(a,b,c){a.translateNamespace=f(a),a.translateNamespace&&"."===c.translateNamespace.charAt(0)?a.translateNamespace+=c.translateNamespace:a.translateNamespace=c.translateNamespace}}}}}function f(a){"use strict";return a.translateNamespace?a.translateNamespace:a.$parent?f(a.$parent):void 0}function i(a,b){"use strict";var c=function(c,d,e){return angular.isObject(d)||(d=a(d)(this)),b.instant(c,d,e)};return b.statefulFilter()&&(c.$stateful=!0),c}function j(a){"use strict";return a("translations")}return angular.module("pascalprecht.translate",["ng"]).run(a),a.$inject=["$translate"],a.displayName="runTranslate",angular.module("pascalprecht.translate").provider("$translateSanitization",b),angular.module("pascalprecht.translate").constant("pascalprechtTranslateOverrider",{}).provider("$translate",c),c.$inject=["$STORAGE_KEY","$windowProvider","$translateSanitizationProvider","pascalprechtTranslateOverrider"],c.displayName="displayName",angular.module("pascalprecht.translate").factory("$translateDefaultInterpolation",d),d.$inject=["$interpolate","$translateSanitization"],d.displayName="$translateDefaultInterpolation",angular.module("pascalprecht.translate").constant("$STORAGE_KEY","NG_TRANSLATE_LANG_KEY"),angular.module("pascalprecht.translate").directive("translate",e),e.$inject=["$translate","$q","$interpolate","$compile","$parse","$rootScope"],e.displayName="translateDirective",angular.module("pascalprecht.translate").directive("translateCloak",g),g.$inject=["$translate"],g.displayName="translateCloakDirective",angular.module("pascalprecht.translate").directive("translateNamespace",h),h.displayName="translateNamespaceDirective",angular.module("pascalprecht.translate").filter("translate",i),i.$inject=["$parse","$translate"],i.displayName="translateFilterFactory",angular.module("pascalprecht.translate").factory("$translationCache",j),j.$inject=["$cacheFactory"],j.displayName="$translationCache","pascalprecht.translate"}); \ No newline at end of file diff --git a/vendor/assets/components/angular-translate/bower.json b/vendor/assets/components/angular-translate/bower.json new file mode 100644 index 000000000..699c291c7 --- /dev/null +++ b/vendor/assets/components/angular-translate/bower.json @@ -0,0 +1,12 @@ +{ + "name": "angular-translate", + "description": "A translation module for AngularJS", + "version": "2.8.1", + "main": "./angular-translate.js", + "ignore": [], + "author": "Pascal Precht", + "license": "MIT", + "dependencies": { + "angular": ">=1.2.26 <=1.5" + } +} diff --git a/vendor/assets/components/angular-ui-calendar/.bower.json b/vendor/assets/components/angular-ui-calendar/.bower.json new file mode 100644 index 000000000..755c1f194 --- /dev/null +++ b/vendor/assets/components/angular-ui-calendar/.bower.json @@ -0,0 +1,36 @@ +{ + "name": "angular-ui-calendar", + "version": "0.9.0-beta.1", + "description": "A complete AngularJS directive for the Arshaw FullCalendar.", + "author": "https://github.com/angular-ui/ui-calendar/graphs/contributors", + "license": "MIT", + "homepage": "http://angular-ui.github.com", + "main": "./src/calendar.js", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test*", + "demo*", + "gruntFile.js", + "package.json" + ], + "dependencies": { + "angular": "~1.2.x", + "fullcalendar": "~2.x" + }, + "devDependencies": { + "angular-mocks": "~1.x", + "bootstrap-css": "2.3.1", + "jquery-ui": "~1.10.3" + }, + "_release": "0.9.0-beta.1", + "_resolution": { + "type": "version", + "tag": "0.9.0-beta.1", + "commit": "80a32ee00a6a327ea1446451d725fdd30a4535e9" + }, + "_source": "git://github.com/angular-ui/ui-calendar.git", + "_target": "0.9.0-beta.1", + "_originalSource": "angular-ui-calendar" +} \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-select2/LICENSE b/vendor/assets/components/angular-ui-calendar/LICENSE similarity index 97% rename from vendor/assets/components/angular-ui-select2/LICENSE rename to vendor/assets/components/angular-ui-calendar/LICENSE index dfc5e0ca1..033432198 100644 --- a/vendor/assets/components/angular-ui-select2/LICENSE +++ b/vendor/assets/components/angular-ui-calendar/LICENSE @@ -1,21 +1,21 @@ -The MIT License - -Copyright (c) 2012 the AngularUI Team, http://angular-ui.github.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +The MIT License + +Copyright (c) 2012 the AngularUI Team, http://angular-ui.github.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-calendar/README.md b/vendor/assets/components/angular-ui-calendar/README.md new file mode 100644 index 000000000..916c18a5d --- /dev/null +++ b/vendor/assets/components/angular-ui-calendar/README.md @@ -0,0 +1,129 @@ +# ui-calendar directive [![Build Status](https://travis-ci.org/angular-ui/ui-calendar.png?branch=master)](https://travis-ci.org/angular-ui/ui-calendar) + +A complete AngularJS directive for the Arshaw FullCalendar. + +# Requirements +- ([AngularJS](http://code.angularjs.org/1.2.1/angular.js)) +- ([fullcalendar.js 2.0 and it's dependencies](http://arshaw.com/fullcalendar/download/)) +- optional - ([gcal-plugin](http://arshaw.com/js/fullcalendar-1.5.3/fullcalendar/gcal.js)) + +# Testing + +We use karma and grunt to ensure the quality of the code. + + npm install -g grunt-cli + npm install + bower install + grunt + +# Usage + +We use [bower](http://twitter.github.com/bower/) for dependency management. Add + + dependencies: { + "angular-ui-calendar": "latest" + } + +To your `components.json` file. Then run + + bower install + +This will copy the ui-calendar files into your `components` folder, along with its dependencies. Load the script files in your application: + + + + + + + + +Add the calendar module as a dependency to your application module: + + var myAppModule = angular.module('MyApp', ['ui.calendar']) + +Apply the directive to your div elements. The calendar must be supplied an array of decoumented event sources to render itself: + +
    + +## Options + +All the Arshaw Fullcalendar options can be passed through the directive. This even means function objects that are declared on the scope. + + myAppModule.controller('MyController', function($scope) { + /* config object */ + $scope.uiConfig = { + calendar:{ + height: 450, + editable: true, + header:{ + left: 'month basicWeek basicDay agendaWeek agendaDay', + center: 'title', + right: 'today prev,next' + }, + dayClick: $scope.alertEventOnClick, + eventDrop: $scope.alertOnDrop, + eventResize: $scope.alertOnResize + } + }; + }); + +
    + +## Working with ng-model + +The ui-calendar directive plays nicely with ng-model. + +An Event Sources objects needs to be created to pass into ng-model. This object will be watched for changes and update the calendar accordingly, giving the calendar some Angular Magic. + +The ui-calendar directive expects the eventSources object to be any type allowed in the documentation for the fullcalendar. [docs](http://arshaw.com/fullcalendar/docs/event_data/Event_Source_Object/) +Note that all calendar options which are functions that are passed into the calendar are wrapped in an apply automatically. + +## Accessing the calendar object + +To avoid potential issues, by default the calendar object is not available in the parent scope. Access the object by declaring a calendar attribute name: + +
    + +Now the calendar object is available in the parent scope: + + $scope.myCalendar.fullCalendar + +This allows you to declare any number of calendar objects with distinct names. + +## Custom event rendering + +You can use fullcalendar's `eventRender` option to customize how events are rendered in the calendar. +However, only certain event attributes are watched for changes (they are `id`, `title`, `url`, `start`, `end`, `allDay`, and `className`). + +If you need to automatically re-render other event data, you can use `calendar-watch-event`. +`calendar-watch-event` expression must return a function that is passed `event` as argument and returns a string or a number, for example: + + $scope.extraEventSignature = function(event) { + returns "" + event.price; + } + + + // will now watch for price + +## Watching the displayed date range of the calendar + +There is no mechanism to $watch the displayed date range on the calendar due to the JQuery nature of fullCalendar. If you want +to track the dates displayed on the calendar so you can fetch events outside the scope of fullCalendar (Say from a caching store +in a service, instead of letting fullCalendar pull them via AJAX), you can add the viewRender callback to the calendar config. + + $scope.calendarConfig = { + calendar:{ + height: "100%", + ... + viewRender: function(view, element) { + $log.debug("View Changed: ", view.visStart, view.visEnd, view.start, view.end); + } + } + }; + +## Documentation for the Calendar + +The calendar works alongside of all the documentation represented [here](http://arshaw.com/fullcalendar/docs) + +## PR's R always Welcome +Make sure that if a new feature is added, that the proper tests are created. diff --git a/vendor/assets/components/angular-ui-calendar/bower.json b/vendor/assets/components/angular-ui-calendar/bower.json new file mode 100644 index 000000000..21193eea2 --- /dev/null +++ b/vendor/assets/components/angular-ui-calendar/bower.json @@ -0,0 +1,27 @@ +{ + "name": "angular-ui-calendar", + "version": "0.9.0-beta.1", + "description": "A complete AngularJS directive for the Arshaw FullCalendar.", + "author": "https://github.com/angular-ui/ui-calendar/graphs/contributors", + "license": "MIT", + "homepage": "http://angular-ui.github.com", + "main": "./src/calendar.js", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test*", + "demo*", + "gruntFile.js", + "package.json" + ], + "dependencies": { + "angular": "~1.2.x", + "fullcalendar": "~2.x" + }, + "devDependencies": { + "angular-mocks": "~1.x", + "bootstrap-css": "2.3.1", + "jquery-ui": "~1.10.3" + } +} diff --git a/vendor/assets/components/angular-ui-calendar/src/calendar.js b/vendor/assets/components/angular-ui-calendar/src/calendar.js new file mode 100644 index 000000000..80a02eaae --- /dev/null +++ b/vendor/assets/components/angular-ui-calendar/src/calendar.js @@ -0,0 +1,274 @@ +/* +* AngularJs Fullcalendar Wrapper for the JQuery FullCalendar +* API @ http://arshaw.com/fullcalendar/ +* +* Angular Calendar Directive that takes in the [eventSources] nested array object as the ng-model and watches it deeply changes. +* Can also take in multiple event urls as a source object(s) and feed the events per view. +* The calendar will watch any eventSource array and update itself when a change is made. +* +*/ + +angular.module('ui.calendar', []) + .constant('uiCalendarConfig', {}) + .controller('uiCalendarCtrl', ['$scope', '$timeout', function($scope, $timeout){ + + var sourceSerialId = 1, + eventSerialId = 1, + sources = $scope.eventSources, + extraEventSignature = $scope.calendarWatchEvent ? $scope.calendarWatchEvent : angular.noop, + + wrapFunctionWithScopeApply = function(functionToWrap){ + var wrapper; + + if (functionToWrap){ + wrapper = function(){ + // This happens outside of angular context so we need to wrap it in a timeout which has an implied apply. + // In this way the function will be safely executed on the next digest. + + var args = arguments; + var _this = this; + $timeout(function(){ + functionToWrap.apply(_this, args); + }); + }; + } + + return wrapper; + }; + + this.eventsFingerprint = function(e) { + if (!e._id) { + e._id = eventSerialId++; + } + // This extracts all the information we need from the event. http://jsperf.com/angular-calendar-events-fingerprint/3 + return "" + e._id + (e.id || '') + (e.title || '') + (e.url || '') + (+e.start || '') + (+e.end || '') + + (e.allDay || '') + (e.className || '') + extraEventSignature(e) || ''; + }; + + this.sourcesFingerprint = function(source) { + return source.__id || (source.__id = sourceSerialId++); + }; + + this.allEvents = function() { + // return sources.flatten(); but we don't have flatten + var arraySources = []; + for (var i = 0, srcLen = sources.length; i < srcLen; i++) { + var source = sources[i]; + if (angular.isArray(source)) { + // event source as array + arraySources.push(source); + } else if(angular.isObject(source) && angular.isArray(source.events)){ + // event source as object, ie extended form + var extEvent = {}; + for(var key in source){ + if(key !== '_uiCalId' && key !== 'events'){ + extEvent[key] = source[key]; + } + } + for(var eI = 0;eI < source.events.length;eI++){ + angular.extend(source.events[eI],extEvent); + } + arraySources.push(source.events); + } + } + + return Array.prototype.concat.apply([], arraySources); + }; + + // Track changes in array by assigning id tokens to each element and watching the scope for changes in those tokens + // arguments: + // arraySource array of function that returns array of objects to watch + // tokenFn function(object) that returns the token for a given object + this.changeWatcher = function(arraySource, tokenFn) { + var self; + var getTokens = function() { + var array = angular.isFunction(arraySource) ? arraySource() : arraySource; + var result = [], token, el; + for (var i = 0, n = array.length; i < n; i++) { + el = array[i]; + token = tokenFn(el); + map[token] = el; + result.push(token); + } + return result; + }; + // returns elements in that are in a but not in b + // subtractAsSets([4, 5, 6], [4, 5, 7]) => [6] + var subtractAsSets = function(a, b) { + var result = [], inB = {}, i, n; + for (i = 0, n = b.length; i < n; i++) { + inB[b[i]] = true; + } + for (i = 0, n = a.length; i < n; i++) { + if (!inB[a[i]]) { + result.push(a[i]); + } + } + return result; + }; + + // Map objects to tokens and vice-versa + var map = {}; + + var applyChanges = function(newTokens, oldTokens) { + var i, n, el, token; + var replacedTokens = {}; + var removedTokens = subtractAsSets(oldTokens, newTokens); + for (i = 0, n = removedTokens.length; i < n; i++) { + var removedToken = removedTokens[i]; + el = map[removedToken]; + delete map[removedToken]; + var newToken = tokenFn(el); + // if the element wasn't removed but simply got a new token, its old token will be different from the current one + if (newToken === removedToken) { + self.onRemoved(el); + } else { + replacedTokens[newToken] = removedToken; + self.onChanged(el); + } + } + + var addedTokens = subtractAsSets(newTokens, oldTokens); + for (i = 0, n = addedTokens.length; i < n; i++) { + token = addedTokens[i]; + el = map[token]; + if (!replacedTokens[token]) { + self.onAdded(el); + } + } + }; + return self = { + subscribe: function(scope, onChanged) { + scope.$watch(getTokens, function(newTokens, oldTokens) { + if (!onChanged || onChanged(newTokens, oldTokens) !== false) { + applyChanges(newTokens, oldTokens); + } + }, true); + }, + onAdded: angular.noop, + onChanged: angular.noop, + onRemoved: angular.noop + }; + }; + + this.getFullCalendarConfig = function(calendarSettings, uiCalendarConfig){ + var config = {}; + + angular.extend(config, uiCalendarConfig); + angular.extend(config, calendarSettings); + + angular.forEach(config, function(value,key){ + if (typeof value === 'function'){ + config[key] = wrapFunctionWithScopeApply(config[key]); + } + }); + + return config; + }; + }]) + .directive('uiCalendar', ['uiCalendarConfig', '$locale', function(uiCalendarConfig, $locale) { + // Configure to use locale names by default + var tValues = function(data) { + // convert {0: "Jan", 1: "Feb", ...} to ["Jan", "Feb", ...] + var r, k; + r = []; + for (k in data) { + r[k] = data[k]; + } + return r; + }; + var dtf = $locale.DATETIME_FORMATS; + uiCalendarConfig = angular.extend({ + monthNames: tValues(dtf.MONTH), + monthNamesShort: tValues(dtf.SHORTMONTH), + dayNames: tValues(dtf.DAY), + dayNamesShort: tValues(dtf.SHORTDAY) + }, uiCalendarConfig || {}); + + return { + restrict: 'A', + scope: {eventSources:'=ngModel',calendarWatchEvent: '&'}, + controller: 'uiCalendarCtrl', + link: function(scope, elm, attrs, controller) { + + var sources = scope.eventSources, + sourcesChanged = false, + eventSourcesWatcher = controller.changeWatcher(sources, controller.sourcesFingerprint), + eventsWatcher = controller.changeWatcher(controller.allEvents, controller.eventsFingerprint), + options = null; + + function getOptions(){ + var calendarSettings = attrs.uiCalendar ? scope.$parent.$eval(attrs.uiCalendar) : {}, + fullCalendarConfig; + + fullCalendarConfig = controller.getFullCalendarConfig(calendarSettings, uiCalendarConfig); + + options = { eventSources: sources }; + angular.extend(options, fullCalendarConfig); + + var options2 = {}; + for(var o in options){ + if(o !== 'eventSources'){ + options2[o] = options[o]; + } + } + return JSON.stringify(options2); + } + + scope.destroy = function(){ + if(scope.calendar && scope.calendar.fullCalendar){ + scope.calendar.fullCalendar('destroy'); + } + if(attrs.calendar) { + scope.calendar = scope.$parent[attrs.calendar] = $(elm).html(''); + } else { + scope.calendar = $(elm).html(''); + } + }; + + scope.init = function(){ + scope.calendar.fullCalendar(options); + }; + + eventSourcesWatcher.onAdded = function(source) { + scope.calendar.fullCalendar('addEventSource', source); + sourcesChanged = true; + }; + + eventSourcesWatcher.onRemoved = function(source) { + scope.calendar.fullCalendar('removeEventSource', source); + sourcesChanged = true; + }; + + eventsWatcher.onAdded = function(event) { + scope.calendar.fullCalendar('renderEvent', event); + }; + + eventsWatcher.onRemoved = function(event) { + scope.calendar.fullCalendar('removeEvents', function(e) { + return e._id === event._id; + }); + }; + + eventsWatcher.onChanged = function(event) { + event._start = $.fullCalendar.moment(event.start); + event._end = $.fullCalendar.moment(event.end); + scope.calendar.fullCalendar('updateEvent', event); + }; + + eventSourcesWatcher.subscribe(scope); + eventsWatcher.subscribe(scope, function(newTokens, oldTokens) { + if (sourcesChanged === true) { + sourcesChanged = false; + // prevent incremental updates in this case + return false; + } + }); + + scope.$watch(getOptions, function(newO,oldO){ + scope.destroy(); + scope.init(); + }); + } + }; +}]); \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-router/.bower.json b/vendor/assets/components/angular-ui-router/.bower.json index a9fcc1b22..791da93f8 100644 --- a/vendor/assets/components/angular-ui-router/.bower.json +++ b/vendor/assets/components/angular-ui-router/.bower.json @@ -1,6 +1,6 @@ { "name": "angular-ui-router", - "version": "0.2.13", + "version": "0.2.15", "main": "./release/angular-ui-router.js", "dependencies": { "angular": ">= 1.0.8" @@ -21,13 +21,13 @@ "files.js" ], "homepage": "https://github.com/angular-ui/ui-router", - "_release": "0.2.13", + "_release": "0.2.15", "_resolution": { "type": "version", - "tag": "0.2.13", - "commit": "c3d543aae43d4600512520a0d70723ac31f2cb62" + "tag": "0.2.15", + "commit": "805e69bae319e922e4d3265b7ef565058aaff850" }, "_source": "git://github.com/angular-ui/ui-router.git", - "_target": ">=0.2.10", + "_target": ">=0.2.15", "_originalSource": "angular-ui-router" } \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-router/CHANGELOG.md b/vendor/assets/components/angular-ui-router/CHANGELOG.md index e0848c38c..23b59d3a7 100644 --- a/vendor/assets/components/angular-ui-router/CHANGELOG.md +++ b/vendor/assets/components/angular-ui-router/CHANGELOG.md @@ -1,3 +1,34 @@ + +### 0.2.14 (2015-04-23) + + +#### Bug Fixes + +* **$StateRefDirective:** resolve missing support for svg anchor elements #1667 ([0149a7bb](https://github.com/angular-ui/ui-router/commit/0149a7bb38b7af99388a1ad7cc9909a7b7c4439d)) +* **$urlMatcherFactory:** + * regex params should respect case-sensitivity ([1e10519f](https://github.com/angular-ui/ui-router/commit/1e10519f3be6bbf0cefdcce623cd2ade06e649e5), closes [#1671](https://github.com/angular-ui/ui-router/issues/1671)) + * unquote all dashes from array params ([06664d33](https://github.com/angular-ui/ui-router/commit/06664d330f882390655dcfa83e10276110d0d0fa)) + * add Type.$normalize function ([b0c6aa23](https://github.com/angular-ui/ui-router/commit/b0c6aa2350fdd3ce8483144774adc12f5a72b7e9)) + * make optional params regex grouping optional ([06f73794](https://github.com/angular-ui/ui-router/commit/06f737945e83e668d09cfc3bcffd04a500ff1963), closes [#1576](https://github.com/angular-ui/ui-router/issues/1576)) +* **$state:** allow about.*.** glob patterns ([e39b27a2](https://github.com/angular-ui/ui-router/commit/e39b27a2cb7d88525c446a041f9fbf1553202010)) +* **uiSref:** + * use Object's toString instead of Window's toString ([2aa7f4d1](https://github.com/angular-ui/ui-router/commit/2aa7f4d139dbd5b9fcc4afdcf2ab6642c87f5671)) + * add absolute to allowed transition options ([ae1b3c4e](https://github.com/angular-ui/ui-router/commit/ae1b3c4eedc37983400d830895afb50457c63af4)) +* **uiSrefActive:** Apply active classes on lazy loaded states ([f0ddbe7b](https://github.com/angular-ui/ui-router/commit/f0ddbe7b4a91daf279c3b7d0cee732bb1f3be5b4)) +* **uiView:** add `$element` to locals for view controller ([db68914c](https://github.com/angular-ui/ui-router/commit/db68914cd6c821e7dec8155bd33142a3a97f5453)) + + +#### Features + +* **$state:** + * support URLs with #fragments ([3da0a170](https://github.com/angular-ui/ui-router/commit/3da0a17069e27598c0f9d9164e104dd5ce05cdc6)) + * inject resolve params into controllerProvider ([b380c223](https://github.com/angular-ui/ui-router/commit/b380c223fe12e2fde7582c0d6b1ed7b15a23579b), closes [#1131](https://github.com/angular-ui/ui-router/issues/1131)) + * added 'state' to state reload method (feat no.1612) - modiefied options.reload ([b8f04575](https://github.com/angular-ui/ui-router/commit/b8f04575a8557035c1858c4d5c8dbde3e1855aaa)) + * broadcast $stateChangeCancel event when event.preventDefault() is called in $sta ([ecefb758](https://github.com/angular-ui/ui-router/commit/ecefb758cb445e41620b62a272aafa3638613d7a)) +* **$uiViewScroll:** change function to return promise ([c2a9a311](https://github.com/angular-ui/ui-router/commit/c2a9a311388bb212e5a2e820536d1d739f829ccd), closes [#1702](https://github.com/angular-ui/ui-router/issues/1702)) +* **uiSrefActive:** Added support for multiple nested uiSref directives ([b1844948](https://github.com/angular-ui/ui-router/commit/b18449481d152b50705abfce2493a444eb059fa5)) + + ### 0.2.13 (2014-11-20) diff --git a/vendor/assets/components/angular-ui-router/LICENSE b/vendor/assets/components/angular-ui-router/LICENSE index 939f8f8a7..6413b092d 100644 --- a/vendor/assets/components/angular-ui-router/LICENSE +++ b/vendor/assets/components/angular-ui-router/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2014 The AngularUI Team, Karsten Sperling +Copyright (c) 2013-2015 The AngularUI Team, Karsten Sperling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/assets/components/angular-ui-router/README.md b/vendor/assets/components/angular-ui-router/README.md index f02d83bcb..1d8bcd618 100644 --- a/vendor/assets/components/angular-ui-router/README.md +++ b/vendor/assets/components/angular-ui-router/README.md @@ -2,7 +2,7 @@ #### The de-facto solution to flexible routing with nested views --- -**[Download 0.2.11](http://angular-ui.github.io/ui-router/release/angular-ui-router.js)** (or **[Minified](http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js)**) **|** +**[Download 0.2.15](http://angular-ui.github.io/ui-router/release/angular-ui-router.js)** (or **[Minified](http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js)**) **|** **[Guide](https://github.com/angular-ui/ui-router/wiki) |** **[API](http://angular-ui.github.io/ui-router/site) |** **[Sample](http://angular-ui.github.com/ui-router/sample/) ([Src](https://github.com/angular-ui/ui-router/tree/gh-pages/sample)) |** @@ -34,8 +34,10 @@ Check out the sample app: http://angular-ui.github.io/ui-router/sample/ **(1)** Get UI-Router in one of the following ways: - clone & [build](CONTRIBUTING.md#developing) this repository - [download the release](http://angular-ui.github.io/ui-router/release/angular-ui-router.js) (or [minified](http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js)) - - via **[Bower](http://bower.io/)**: by running `$ bower install angular-ui-router` from your console + - [link to cdn](http://cdnjs.com/libraries/angular-ui-router) + - via **[jspm](http://jspm.io/)**: by running `$ jspm install angular-ui-router` from your console - or via **[npm](https://www.npmjs.org/)**: by running `$ npm install angular-ui-router` from your console + - or via **[Bower](http://bower.io/)**: by running `$ bower install angular-ui-router` from your console - or via **[Component](https://github.com/component/component)**: by running `$ component install angular-ui/ui-router` from your console **(2)** Include `angular-ui-router.js` (or `angular-ui-router.min.js`) in your `index.html`, after including Angular itself (For Component users: ignore this step) diff --git a/vendor/assets/components/angular-ui-router/bower.json b/vendor/assets/components/angular-ui-router/bower.json index 45e802a88..f4f08fa26 100644 --- a/vendor/assets/components/angular-ui-router/bower.json +++ b/vendor/assets/components/angular-ui-router/bower.json @@ -1,6 +1,6 @@ { "name": "angular-ui-router", - "version": "0.2.13", + "version": "0.2.15", "main": "./release/angular-ui-router.js", "dependencies": { "angular": ">= 1.0.8" diff --git a/vendor/assets/components/angular-ui-router/release/angular-ui-router.js b/vendor/assets/components/angular-ui-router/release/angular-ui-router.js index d2636f8ec..57c62cca8 100644 --- a/vendor/assets/components/angular-ui-router/release/angular-ui-router.js +++ b/vendor/assets/components/angular-ui-router/release/angular-ui-router.js @@ -1,6 +1,6 @@ /** * State-based routing for AngularJS - * @version v0.2.13 + * @version v0.2.15 * @link http://angular-ui.github.com/ * @license MIT License, http://www.opensource.org/licenses/MIT */ @@ -68,7 +68,7 @@ function objectKeys(object) { } var result = []; - angular.forEach(object, function(val, key) { + forEach(object, function(val, key) { result.push(key); }); return result; @@ -680,7 +680,7 @@ var $$UMFP; // reference to $UrlMatcherFactoryProvider * of search parameters. Multiple search parameter names are separated by '&'. Search parameters * do not influence whether or not a URL is matched, but their values are passed through into * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}. - * + * * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace * syntax, which optionally allows a regular expression for the parameter to be specified: * @@ -691,13 +691,13 @@ var $$UMFP; // reference to $UrlMatcherFactoryProvider * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash. * * Parameter names may contain only word characters (latin letters, digits, and underscore) and - * must be unique within the pattern (across both path and search parameters). For colon + * must be unique within the pattern (across both path and search parameters). For colon * placeholders or curly placeholders without an explicit regexp, a path parameter matches any * number of characters other than '/'. For catch-all placeholders the path parameter matches * any number of characters. - * + * * Examples: - * + * * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for * trailing slashes, and patterns have to match the entire path, not just a prefix. * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or @@ -730,7 +730,7 @@ var $$UMFP; // reference to $UrlMatcherFactoryProvider * * @property {string} sourceSearch The search portion of the source property * - * @property {string} regex The constructed regex that will be used to match against the url when + * @property {string} regex The constructed regex that will be used to match against the url when * it is time to determine which url will match. * * @returns {Object} New `UrlMatcher` object @@ -768,13 +768,13 @@ function UrlMatcher(pattern, config, parentMatcher) { return params[id]; } - function quoteRegExp(string, pattern, squash) { + function quoteRegExp(string, pattern, squash, optional) { var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); if (!pattern) return result; switch(squash) { - case false: surroundPattern = ['(', ')']; break; + case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break; case true: surroundPattern = ['?(', ')?']; break; - default: surroundPattern = ['(' + squash + "|", ')?']; break; + default: surroundPattern = ['(' + squash + "|", ')?']; break; } return result + surroundPattern[0] + pattern + surroundPattern[1]; } @@ -789,7 +789,7 @@ function UrlMatcher(pattern, config, parentMatcher) { cfg = config.params[id]; segment = pattern.substring(last, m.index); regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null); - type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp) }); + type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) }); return { id: id, regexp: regexp, segment: segment, type: type, cfg: cfg }; @@ -801,7 +801,7 @@ function UrlMatcher(pattern, config, parentMatcher) { if (p.segment.indexOf('?') >= 0) break; // we're into the search part param = addParameter(p.id, p.type, p.cfg, "path"); - compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash); + compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional); segments.push(p.segment); last = placeholder.lastIndex; } @@ -912,7 +912,7 @@ UrlMatcher.prototype.exec = function (path, searchParams) { function decodePathArray(string) { function reverseString(str) { return str.split("").reverse().join(""); } - function unquoteDashes(str) { return str.replace(/\\-/, "-"); } + function unquoteDashes(str) { return str.replace(/\\-/g, "-"); } var split = reverseString(string).split(/-(?!\\)/); var allReversed = map(split, reverseString); @@ -945,7 +945,7 @@ UrlMatcher.prototype.exec = function (path, searchParams) { * * @description * Returns the names of all path and search parameters of this pattern in an unspecified order. - * + * * @returns {Array.} An array of parameter names. Must be treated as read-only. If the * pattern has no parameters, an empty array is returned. */ @@ -1150,6 +1150,11 @@ Type.prototype.pattern = /.*/; Type.prototype.toString = function() { return "{Type:" + this.name + "}"; }; +/** Given an encoded string, or a decoded object, returns a decoded object */ +Type.prototype.$normalize = function(val) { + return this.is(val) ? val : this.decode(val); +}; + /* * Wraps an existing custom Type as an array of Type, depending on 'mode'. * e.g.: @@ -1163,7 +1168,6 @@ Type.prototype.toString = function() { return "{Type:" + this.name + "}"; }; Type.prototype.$asArray = function(mode, isSearch) { if (!mode) return this; if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only"); - return new ArrayType(this, mode); function ArrayType(type, mode) { function bindTo(type, callbackName) { @@ -1212,8 +1216,12 @@ Type.prototype.$asArray = function(mode, isSearch) { this.is = arrayHandler(bindTo(type, 'is'), true); this.equals = arrayEqualsHandler(bindTo(type, 'equals')); this.pattern = type.pattern; + this.$normalize = arrayHandler(bindTo(type, '$normalize')); + this.name = type.name; this.$arrayMode = mode; } + + return new ArrayType(this, mode); }; @@ -1233,15 +1241,14 @@ function $UrlMatcherFactory() { function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; } function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; } -// TODO: in 1.0, make string .is() return false if value is undefined by default. -// function regexpMatches(val) { /*jshint validthis:true */ return isDefined(val) && this.pattern.test(val); } - function regexpMatches(val) { /*jshint validthis:true */ return this.pattern.test(val); } var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = { string: { encode: valToString, decode: valFromString, - is: regexpMatches, + // TODO: in 1.0, make string .is() return false if value is undefined/null by default. + // In 0.2.x, string params are optional by default for backwards compat + is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; }, pattern: /[^/]*/ }, int: { @@ -1285,7 +1292,6 @@ function $UrlMatcherFactory() { any: { // does not encode/decode encode: angular.identity, decode: angular.identity, - is: angular.identity, equals: angular.equals, pattern: /.*/ } @@ -1615,7 +1621,10 @@ function $UrlMatcherFactory() { */ function $$getDefaultValue() { if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); - return injector.invoke(config.$$fn); + var defaultValue = injector.invoke(config.$$fn); + if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue)) + throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")"); + return defaultValue; } /** @@ -1629,7 +1638,7 @@ function $UrlMatcherFactory() { return replacement.length ? replacement[0] : value; } value = $replace(value); - return isDefined(value) ? self.type.decode(value) : $$getDefaultValue(); + return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value); } function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; } @@ -1685,15 +1694,20 @@ function $UrlMatcherFactory() { return equal; }, $$validates: function $$validate(paramValues) { - var result = true, isOptional, val, param, self = this; - - forEach(this.$$keys(), function(key) { - param = self[key]; - val = paramValues[key]; - isOptional = !val && param.isOptional; - result = result && (isOptional || !!param.type.is(val)); - }); - return result; + var keys = this.$$keys(), i, param, rawVal, normalized, encoded; + for (i = 0; i < keys.length; i++) { + param = this[keys[i]]; + rawVal = paramValues[keys[i]]; + if ((rawVal === undefined || rawVal === null) && param.isOptional) + break; // There was no parameter value, but the param is optional + normalized = param.type.$normalize(rawVal); + if (!param.type.is(normalized)) + return false; // The value was not of the correct Type, and could not be decoded to the correct Type + encoded = param.type.encode(normalized); + if (angular.isString(encoded) && !param.type.pattern.exec(encoded)) + return false; // The value was of the correct type, but when encoded, did not match the Type's regexp + } + return true; }, $$parent: undefined }; @@ -1986,7 +2000,8 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { if (evt && evt.defaultPrevented) return; var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl; lastPushedUrl = undefined; - if (ignoreUpdate) return true; + // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573 + //if (ignoreUpdate) return true; function check(rule) { var handled = rule($injector, $location); @@ -2058,7 +2073,14 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { }, push: function(urlMatcher, params, options) { - $location.url(urlMatcher.format(params || {})); + var url = urlMatcher.format(params || {}); + + // Handle the special hash param, if needed + if (url !== null && params && params['#']) { + url += '#' + params['#']; + } + + $location.url(url); lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined; if (options && options.replace) $location.replace(); }, @@ -2102,6 +2124,12 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { if (!isHtml5 && url !== null) { url = "#" + $locationProvider.hashPrefix() + url; } + + // Handle special hash param, if needed + if (url !== null && params && params['#']) { + url += '#' + params['#']; + } + url = appendBasePath(url, isHtml5, options.absolute); if (!options.absolute || !url) { @@ -2336,6 +2364,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { var globSegments = glob.split('.'), segments = $state.$current.name.split('.'); + //match single stars + for (var i = 0, l = globSegments.length; i < l; i++) { + if (globSegments[i] === '*') { + segments[i] = '*'; + } + } + //match greedy starts if (globSegments[0] === '**') { segments = segments.slice(indexOf(segments, globSegments[1])); @@ -2351,13 +2386,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { return false; } - //match single stars - for (var i = 0, l = globSegments.length; i < l; i++) { - if (globSegments[i] === '*') { - segments[i] = '*'; - } - } - return segments.join('') === globSegments.join(''); } @@ -2566,6 +2594,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * published to scope under the controllerAs name. *
    controllerAs: "myCtrl"
    * + * @param {string|object=} stateConfig.parent + * + * Optionally specifies the parent state of this state. + * + *
    parent: 'parentState'
    + *
    parent: parentState // JS variable
    + * * @param {object=} stateConfig.resolve * * @@ -2597,6 +2632,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * transitioned to, the `$stateParams` service will be populated with any * parameters that were passed. * + * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for + * more details on acceptable patterns ) + * * examples: *
    url: "/home"
        * url: "/users/:userid"
    @@ -2604,8 +2642,9 @@ function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory) {
        * url: "/books/{categoryid:int}"
        * url: "/books/{publishername:string}/{categoryid:int}"
        * url: "/messages?before&after"
    -   * url: "/messages?{before:date}&{after:date}"
    + * url: "/messages?{before:date}&{after:date}" * url: "/messages/:mailboxid?{before:date}&{after:date}" + * * * @param {object=} stateConfig.views * @@ -2909,8 +2948,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * @methodOf ui.router.state.$state * * @description - * A method that force reloads the current state. All resolves are re-resolved, events are not re-fired, - * and controllers reinstantiated (bug with controllers reinstantiating right now, fixing soon). + * A method that force reloads the current state. All resolves are re-resolved, + * controllers reinstantiated, and events re-fired. * * @example *
    @@ -2930,11 +2969,33 @@ function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory) {
          * });
          * 
    * + * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved. + * @example + *
    +     * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item' 
    +     * //and current state is 'contacts.detail.item'
    +     * var app angular.module('app', ['ui.router']);
    +     *
    +     * app.controller('ctrl', function ($scope, $state) {
    +     *   $scope.reload = function(){
    +     *     //will reload 'contact.detail' and 'contact.detail.item' states
    +     *     $state.reload('contact.detail');
    +     *   }
    +     * });
    +     * 
    + * + * `reload()` is just an alias for: + *
    +     * $state.transitionTo($state.current, $stateParams, { 
    +     *   reload: true, inherit: false, notify: true
    +     * });
    +     * 
    + * @returns {promise} A promise representing the state of the new transition. See * {@link ui.router.state.$state#methods_go $state.go}. */ - $state.reload = function reload() { - return $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: true }); + $state.reload = function reload(state) { + return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true}); }; /** @@ -3038,9 +3099,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), * defines which state to be relative from. * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. - * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params + * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd * use this when you want to force a reload when *everything* is the same, including search params. + * if String, then will reload the state with the name given in reload, and any children. + * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children. * * @returns {promise} A promise representing the state of the new transition. See * {@link ui.router.state.$state#methods_go $state.go}. @@ -3054,6 +3117,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { var from = $state.$current, fromParams = $state.params, fromPath = from.path; var evt, toState = findState(to, options.relative); + // Store the hash param for later (since it will be stripped out by various methods) + var hash = toParams['#']; + if (!isDefined(toState)) { var redirect = { to: to, toParams: toParams, options: options }; var redirectResult = handleRedirect(redirect, from.self, fromParams, options); @@ -3092,6 +3158,21 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { keep++; state = toPath[keep]; } + } else if (isString(options.reload) || isObject(options.reload)) { + if (isObject(options.reload) && !options.reload.name) { + throw new Error('Invalid reload state object'); + } + + var reloadState = options.reload === true ? fromPath[0] : findState(options.reload); + if (options.reload && !reloadState) { + throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'"); + } + + while (state && state === fromPath[keep] && state !== reloadState) { + locals = toLocals[keep] = state.locals; + keep++; + state = toPath[keep]; + } } // If we're going to the same state and all locals are kept, we've got nothing to do. @@ -3099,8 +3180,16 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { // TODO: We may not want to bump 'transition' if we're called from a location change // that we've initiated ourselves, because we might accidentally abort a legitimate // transition initiated from code? - if (shouldTriggerReload(to, from, locals, options)) { - if (to.self.reloadOnSearch !== false) $urlRouter.update(); + if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) { + if (hash) toParams['#'] = hash; + $state.params = toParams; + copy($state.params, $stateParams); + if (options.location && to.navigable && to.navigable.url) { + $urlRouter.push(to.navigable.url, toParams, { + $$avoidResync: true, replace: options.location === 'replace' + }); + $urlRouter.update(true); + } $state.transition = null; return $q.when($state.current); } @@ -3138,6 +3227,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * */ if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) { + $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); $urlRouter.update(); return TransitionPrevented; } @@ -3184,6 +3274,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { } } + // Re-add the saved hash before we start returning things + if (hash) toParams['#'] = hash; + // Run it again, to catch any transitions in callbacks if ($state.transition !== transition) return TransitionSuperseded; @@ -3409,7 +3502,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { if (!nav || nav.url === undefined || nav.url === null) { return null; } - return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys(), params || {}), { + return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), { absolute: options.absolute }); }; @@ -3451,30 +3544,38 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { })]; if (inherited) promises.push(inherited); - // Resolve template and dependencies for all views. - forEach(state.views, function (view, name) { - var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); - injectables.$template = [ function () { - return $view.load(name, { view: view, locals: locals, params: $stateParams, notify: options.notify }) || ''; - }]; + function resolveViews() { + var viewsPromises = []; - promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) { - // References to the controller (only instantiated at link time) - if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { - var injectLocals = angular.extend({}, injectables, locals); - result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); - } else { - result.$$controller = view.controller; - } - // Provide access to the state itself for internal use - result.$$state = state; - result.$$controllerAs = view.controllerAs; - dst[name] = result; - })); - }); + // Resolve template and dependencies for all views. + forEach(state.views, function (view, name) { + var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); + injectables.$template = [ function () { + return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || ''; + }]; + + viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) { + // References to the controller (only instantiated at link time) + if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { + var injectLocals = angular.extend({}, injectables, dst.globals); + result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); + } else { + result.$$controller = view.controller; + } + // Provide access to the state itself for internal use + result.$$state = state; + result.$$controllerAs = view.controllerAs; + dst[name] = result; + })); + }); + + return $q.all(viewsPromises).then(function(){ + return dst.globals; + }); + } // Wait for all the promises and then return the activation object - return $q.all(promises).then(function (values) { + return $q.all(promises).then(resolveViews).then(function (values) { return dst; }); } @@ -3482,8 +3583,27 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { return $state; } - function shouldTriggerReload(to, from, locals, options) { - if (to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false))) { + function shouldSkipReload(to, toParams, from, fromParams, locals, options) { + // Return true if there are no differences in non-search (path/object) params, false if there are differences + function nonSearchParamsEqual(fromAndToState, fromParams, toParams) { + // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params. + function notSearchParam(key) { + return fromAndToState.params[key].location != "search"; + } + var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam); + var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys)); + var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams); + return nonQueryParamSet.$$equals(fromParams, toParams); + } + + // If reload was not explicitly requested + // and we're transitioning to the same state we're already in + // and the locals didn't change + // or they changed in a way that doesn't merit reloading + // (reloadOnParams:false, or reloadOnSearch.false and only search params changed) + // Then return true. + if (!options.reload && to === from && + (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) { return true; } } @@ -3609,7 +3729,7 @@ function $ViewScrollProvider() { } return function ($element) { - $timeout(function () { + return $timeout(function () { $element[0].scrollIntoView(); }, 0, false); }; @@ -3894,6 +4014,7 @@ function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate if (locals.$$controller) { locals.$scope = scope; + locals.$element = $element; var controller = $controller(locals.$$controller, locals); if (locals.$$controllerAs) { scope[locals.$$controllerAs] = controller; @@ -4001,7 +4122,7 @@ function stateContext(el) { */ $StateRefDirective.$inject = ['$state', '$timeout']; function $StateRefDirective($state, $timeout) { - var allowedOptions = ['location', 'inherit', 'reload']; + var allowedOptions = ['location', 'inherit', 'reload', 'absolute']; return { restrict: 'A', @@ -4009,9 +4130,12 @@ function $StateRefDirective($state, $timeout) { link: function(scope, element, attrs, uiSrefActive) { var ref = parseStateRef(attrs.uiSref, $state.current.name); var params = null, url = null, base = stateContext(element) || $state.$current; - var newHref = null, isAnchor = element.prop("tagName") === "A"; + // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. + var hrefKind = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? + 'xlink:href' : 'href'; + var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A"; var isForm = element[0].nodeName === "FORM"; - var attr = isForm ? "action" : "href", nav = true; + var attr = isForm ? "action" : hrefKind, nav = true; var options = { relative: base, inherit: true }; var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {}; @@ -4030,7 +4154,7 @@ function $StateRefDirective($state, $timeout) { var activeDirective = uiSrefActive[1] || uiSrefActive[0]; if (activeDirective) { - activeDirective.$$setStateInfo(ref.state, params); + activeDirective.$$addStateInfo(ref.state, params); } if (newHref === null) { nav = false; @@ -4149,7 +4273,7 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) { return { restrict: "A", controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) { - var state, params, activeClass; + var states = [], activeClass; // There probably isn't much point in $observing this // uiSrefActive and uiSrefActiveEq share the same directive object with some @@ -4157,9 +4281,14 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) { activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope); // Allow uiSref to communicate with uiSrefActive[Equals] - this.$$setStateInfo = function (newState, newParams) { - state = $state.get(newState, stateContext($element)); - params = newParams; + this.$$addStateInfo = function (newState, newParams) { + var state = $state.get(newState, stateContext($element)); + + states.push({ + state: state || { name: newState }, + params: newParams + }); + update(); }; @@ -4167,18 +4296,27 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) { // Update route state function update() { - if (isMatch()) { + if (anyMatch()) { $element.addClass(activeClass); } else { $element.removeClass(activeClass); } } - function isMatch() { + function anyMatch() { + for (var i = 0; i < states.length; i++) { + if (isMatch(states[i].state, states[i].params)) { + return true; + } + } + return false; + } + + function isMatch(state, params) { if (typeof $attrs.uiSrefActiveEq !== 'undefined') { - return state && $state.is(state.name, params); + return $state.is(state.name, params); } else { - return state && $state.includes(state.name, params); + return $state.includes(state.name, params); } } }] diff --git a/vendor/assets/components/angular-ui-router/release/angular-ui-router.min.js b/vendor/assets/components/angular-ui-router/release/angular-ui-router.min.js index be06fb5b0..18d8307f5 100644 --- a/vendor/assets/components/angular-ui-router/release/angular-ui-router.min.js +++ b/vendor/assets/components/angular-ui-router/release/angular-ui-router.min.js @@ -1,7 +1,7 @@ /** * State-based routing for AngularJS - * @version v0.2.13 + * @version v0.2.15 * @link http://angular-ui.github.com/ * @license MIT License, http://www.opensource.org/licenses/MIT */ -"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return M(new(M(function(){},{prototype:a})),b)}function e(a){return L(arguments,function(b){b!==a&&L(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a){if(Object.keys)return Object.keys(a);var c=[];return b.forEach(a,function(a,b){c.push(b)}),c}function h(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function i(a,b,c,d){var e,i=f(c,d),j={},k=[];for(var l in i)if(i[l].params&&(e=g(i[l].params),e.length))for(var m in e)h(k,e[m])>=0||(k.push(e[m]),j[e[m]]=a[e[m]]);return M({},j,b)}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(s[c]=d,I(a))q.push(c,[function(){return b.get(a)}],j);else{var e=b.annotate(a);L(e,function(a){a!==c&&i.hasOwnProperty(a)&&n(i[a],a)}),q.push(c,a,e)}r.pop(),s[c]=f}}function o(a){return J(a)&&a.then&&a.$$promises}if(!J(i))throw new Error("'invocables' must be an object");var p=g(i||{}),q=[],r=[],s={};return L(i,n),i=r=s=null,function(d,f,g){function h(){--u||(v||e(t,f.$$values),r.$$values=t,r.$$promises=r.$$promises||!0,delete r.$$inheritedValues,n.resolve(t))}function i(a){r.$$failure=a,n.reject(a)}function j(c,e,f){function j(a){l.reject(a),i(a)}function k(){if(!G(r.$$failure))try{l.resolve(b.invoke(e,g,t)),l.promise.then(function(a){t[c]=a,h()},j)}catch(a){j(a)}}var l=a.defer(),m=0;L(f,function(a){s.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,s[a].then(function(b){t[a]=b,--m||k()},j))}),m||k(),s[c]=l.promise}if(o(d)&&g===c&&(g=f,f=d,d=null),d){if(!J(d))throw new Error("'locals' must be an object")}else d=k;if(f){if(!o(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=m;var n=a.defer(),r=n.promise,s=r.$$promises={},t=M({},d),u=1+q.length/3,v=!1;if(G(f.$$failure))return i(f.$$failure),r;f.$$inheritedValues&&e(t,l(f.$$inheritedValues,p)),M(s,f.$$promises),f.$$values?(v=e(t,l(f.$$values,p)),r.$$inheritedValues=l(f.$$values,p),h()):(f.$$inheritedValues&&(r.$$inheritedValues=l(f.$$inheritedValues,p)),f.then(h,i));for(var w=0,x=q.length;x>w;w+=3)d.hasOwnProperty(q[w])?h():j(q[w],q[w+1],q[w+2]);return r}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function p(a,b,c){this.fromConfig=function(a,b,c){return G(a.template)?this.fromString(a.template,b):G(a.templateUrl)?this.fromUrl(a.templateUrl,b):G(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return H(a)?a(b):a},this.fromUrl=function(c,d){return H(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b,headers:{Accept:"text/html"}}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function q(a,b,e){function f(b,c,d,e){if(q.push(b),o[b])return o[b];if(!/^\w+(-+\w+)*(?:\[\])?$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(p[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");return p[b]=new O.Param(b,c,d,e),p[b]}function g(a,b,c){var d=["",""],e=a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&");if(!b)return e;switch(c){case!1:d=["(",")"];break;case!0:d=["?(",")?"];break;default:d=["("+c+"|",")?"]}return e+d[0]+b+d[1]}function h(c,e){var f,g,h,i,j;return f=c[2]||c[3],j=b.params[f],h=a.substring(m,c.index),g=e?c[4]:c[4]||("*"==c[1]?".*":null),i=O.type(g||"string")||d(O.type("string"),{pattern:new RegExp(g)}),{id:f,regexp:g,segment:h,type:i,cfg:j}}b=M({params:{}},J(b)?b:{});var i,j=/([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,k=/([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,l="^",m=0,n=this.segments=[],o=e?e.params:{},p=this.params=e?e.params.$$new():new O.ParamSet,q=[];this.source=a;for(var r,s,t;(i=j.exec(a))&&(r=h(i,!1),!(r.segment.indexOf("?")>=0));)s=f(r.id,r.type,r.cfg,"path"),l+=g(r.segment,s.type.pattern.source,s.squash),n.push(r.segment),m=j.lastIndex;t=a.substring(m);var u=t.indexOf("?");if(u>=0){var v=this.sourceSearch=t.substring(u);if(t=t.substring(0,u),this.sourcePath=a.substring(0,m+u),v.length>0)for(m=0;i=k.exec(v);)r=h(i,!0),s=f(r.id,r.type,r.cfg,"search"),m=j.lastIndex}else this.sourcePath=a,this.sourceSearch="";l+=g(t)+(b.strict===!1?"/?":"")+"$",n.push(t),this.regexp=new RegExp(l,b.caseInsensitive?"i":c),this.prefix=n[0],this.$$paramNames=q}function r(a){M(this,a)}function s(){function a(a){return null!=a?a.toString().replace(/\//g,"%2F"):a}function e(a){return null!=a?a.toString().replace(/%2F/g,"/"):a}function f(a){return this.pattern.test(a)}function i(){return{strict:t,caseInsensitive:p}}function j(a){return H(a)||K(a)&&H(a[a.length-1])}function k(){for(;x.length;){var a=x.shift();if(a.pattern)throw new Error("You cannot override a type's .pattern at runtime.");b.extend(v[a.name],o.invoke(a.def))}}function l(a){M(this,a||{})}O=this;var o,p=!1,t=!0,u=!1,v={},w=!0,x=[],y={string:{encode:a,decode:e,is:f,pattern:/[^/]*/},"int":{encode:a,decode:function(a){return parseInt(a,10)},is:function(a){return G(a)&&this.decode(a.toString())===a},pattern:/\d+/},bool:{encode:function(a){return a?1:0},decode:function(a){return 0!==parseInt(a,10)},is:function(a){return a===!0||a===!1},pattern:/0|1/},date:{encode:function(a){return this.is(a)?[a.getFullYear(),("0"+(a.getMonth()+1)).slice(-2),("0"+a.getDate()).slice(-2)].join("-"):c},decode:function(a){if(this.is(a))return a;var b=this.capture.exec(a);return b?new Date(b[1],b[2]-1,b[3]):c},is:function(a){return a instanceof Date&&!isNaN(a.valueOf())},equals:function(a,b){return this.is(a)&&this.is(b)&&a.toISOString()===b.toISOString()},pattern:/[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,capture:/([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/},json:{encode:b.toJson,decode:b.fromJson,is:b.isObject,equals:b.equals,pattern:/[^/]*/},any:{encode:b.identity,decode:b.identity,is:b.identity,equals:b.equals,pattern:/.*/}};s.$$getDefaultValue=function(a){if(!j(a.value))return a.value;if(!o)throw new Error("Injectable functions cannot be called at configuration time");return o.invoke(a.value)},this.caseInsensitive=function(a){return G(a)&&(p=a),p},this.strictMode=function(a){return G(a)&&(t=a),t},this.defaultSquashPolicy=function(a){if(!G(a))return u;if(a!==!0&&a!==!1&&!I(a))throw new Error("Invalid squash policy: "+a+". Valid policies: false, true, arbitrary-string");return u=a,a},this.compile=function(a,b){return new q(a,M(i(),b))},this.isMatcher=function(a){if(!J(a))return!1;var b=!0;return L(q.prototype,function(c,d){H(c)&&(b=b&&G(a[d])&&H(a[d]))}),b},this.type=function(a,b,c){if(!G(b))return v[a];if(v.hasOwnProperty(a))throw new Error("A type named '"+a+"' has already been defined.");return v[a]=new r(M({name:a},b)),c&&(x.push({name:a,def:c}),w||k()),this},L(y,function(a,b){v[b]=new r(M({name:b},a))}),v=d(v,{}),this.$get=["$injector",function(a){return o=a,w=!1,k(),L(y,function(a,b){v[b]||(v[b]=new r(a))}),this}],this.Param=function(a,b,d,e){function f(a){var b=J(a)?g(a):[],c=-1===h(b,"value")&&-1===h(b,"type")&&-1===h(b,"squash")&&-1===h(b,"array");return c&&(a={value:a}),a.$$fn=j(a.value)?a.value:function(){return a.value},a}function i(b,c,d){if(b.type&&c)throw new Error("Param '"+a+"' has two type configurations.");return c?c:b.type?b.type instanceof r?b.type:new r(b.type):"config"===d?v.any:v.string}function k(){var b={array:"search"===e?"auto":!1},c=a.match(/\[\]$/)?{array:!0}:{};return M(b,c,d).array}function l(a,b){var c=a.squash;if(!b||c===!1)return!1;if(!G(c)||null==c)return u;if(c===!0||I(c))return c;throw new Error("Invalid squash policy: '"+c+"'. Valid policies: false, true, or arbitrary string")}function p(a,b,d,e){var f,g,i=[{from:"",to:d||b?c:""},{from:null,to:d||b?c:""}];return f=K(a.replace)?a.replace:[],I(e)&&f.push({from:e,to:c}),g=n(f,function(a){return a.from}),m(i,function(a){return-1===h(g,a.from)}).concat(f)}function q(){if(!o)throw new Error("Injectable functions cannot be called at configuration time");return o.invoke(d.$$fn)}function s(a){function b(a){return function(b){return b.from===a}}function c(a){var c=n(m(w.replace,b(a)),function(a){return a.to});return c.length?c[0]:a}return a=c(a),G(a)?w.type.decode(a):q()}function t(){return"{Param:"+a+" "+b+" squash: '"+z+"' optional: "+y+"}"}var w=this;d=f(d),b=i(d,b,e);var x=k();b=x?b.$asArray(x,"search"===e):b,"string"!==b.name||x||"path"!==e||d.value!==c||(d.value="");var y=d.value!==c,z=l(d,y),A=p(d,x,y,z);M(this,{id:a,type:b,location:e,array:x,squash:z,replace:A,isOptional:y,value:s,dynamic:c,config:d,toString:t})},l.prototype={$$new:function(){return d(this,M(new l,{$$parent:this}))},$$keys:function(){for(var a=[],b=[],c=this,d=g(l.prototype);c;)b.push(c),c=c.$$parent;return b.reverse(),L(b,function(b){L(g(b),function(b){-1===h(a,b)&&-1===h(d,b)&&a.push(b)})}),a},$$values:function(a){var b={},c=this;return L(c.$$keys(),function(d){b[d]=c[d].value(a&&a[d])}),b},$$equals:function(a,b){var c=!0,d=this;return L(d.$$keys(),function(e){var f=a&&a[e],g=b&&b[e];d[e].type.equals(f,g)||(c=!1)}),c},$$validates:function(a){var b,c,d,e=!0,f=this;return L(this.$$keys(),function(g){d=f[g],c=a[g],b=!c&&d.isOptional,e=e&&(b||!!d.type.is(c))}),e},$$parent:c},this.ParamSet=l}function t(a,d){function e(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function f(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function g(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return G(d)?d:!0}function h(d,e,f,g){function h(a,b,c){return"/"===p?a:b?p.slice(0,-1)+a:c?p.slice(1)+a:a}function m(a){function b(a){var b=a(f,d);return b?(I(b)&&d.replace().url(b),!0):!1}if(!a||!a.defaultPrevented){var e=o&&d.url()===o;if(o=c,e)return!0;var g,h=j.length;for(g=0;h>g;g++)if(b(j[g]))return;k&&b(k)}}function n(){return i=i||e.$on("$locationChangeSuccess",m)}var o,p=g.baseHref(),q=d.url();return l||n(),{sync:function(){m()},listen:function(){return n()},update:function(a){return a?void(q=d.url()):void(d.url()!==q&&(d.url(q),d.replace()))},push:function(a,b,e){d.url(a.format(b||{})),o=e&&e.$$avoidResync?d.url():c,e&&e.replace&&d.replace()},href:function(c,e,f){if(!c.validates(e))return null;var g=a.html5Mode();b.isObject(g)&&(g=g.enabled);var i=c.format(e);if(f=f||{},g||null===i||(i="#"+a.hashPrefix()+i),i=h(i,g,f.absolute),!f.absolute||!i)return i;var j=!g&&i?"/":"",k=d.port();return k=80===k||443===k?"":":"+k,[d.protocol(),"://",d.host(),k,j,i].join("")}}}var i,j=[],k=null,l=!1;this.rule=function(a){if(!H(a))throw new Error("'rule' must be a function");return j.push(a),this},this.otherwise=function(a){if(I(a)){var b=a;a=function(){return b}}else if(!H(a))throw new Error("'rule' must be a function");return k=a,this},this.when=function(a,b){var c,h=I(b);if(I(a)&&(a=d.compile(a)),!h&&!H(b)&&!K(b))throw new Error("invalid 'handler' in when()");var i={matcher:function(a,b){return h&&(c=d.compile(b),b=["$match",function(a){return c.format(a)}]),M(function(c,d){return g(c,b,a.exec(d.path(),d.search()))},{prefix:I(a.prefix)?a.prefix:""})},regex:function(a,b){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(c=b,b=["$match",function(a){return f(c,a)}]),M(function(c,d){return g(c,b,a.exec(d.path()))},{prefix:e(a)})}},j={matcher:d.isMatcher(a),regex:a instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](a,b));throw new Error("invalid 'what' in when()")},this.deferIntercept=function(a){a===c&&(a=!0),l=a},this.$get=h,h.$inject=["$location","$rootScope","$injector","$browser"]}function u(a,e){function f(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){if(!a)return c;var d=I(a),e=d?a:a.name,g=f(e);if(g){if(!b)throw new Error("No reference point given for path '"+e+"'");b=l(b);for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var m=y[e];return!m||!d&&(d||m!==a&&m.self!==a)?c:m}function m(a,b){z[a]||(z[a]=[]),z[a].push(b)}function o(a){for(var b=z[a]||[];b.length;)p(b.shift())}function p(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!I(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(y.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):I(b.parent)?b.parent:J(b.parent)&&I(b.parent.name)?b.parent.name:"";if(e&&!y[e])return m(e,b.self);for(var f in B)H(B[f])&&(b[f]=B[f](b,B.$delegates[f]));return y[c]=b,!b[A]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){x.$current.navigable==b&&j(a,c)||x.transitionTo(b,a,{inherit:!0,location:!1})}]),o(c),b}function q(a){return a.indexOf("*")>-1}function r(a){var b=a.split("."),c=x.$current.name.split(".");if("**"===b[0]&&(c=c.slice(h(c,b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(h(c,b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length)return!1;for(var d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return c.join("")===b.join("")}function s(a,b){return I(a)&&!G(b)?B[a]:H(b)&&I(a)?(B[a]&&!B.$delegates[a]&&(B.$delegates[a]=B[a]),B[a]=b,this):this}function t(a,b){return J(a)?b=a:b.name=a,p(b),this}function u(a,e,f,h,m,o,p){function s(b,c,d,f){var g=a.$broadcast("$stateNotFound",b,c,d);if(g.defaultPrevented)return p.update(),B;if(!g.retry)return null;if(f.$retry)return p.update(),C;var h=x.transition=e.when(g.retry);return h.then(function(){return h!==x.transition?u:(b.options.$retry=!0,x.transitionTo(b.to,b.toParams,b.options))},function(){return B}),p.update(),h}function t(a,c,d,g,i,j){var l=d?c:k(a.params.$$keys(),c),n={$stateParams:l};i.resolve=m.resolve(a.resolve,n,i.resolve,a);var o=[i.resolve.then(function(a){i.globals=a})];return g&&o.push(g),L(a.views,function(c,d){var e=c.resolve&&c.resolve!==a.resolve?c.resolve:{};e.$template=[function(){return f.load(d,{view:c,locals:n,params:l,notify:j.notify})||""}],o.push(m.resolve(e,n,i.resolve,a).then(function(f){if(H(c.controllerProvider)||K(c.controllerProvider)){var g=b.extend({},e,n);f.$$controller=h.invoke(c.controllerProvider,null,g)}else f.$$controller=c.controller;f.$$state=a,f.$$controllerAs=c.controllerAs,i[d]=f}))}),e.all(o).then(function(){return i})}var u=e.reject(new Error("transition superseded")),z=e.reject(new Error("transition prevented")),B=e.reject(new Error("transition aborted")),C=e.reject(new Error("transition failed"));return w.locals={resolve:null,globals:{$stateParams:{}}},x={params:{},current:w.self,$current:w,transition:null},x.reload=function(){return x.transitionTo(x.current,o,{reload:!0,inherit:!1,notify:!0})},x.go=function(a,b,c){return x.transitionTo(a,b,M({inherit:!0,relative:x.$current},c))},x.transitionTo=function(b,c,f){c=c||{},f=M({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,j=x.$current,m=x.params,n=j.path,q=l(b,f.relative);if(!G(q)){var r={to:b,toParams:c,options:f},y=s(r,j.self,m,f);if(y)return y;if(b=r.to,c=r.toParams,f=r.options,q=l(b,f.relative),!G(q)){if(!f.relative)throw new Error("No such state '"+b+"'");throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'")}}if(q[A])throw new Error("Cannot transition to abstract state '"+b+"'");if(f.inherit&&(c=i(o,c||{},x.$current,q)),!q.params.$$validates(c))return C;c=q.params.$$values(c),b=q;var B=b.path,D=0,E=B[D],F=w.locals,H=[];if(!f.reload)for(;E&&E===n[D]&&E.ownParams.$$equals(c,m);)F=H[D]=E.locals,D++,E=B[D];if(v(b,j,F,f))return b.self.reloadOnSearch!==!1&&p.update(),x.transition=null,e.when(x.current);if(c=k(b.params.$$keys(),c||{}),f.notify&&a.$broadcast("$stateChangeStart",b.self,c,j.self,m).defaultPrevented)return p.update(),z;for(var I=e.when(F),J=D;J=D;d--)g=n[d],g.self.onExit&&h.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=D;d=0?e:e+"@"+(f?f.state.name:"")}function A(a,b){var c,d=a.match(/^\s*({[^}]*})\s*$/);if(d&&(a=b+"("+d[1]+")"),c=a.replace(/\n/g," ").match(/^([^(]+?)\s*(\((.*)\))?$/),!c||4!==c.length)throw new Error("Invalid state ref '"+a+"'");return{state:c[1],paramExpr:c[3]||null}}function B(a){var b=a.parent().inheritedData("$uiView");return b&&b.state&&b.state.name?b.state:void 0}function C(a,c){var d=["location","inherit","reload"];return{restrict:"A",require:["?^uiSrefActive","?^uiSrefActiveEq"],link:function(e,f,g,h){var i=A(g.uiSref,a.current.name),j=null,k=B(f)||a.$current,l=null,m="A"===f.prop("tagName"),n="FORM"===f[0].nodeName,o=n?"action":"href",p=!0,q={relative:k,inherit:!0},r=e.$eval(g.uiSrefOpts)||{};b.forEach(d,function(a){a in r&&(q[a]=r[a])});var s=function(c){if(c&&(j=b.copy(c)),p){l=a.href(i.state,j,q);var d=h[1]||h[0];return d&&d.$$setStateInfo(i.state,j),null===l?(p=!1,!1):void g.$set(o,l)}};i.paramExpr&&(e.$watch(i.paramExpr,function(a){a!==j&&s(a)},!0),j=b.copy(e.$eval(i.paramExpr))),s(),n||f.bind("click",function(b){var d=b.which||b.button;if(!(d>1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target"))){var e=c(function(){a.go(i.state,j,q)});b.preventDefault();var g=m&&!l?1:0;b.preventDefault=function(){g--<=0&&c.cancel(e)}}})}}}function D(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(b,d,e){function f(){g()?d.addClass(j):d.removeClass(j)}function g(){return"undefined"!=typeof e.uiSrefActiveEq?h&&a.is(h.name,i):h&&a.includes(h.name,i)}var h,i,j;j=c(e.uiSrefActiveEq||e.uiSrefActive||"",!1)(b),this.$$setStateInfo=function(b,c){h=a.get(b,B(d)),i=c,f()},b.$on("$stateChangeSuccess",f)}]}}function E(a){var b=function(b){return a.is(b)};return b.$stateful=!0,b}function F(a){var b=function(b){return a.includes(b)};return b.$stateful=!0,b}var G=b.isDefined,H=b.isFunction,I=b.isString,J=b.isObject,K=b.isArray,L=b.forEach,M=b.extend,N=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),o.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",o),p.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",p);var O;q.prototype.concat=function(a,b){var c={caseInsensitive:O.caseInsensitive(),strict:O.strictMode(),squash:O.defaultSquashPolicy()};return new q(this.sourcePath+a+this.sourceSearch,M(c,b),this)},q.prototype.toString=function(){return this.source},q.prototype.exec=function(a,b){function c(a){function b(a){return a.split("").reverse().join("")}function c(a){return a.replace(/\\-/,"-")}var d=b(a).split(/-(?!\\)/),e=n(d,b);return n(e,c).reverse()}var d=this.regexp.exec(a);if(!d)return null;b=b||{};var e,f,g,h=this.parameters(),i=h.length,j=this.segments.length-1,k={};if(j!==d.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(e=0;j>e;e++){g=h[e];var l=this.params[g],m=d[e+1];for(f=0;fe;e++)g=h[e],k[g]=this.params[g].value(b[g]);return k},q.prototype.parameters=function(a){return G(a)?this.params[a]||null:this.$$paramNames},q.prototype.validates=function(a){return this.params.$$validates(a)},q.prototype.format=function(a){function b(a){return encodeURIComponent(a).replace(/-/g,function(a){return"%5C%"+a.charCodeAt(0).toString(16).toUpperCase()})}a=a||{};var c=this.segments,d=this.parameters(),e=this.params;if(!this.validates(a))return null;var f,g=!1,h=c.length-1,i=d.length,j=c[0];for(f=0;i>f;f++){var k=h>f,l=d[f],m=e[l],o=m.value(a[l]),p=m.isOptional&&m.type.equals(m.value(),o),q=p?m.squash:!1,r=m.type.encode(o);if(k){var s=c[f+1];if(q===!1)null!=r&&(j+=K(r)?n(r,b).join("-"):encodeURIComponent(r)),j+=s;else if(q===!0){var t=j.match(/\/$/)?/\/?(.*)/:/(.*)/;j+=s.match(t)[1]}else I(q)&&(j+=q+s)}else{if(null==r||p&&q!==!1)continue;K(r)||(r=[r]),r=n(r,encodeURIComponent).join("&"+l+"="),j+=(g?"&":"?")+(l+"="+r),g=!0}}return j},r.prototype.is=function(){return!0},r.prototype.encode=function(a){return a},r.prototype.decode=function(a){return a},r.prototype.equals=function(a,b){return a==b},r.prototype.$subPattern=function(){var a=this.pattern.toString();return a.substr(1,a.length-2)},r.prototype.pattern=/.*/,r.prototype.toString=function(){return"{Type:"+this.name+"}"},r.prototype.$asArray=function(a,b){function d(a,b){function d(a,b){return function(){return a[b].apply(a,arguments)}}function e(a){return K(a)?a:G(a)?[a]:[]}function f(a){switch(a.length){case 0:return c;case 1:return"auto"===b?a[0]:a;default:return a}}function g(a){return!a}function h(a,b){return function(c){c=e(c);var d=n(c,a);return b===!0?0===m(d,g).length:f(d)}}function i(a){return function(b,c){var d=e(b),f=e(c);if(d.length!==f.length)return!1;for(var g=0;g>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function i(a,b,c,d){var e,i=f(c,d),j={},k=[];for(var l in i)if(i[l].params&&(e=g(i[l].params),e.length))for(var m in e)h(k,e[m])>=0||(k.push(e[m]),j[e[m]]=a[e[m]]);return N({},j,b)}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(s[c]=d,J(a))q.push(c,[function(){return b.get(a)}],j);else{var e=b.annotate(a);M(e,function(a){a!==c&&i.hasOwnProperty(a)&&n(i[a],a)}),q.push(c,a,e)}r.pop(),s[c]=f}}function o(a){return K(a)&&a.then&&a.$$promises}if(!K(i))throw new Error("'invocables' must be an object");var p=g(i||{}),q=[],r=[],s={};return M(i,n),i=r=s=null,function(d,f,g){function h(){--u||(v||e(t,f.$$values),r.$$values=t,r.$$promises=r.$$promises||!0,delete r.$$inheritedValues,n.resolve(t))}function i(a){r.$$failure=a,n.reject(a)}function j(c,e,f){function j(a){l.reject(a),i(a)}function k(){if(!H(r.$$failure))try{l.resolve(b.invoke(e,g,t)),l.promise.then(function(a){t[c]=a,h()},j)}catch(a){j(a)}}var l=a.defer(),m=0;M(f,function(a){s.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,s[a].then(function(b){t[a]=b,--m||k()},j))}),m||k(),s[c]=l.promise}if(o(d)&&g===c&&(g=f,f=d,d=null),d){if(!K(d))throw new Error("'locals' must be an object")}else d=k;if(f){if(!o(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=l;var n=a.defer(),r=n.promise,s=r.$$promises={},t=N({},d),u=1+q.length/3,v=!1;if(H(f.$$failure))return i(f.$$failure),r;f.$$inheritedValues&&e(t,m(f.$$inheritedValues,p)),N(s,f.$$promises),f.$$values?(v=e(t,m(f.$$values,p)),r.$$inheritedValues=m(f.$$values,p),h()):(f.$$inheritedValues&&(r.$$inheritedValues=m(f.$$inheritedValues,p)),f.then(h,i));for(var w=0,x=q.length;x>w;w+=3)d.hasOwnProperty(q[w])?h():j(q[w],q[w+1],q[w+2]);return r}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function q(a,b,c){this.fromConfig=function(a,b,c){return H(a.template)?this.fromString(a.template,b):H(a.templateUrl)?this.fromUrl(a.templateUrl,b):H(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return I(a)?a(b):a},this.fromUrl=function(c,d){return I(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b,headers:{Accept:"text/html"}}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function r(a,b,e){function f(b,c,d,e){if(q.push(b),o[b])return o[b];if(!/^\w+(-+\w+)*(?:\[\])?$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(p[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");return p[b]=new P.Param(b,c,d,e),p[b]}function g(a,b,c,d){var e=["",""],f=a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&");if(!b)return f;switch(c){case!1:e=["(",")"+(d?"?":"")];break;case!0:e=["?(",")?"];break;default:e=["("+c+"|",")?"]}return f+e[0]+b+e[1]}function h(e,f){var g,h,i,j,k;return g=e[2]||e[3],k=b.params[g],i=a.substring(m,e.index),h=f?e[4]:e[4]||("*"==e[1]?".*":null),j=P.type(h||"string")||d(P.type("string"),{pattern:new RegExp(h,b.caseInsensitive?"i":c)}),{id:g,regexp:h,segment:i,type:j,cfg:k}}b=N({params:{}},K(b)?b:{});var i,j=/([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,k=/([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,l="^",m=0,n=this.segments=[],o=e?e.params:{},p=this.params=e?e.params.$$new():new P.ParamSet,q=[];this.source=a;for(var r,s,t;(i=j.exec(a))&&(r=h(i,!1),!(r.segment.indexOf("?")>=0));)s=f(r.id,r.type,r.cfg,"path"),l+=g(r.segment,s.type.pattern.source,s.squash,s.isOptional),n.push(r.segment),m=j.lastIndex;t=a.substring(m);var u=t.indexOf("?");if(u>=0){var v=this.sourceSearch=t.substring(u);if(t=t.substring(0,u),this.sourcePath=a.substring(0,m+u),v.length>0)for(m=0;i=k.exec(v);)r=h(i,!0),s=f(r.id,r.type,r.cfg,"search"),m=j.lastIndex}else this.sourcePath=a,this.sourceSearch="";l+=g(t)+(b.strict===!1?"/?":"")+"$",n.push(t),this.regexp=new RegExp(l,b.caseInsensitive?"i":c),this.prefix=n[0],this.$$paramNames=q}function s(a){N(this,a)}function t(){function a(a){return null!=a?a.toString().replace(/\//g,"%2F"):a}function e(a){return null!=a?a.toString().replace(/%2F/g,"/"):a}function f(){return{strict:p,caseInsensitive:m}}function i(a){return I(a)||L(a)&&I(a[a.length-1])}function j(){for(;w.length;){var a=w.shift();if(a.pattern)throw new Error("You cannot override a type's .pattern at runtime.");b.extend(u[a.name],l.invoke(a.def))}}function k(a){N(this,a||{})}P=this;var l,m=!1,p=!0,q=!1,u={},v=!0,w=[],x={string:{encode:a,decode:e,is:function(a){return null==a||!H(a)||"string"==typeof a},pattern:/[^/]*/},"int":{encode:a,decode:function(a){return parseInt(a,10)},is:function(a){return H(a)&&this.decode(a.toString())===a},pattern:/\d+/},bool:{encode:function(a){return a?1:0},decode:function(a){return 0!==parseInt(a,10)},is:function(a){return a===!0||a===!1},pattern:/0|1/},date:{encode:function(a){return this.is(a)?[a.getFullYear(),("0"+(a.getMonth()+1)).slice(-2),("0"+a.getDate()).slice(-2)].join("-"):c},decode:function(a){if(this.is(a))return a;var b=this.capture.exec(a);return b?new Date(b[1],b[2]-1,b[3]):c},is:function(a){return a instanceof Date&&!isNaN(a.valueOf())},equals:function(a,b){return this.is(a)&&this.is(b)&&a.toISOString()===b.toISOString()},pattern:/[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,capture:/([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/},json:{encode:b.toJson,decode:b.fromJson,is:b.isObject,equals:b.equals,pattern:/[^/]*/},any:{encode:b.identity,decode:b.identity,equals:b.equals,pattern:/.*/}};t.$$getDefaultValue=function(a){if(!i(a.value))return a.value;if(!l)throw new Error("Injectable functions cannot be called at configuration time");return l.invoke(a.value)},this.caseInsensitive=function(a){return H(a)&&(m=a),m},this.strictMode=function(a){return H(a)&&(p=a),p},this.defaultSquashPolicy=function(a){if(!H(a))return q;if(a!==!0&&a!==!1&&!J(a))throw new Error("Invalid squash policy: "+a+". Valid policies: false, true, arbitrary-string");return q=a,a},this.compile=function(a,b){return new r(a,N(f(),b))},this.isMatcher=function(a){if(!K(a))return!1;var b=!0;return M(r.prototype,function(c,d){I(c)&&(b=b&&H(a[d])&&I(a[d]))}),b},this.type=function(a,b,c){if(!H(b))return u[a];if(u.hasOwnProperty(a))throw new Error("A type named '"+a+"' has already been defined.");return u[a]=new s(N({name:a},b)),c&&(w.push({name:a,def:c}),v||j()),this},M(x,function(a,b){u[b]=new s(N({name:b},a))}),u=d(u,{}),this.$get=["$injector",function(a){return l=a,v=!1,j(),M(x,function(a,b){u[b]||(u[b]=new s(a))}),this}],this.Param=function(a,b,d,e){function f(a){var b=K(a)?g(a):[],c=-1===h(b,"value")&&-1===h(b,"type")&&-1===h(b,"squash")&&-1===h(b,"array");return c&&(a={value:a}),a.$$fn=i(a.value)?a.value:function(){return a.value},a}function j(b,c,d){if(b.type&&c)throw new Error("Param '"+a+"' has two type configurations.");return c?c:b.type?b.type instanceof s?b.type:new s(b.type):"config"===d?u.any:u.string}function k(){var b={array:"search"===e?"auto":!1},c=a.match(/\[\]$/)?{array:!0}:{};return N(b,c,d).array}function m(a,b){var c=a.squash;if(!b||c===!1)return!1;if(!H(c)||null==c)return q;if(c===!0||J(c))return c;throw new Error("Invalid squash policy: '"+c+"'. Valid policies: false, true, or arbitrary string")}function p(a,b,d,e){var f,g,i=[{from:"",to:d||b?c:""},{from:null,to:d||b?c:""}];return f=L(a.replace)?a.replace:[],J(e)&&f.push({from:e,to:c}),g=o(f,function(a){return a.from}),n(i,function(a){return-1===h(g,a.from)}).concat(f)}function r(){if(!l)throw new Error("Injectable functions cannot be called at configuration time");var a=l.invoke(d.$$fn);if(null!==a&&a!==c&&!w.type.is(a))throw new Error("Default value ("+a+") for parameter '"+w.id+"' is not an instance of Type ("+w.type.name+")");return a}function t(a){function b(a){return function(b){return b.from===a}}function c(a){var c=o(n(w.replace,b(a)),function(a){return a.to});return c.length?c[0]:a}return a=c(a),H(a)?w.type.$normalize(a):r()}function v(){return"{Param:"+a+" "+b+" squash: '"+z+"' optional: "+y+"}"}var w=this;d=f(d),b=j(d,b,e);var x=k();b=x?b.$asArray(x,"search"===e):b,"string"!==b.name||x||"path"!==e||d.value!==c||(d.value="");var y=d.value!==c,z=m(d,y),A=p(d,x,y,z);N(this,{id:a,type:b,location:e,array:x,squash:z,replace:A,isOptional:y,value:t,dynamic:c,config:d,toString:v})},k.prototype={$$new:function(){return d(this,N(new k,{$$parent:this}))},$$keys:function(){for(var a=[],b=[],c=this,d=g(k.prototype);c;)b.push(c),c=c.$$parent;return b.reverse(),M(b,function(b){M(g(b),function(b){-1===h(a,b)&&-1===h(d,b)&&a.push(b)})}),a},$$values:function(a){var b={},c=this;return M(c.$$keys(),function(d){b[d]=c[d].value(a&&a[d])}),b},$$equals:function(a,b){var c=!0,d=this;return M(d.$$keys(),function(e){var f=a&&a[e],g=b&&b[e];d[e].type.equals(f,g)||(c=!1)}),c},$$validates:function(a){var d,e,f,g,h,i=this.$$keys();for(d=0;de;e++)if(b(j[e]))return;k&&b(k)}}function n(){return i=i||e.$on("$locationChangeSuccess",m)}var o,p=g.baseHref(),q=d.url();return l||n(),{sync:function(){m()},listen:function(){return n()},update:function(a){return a?void(q=d.url()):void(d.url()!==q&&(d.url(q),d.replace()))},push:function(a,b,e){var f=a.format(b||{});null!==f&&b&&b["#"]&&(f+="#"+b["#"]),d.url(f),o=e&&e.$$avoidResync?d.url():c,e&&e.replace&&d.replace()},href:function(c,e,f){if(!c.validates(e))return null;var g=a.html5Mode();b.isObject(g)&&(g=g.enabled);var i=c.format(e);if(f=f||{},g||null===i||(i="#"+a.hashPrefix()+i),null!==i&&e&&e["#"]&&(i+="#"+e["#"]),i=h(i,g,f.absolute),!f.absolute||!i)return i;var j=!g&&i?"/":"",k=d.port();return k=80===k||443===k?"":":"+k,[d.protocol(),"://",d.host(),k,j,i].join("")}}}var i,j=[],k=null,l=!1;this.rule=function(a){if(!I(a))throw new Error("'rule' must be a function");return j.push(a),this},this.otherwise=function(a){if(J(a)){var b=a;a=function(){return b}}else if(!I(a))throw new Error("'rule' must be a function");return k=a,this},this.when=function(a,b){var c,h=J(b);if(J(a)&&(a=d.compile(a)),!h&&!I(b)&&!L(b))throw new Error("invalid 'handler' in when()");var i={matcher:function(a,b){return h&&(c=d.compile(b),b=["$match",function(a){return c.format(a)}]),N(function(c,d){return g(c,b,a.exec(d.path(),d.search()))},{prefix:J(a.prefix)?a.prefix:""})},regex:function(a,b){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(c=b,b=["$match",function(a){return f(c,a)}]),N(function(c,d){return g(c,b,a.exec(d.path()))},{prefix:e(a)})}},j={matcher:d.isMatcher(a),regex:a instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](a,b));throw new Error("invalid 'what' in when()")},this.deferIntercept=function(a){a===c&&(a=!0),l=a},this.$get=h,h.$inject=["$location","$rootScope","$injector","$browser"]}function v(a,e){function f(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function m(a,b){if(!a)return c;var d=J(a),e=d?a:a.name,g=f(e);if(g){if(!b)throw new Error("No reference point given for path '"+e+"'");b=m(b);for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=z[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function n(a,b){A[a]||(A[a]=[]),A[a].push(b)}function p(a){for(var b=A[a]||[];b.length;)q(b.shift())}function q(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!J(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(z.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):J(b.parent)?b.parent:K(b.parent)&&J(b.parent.name)?b.parent.name:"";if(e&&!z[e])return n(e,b.self);for(var f in C)I(C[f])&&(b[f]=C[f](b,C.$delegates[f]));return z[c]=b,!b[B]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){y.$current.navigable==b&&j(a,c)||y.transitionTo(b,a,{inherit:!0,location:!1})}]),p(c),b}function r(a){return a.indexOf("*")>-1}function s(a){for(var b=a.split("."),c=y.$current.name.split("."),d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return"**"===b[0]&&(c=c.slice(h(c,b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(h(c,b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length?!1:c.join("")===b.join("")}function t(a,b){return J(a)&&!H(b)?C[a]:I(b)&&J(a)?(C[a]&&!C.$delegates[a]&&(C.$delegates[a]=C[a]),C[a]=b,this):this}function u(a,b){return K(a)?b=a:b.name=a,q(b),this}function v(a,e,f,h,l,n,p,q,t){function u(b,c,d,f){var g=a.$broadcast("$stateNotFound",b,c,d);if(g.defaultPrevented)return p.update(),D;if(!g.retry)return null;if(f.$retry)return p.update(),E;var h=y.transition=e.when(g.retry);return h.then(function(){return h!==y.transition?A:(b.options.$retry=!0,y.transitionTo(b.to,b.toParams,b.options))},function(){return D}),p.update(),h}function v(a,c,d,g,i,j){function m(){var c=[];return M(a.views,function(d,e){var g=d.resolve&&d.resolve!==a.resolve?d.resolve:{};g.$template=[function(){return f.load(e,{view:d,locals:i.globals,params:n,notify:j.notify})||""}],c.push(l.resolve(g,i.globals,i.resolve,a).then(function(c){if(I(d.controllerProvider)||L(d.controllerProvider)){var f=b.extend({},g,i.globals);c.$$controller=h.invoke(d.controllerProvider,null,f)}else c.$$controller=d.controller;c.$$state=a,c.$$controllerAs=d.controllerAs,i[e]=c}))}),e.all(c).then(function(){return i.globals})}var n=d?c:k(a.params.$$keys(),c),o={$stateParams:n};i.resolve=l.resolve(a.resolve,o,i.resolve,a);var p=[i.resolve.then(function(a){i.globals=a})];return g&&p.push(g),e.all(p).then(m).then(function(a){return i})}var A=e.reject(new Error("transition superseded")),C=e.reject(new Error("transition prevented")),D=e.reject(new Error("transition aborted")),E=e.reject(new Error("transition failed"));return x.locals={resolve:null,globals:{$stateParams:{}}},y={params:{},current:x.self,$current:x,transition:null},y.reload=function(a){return y.transitionTo(y.current,n,{reload:a||!0,inherit:!1,notify:!0})},y.go=function(a,b,c){return y.transitionTo(a,b,N({inherit:!0,relative:y.$current},c))},y.transitionTo=function(b,c,f){c=c||{},f=N({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,j=y.$current,l=y.params,o=j.path,q=m(b,f.relative),r=c["#"];if(!H(q)){var s={to:b,toParams:c,options:f},t=u(s,j.self,l,f);if(t)return t;if(b=s.to,c=s.toParams,f=s.options,q=m(b,f.relative),!H(q)){if(!f.relative)throw new Error("No such state '"+b+"'");throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'")}}if(q[B])throw new Error("Cannot transition to abstract state '"+b+"'");if(f.inherit&&(c=i(n,c||{},y.$current,q)),!q.params.$$validates(c))return E;c=q.params.$$values(c),b=q;var z=b.path,D=0,F=z[D],G=x.locals,I=[];if(f.reload){if(J(f.reload)||K(f.reload)){if(K(f.reload)&&!f.reload.name)throw new Error("Invalid reload state object");var L=f.reload===!0?o[0]:m(f.reload);if(f.reload&&!L)throw new Error("No such reload state '"+(J(f.reload)?f.reload:f.reload.name)+"'");for(;F&&F===o[D]&&F!==L;)G=I[D]=F.locals,D++,F=z[D]}}else for(;F&&F===o[D]&&F.ownParams.$$equals(c,l);)G=I[D]=F.locals,D++,F=z[D];if(w(b,c,j,l,G,f))return r&&(c["#"]=r),y.params=c,O(y.params,n),f.location&&b.navigable&&b.navigable.url&&(p.push(b.navigable.url,c,{$$avoidResync:!0,replace:"replace"===f.location}),p.update(!0)),y.transition=null,e.when(y.current);if(c=k(b.params.$$keys(),c||{}),f.notify&&a.$broadcast("$stateChangeStart",b.self,c,j.self,l).defaultPrevented)return a.$broadcast("$stateChangeCancel",b.self,c,j.self,l),p.update(),C;for(var M=e.when(G),P=D;P=D;d--)g=o[d],g.self.onExit&&h.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=D;d=0?e:e+"@"+(f?f.state.name:"")}function B(a,b){var c,d=a.match(/^\s*({[^}]*})\s*$/);if(d&&(a=b+"("+d[1]+")"),c=a.replace(/\n/g," ").match(/^([^(]+?)\s*(\((.*)\))?$/),!c||4!==c.length)throw new Error("Invalid state ref '"+a+"'");return{state:c[1],paramExpr:c[3]||null}}function C(a){var b=a.parent().inheritedData("$uiView");return b&&b.state&&b.state.name?b.state:void 0}function D(a,c){var d=["location","inherit","reload","absolute"];return{restrict:"A",require:["?^uiSrefActive","?^uiSrefActiveEq"],link:function(e,f,g,h){var i=B(g.uiSref,a.current.name),j=null,k=C(f)||a.$current,l="[object SVGAnimatedString]"===Object.prototype.toString.call(f.prop("href"))?"xlink:href":"href",m=null,n="A"===f.prop("tagName").toUpperCase(),o="FORM"===f[0].nodeName,p=o?"action":l,q=!0,r={relative:k,inherit:!0},s=e.$eval(g.uiSrefOpts)||{};b.forEach(d,function(a){a in s&&(r[a]=s[a])});var t=function(c){if(c&&(j=b.copy(c)),q){m=a.href(i.state,j,r);var d=h[1]||h[0];return d&&d.$$addStateInfo(i.state,j),null===m?(q=!1,!1):void g.$set(p,m)}};i.paramExpr&&(e.$watch(i.paramExpr,function(a,b){a!==j&&t(a)},!0),j=b.copy(e.$eval(i.paramExpr))),t(),o||f.bind("click",function(b){var d=b.which||b.button;if(!(d>1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target"))){var e=c(function(){a.go(i.state,j,r)});b.preventDefault();var g=n&&!m?1:0;b.preventDefault=function(){g--<=0&&c.cancel(e)}}})}}}function E(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(b,d,e){function f(){g()?d.addClass(i):d.removeClass(i)}function g(){for(var a=0;ae;e++){g=h[e];var l=this.params[g],m=d[e+1];for(f=0;fe;e++)g=h[e],k[g]=this.params[g].value(b[g]);return k},r.prototype.parameters=function(a){return H(a)?this.params[a]||null:this.$$paramNames},r.prototype.validates=function(a){return this.params.$$validates(a)},r.prototype.format=function(a){function b(a){return encodeURIComponent(a).replace(/-/g,function(a){return"%5C%"+a.charCodeAt(0).toString(16).toUpperCase()})}a=a||{};var c=this.segments,d=this.parameters(),e=this.params;if(!this.validates(a))return null;var f,g=!1,h=c.length-1,i=d.length,j=c[0];for(f=0;i>f;f++){var k=h>f,l=d[f],m=e[l],n=m.value(a[l]),p=m.isOptional&&m.type.equals(m.value(),n),q=p?m.squash:!1,r=m.type.encode(n);if(k){var s=c[f+1];if(q===!1)null!=r&&(j+=L(r)?o(r,b).join("-"):encodeURIComponent(r)),j+=s;else if(q===!0){var t=j.match(/\/$/)?/\/?(.*)/:/(.*)/;j+=s.match(t)[1]}else J(q)&&(j+=q+s)}else{if(null==r||p&&q!==!1)continue;L(r)||(r=[r]),r=o(r,encodeURIComponent).join("&"+l+"="),j+=(g?"&":"?")+(l+"="+r),g=!0}}return j},s.prototype.is=function(a,b){return!0},s.prototype.encode=function(a,b){return a},s.prototype.decode=function(a,b){return a},s.prototype.equals=function(a,b){return a==b},s.prototype.$subPattern=function(){var a=this.pattern.toString();return a.substr(1,a.length-2)},s.prototype.pattern=/.*/,s.prototype.toString=function(){return"{Type:"+this.name+"}"},s.prototype.$normalize=function(a){return this.is(a)?a:this.decode(a)},s.prototype.$asArray=function(a,b){function d(a,b){function d(a,b){return function(){return a[b].apply(a,arguments)}}function e(a){return L(a)?a:H(a)?[a]:[]}function f(a){switch(a.length){case 0:return c;case 1:return"auto"===b?a[0]:a;default:return a}}function g(a){return!a}function h(a,b){return function(c){c=e(c);var d=o(c,a);return b===!0?0===n(d,g).length:f(d)}}function i(a){return function(b,c){var d=e(b),f=e(c);if(d.length!==f.length)return!1;for(var g=0;gcontrollerAs: "myCtrl" * + * @param {string|object=} stateConfig.parent + * + * Optionally specifies the parent state of this state. + * + *
    parent: 'parentState'
    + *
    parent: parentState // JS variable
    + * * @param {object=} stateConfig.resolve * * @@ -476,6 +483,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * transitioned to, the `$stateParams` service will be populated with any * parameters that were passed. * + * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for + * more details on acceptable patterns ) + * * examples: *
    url: "/home"
        * url: "/users/:userid"
    @@ -483,8 +493,9 @@ function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory) {
        * url: "/books/{categoryid:int}"
        * url: "/books/{publishername:string}/{categoryid:int}"
        * url: "/messages?before&after"
    -   * url: "/messages?{before:date}&{after:date}"
    + * url: "/messages?{before:date}&{after:date}" * url: "/messages/:mailboxid?{before:date}&{after:date}" + * * * @param {object=} stateConfig.views * @@ -788,8 +799,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * @methodOf ui.router.state.$state * * @description - * A method that force reloads the current state. All resolves are re-resolved, events are not re-fired, - * and controllers reinstantiated (bug with controllers reinstantiating right now, fixing soon). + * A method that force reloads the current state. All resolves are re-resolved, + * controllers reinstantiated, and events re-fired. * * @example *
    @@ -809,11 +820,33 @@ function $StateProvider(   $urlRouterProvider,   $urlMatcherFactory) {
          * });
          * 
    * + * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved. + * @example + *
    +     * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item' 
    +     * //and current state is 'contacts.detail.item'
    +     * var app angular.module('app', ['ui.router']);
    +     *
    +     * app.controller('ctrl', function ($scope, $state) {
    +     *   $scope.reload = function(){
    +     *     //will reload 'contact.detail' and 'contact.detail.item' states
    +     *     $state.reload('contact.detail');
    +     *   }
    +     * });
    +     * 
    + * + * `reload()` is just an alias for: + *
    +     * $state.transitionTo($state.current, $stateParams, { 
    +     *   reload: true, inherit: false, notify: true
    +     * });
    +     * 
    + * @returns {promise} A promise representing the state of the new transition. See * {@link ui.router.state.$state#methods_go $state.go}. */ - $state.reload = function reload() { - return $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: true }); + $state.reload = function reload(state) { + return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true}); }; /** @@ -917,9 +950,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), * defines which state to be relative from. * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. - * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params + * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd * use this when you want to force a reload when *everything* is the same, including search params. + * if String, then will reload the state with the name given in reload, and any children. + * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children. * * @returns {promise} A promise representing the state of the new transition. See * {@link ui.router.state.$state#methods_go $state.go}. @@ -933,6 +968,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { var from = $state.$current, fromParams = $state.params, fromPath = from.path; var evt, toState = findState(to, options.relative); + // Store the hash param for later (since it will be stripped out by various methods) + var hash = toParams['#']; + if (!isDefined(toState)) { var redirect = { to: to, toParams: toParams, options: options }; var redirectResult = handleRedirect(redirect, from.self, fromParams, options); @@ -971,6 +1009,21 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { keep++; state = toPath[keep]; } + } else if (isString(options.reload) || isObject(options.reload)) { + if (isObject(options.reload) && !options.reload.name) { + throw new Error('Invalid reload state object'); + } + + var reloadState = options.reload === true ? fromPath[0] : findState(options.reload); + if (options.reload && !reloadState) { + throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'"); + } + + while (state && state === fromPath[keep] && state !== reloadState) { + locals = toLocals[keep] = state.locals; + keep++; + state = toPath[keep]; + } } // If we're going to the same state and all locals are kept, we've got nothing to do. @@ -978,8 +1031,16 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { // TODO: We may not want to bump 'transition' if we're called from a location change // that we've initiated ourselves, because we might accidentally abort a legitimate // transition initiated from code? - if (shouldTriggerReload(to, from, locals, options)) { - if (to.self.reloadOnSearch !== false) $urlRouter.update(); + if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) { + if (hash) toParams['#'] = hash; + $state.params = toParams; + copy($state.params, $stateParams); + if (options.location && to.navigable && to.navigable.url) { + $urlRouter.push(to.navigable.url, toParams, { + $$avoidResync: true, replace: options.location === 'replace' + }); + $urlRouter.update(true); + } $state.transition = null; return $q.when($state.current); } @@ -1017,6 +1078,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * */ if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) { + $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); $urlRouter.update(); return TransitionPrevented; } @@ -1063,6 +1125,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { } } + // Re-add the saved hash before we start returning things + if (hash) toParams['#'] = hash; + // Run it again, to catch any transitions in callbacks if ($state.transition !== transition) return TransitionSuperseded; @@ -1288,7 +1353,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { if (!nav || nav.url === undefined || nav.url === null) { return null; } - return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys(), params || {}), { + return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), { absolute: options.absolute }); }; @@ -1330,30 +1395,38 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { })]; if (inherited) promises.push(inherited); - // Resolve template and dependencies for all views. - forEach(state.views, function (view, name) { - var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); - injectables.$template = [ function () { - return $view.load(name, { view: view, locals: locals, params: $stateParams, notify: options.notify }) || ''; - }]; + function resolveViews() { + var viewsPromises = []; - promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) { - // References to the controller (only instantiated at link time) - if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { - var injectLocals = angular.extend({}, injectables, locals); - result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); - } else { - result.$$controller = view.controller; - } - // Provide access to the state itself for internal use - result.$$state = state; - result.$$controllerAs = view.controllerAs; - dst[name] = result; - })); - }); + // Resolve template and dependencies for all views. + forEach(state.views, function (view, name) { + var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); + injectables.$template = [ function () { + return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || ''; + }]; + + viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) { + // References to the controller (only instantiated at link time) + if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { + var injectLocals = angular.extend({}, injectables, dst.globals); + result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); + } else { + result.$$controller = view.controller; + } + // Provide access to the state itself for internal use + result.$$state = state; + result.$$controllerAs = view.controllerAs; + dst[name] = result; + })); + }); + + return $q.all(viewsPromises).then(function(){ + return dst.globals; + }); + } // Wait for all the promises and then return the activation object - return $q.all(promises).then(function (values) { + return $q.all(promises).then(resolveViews).then(function (values) { return dst; }); } @@ -1361,8 +1434,27 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { return $state; } - function shouldTriggerReload(to, from, locals, options) { - if (to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false))) { + function shouldSkipReload(to, toParams, from, fromParams, locals, options) { + // Return true if there are no differences in non-search (path/object) params, false if there are differences + function nonSearchParamsEqual(fromAndToState, fromParams, toParams) { + // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params. + function notSearchParam(key) { + return fromAndToState.params[key].location != "search"; + } + var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam); + var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys)); + var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams); + return nonQueryParamSet.$$equals(fromParams, toParams); + } + + // If reload was not explicitly requested + // and we're transitioning to the same state we're already in + // and the locals didn't change + // or they changed in a way that doesn't merit reloading + // (reloadOnParams:false, or reloadOnSearch.false and only search params changed) + // Then return true. + if (!options.reload && to === from && + (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) { return true; } } diff --git a/vendor/assets/components/angular-ui-router/src/stateDirectives.js b/vendor/assets/components/angular-ui-router/src/stateDirectives.js index 4d9d527dd..09991030c 100644 --- a/vendor/assets/components/angular-ui-router/src/stateDirectives.js +++ b/vendor/assets/components/angular-ui-router/src/stateDirectives.js @@ -78,7 +78,7 @@ function stateContext(el) { */ $StateRefDirective.$inject = ['$state', '$timeout']; function $StateRefDirective($state, $timeout) { - var allowedOptions = ['location', 'inherit', 'reload']; + var allowedOptions = ['location', 'inherit', 'reload', 'absolute']; return { restrict: 'A', @@ -86,9 +86,12 @@ function $StateRefDirective($state, $timeout) { link: function(scope, element, attrs, uiSrefActive) { var ref = parseStateRef(attrs.uiSref, $state.current.name); var params = null, url = null, base = stateContext(element) || $state.$current; - var newHref = null, isAnchor = element.prop("tagName") === "A"; + // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. + var hrefKind = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? + 'xlink:href' : 'href'; + var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A"; var isForm = element[0].nodeName === "FORM"; - var attr = isForm ? "action" : "href", nav = true; + var attr = isForm ? "action" : hrefKind, nav = true; var options = { relative: base, inherit: true }; var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {}; @@ -107,7 +110,7 @@ function $StateRefDirective($state, $timeout) { var activeDirective = uiSrefActive[1] || uiSrefActive[0]; if (activeDirective) { - activeDirective.$$setStateInfo(ref.state, params); + activeDirective.$$addStateInfo(ref.state, params); } if (newHref === null) { nav = false; @@ -226,7 +229,7 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) { return { restrict: "A", controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) { - var state, params, activeClass; + var states = [], activeClass; // There probably isn't much point in $observing this // uiSrefActive and uiSrefActiveEq share the same directive object with some @@ -234,9 +237,14 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) { activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope); // Allow uiSref to communicate with uiSrefActive[Equals] - this.$$setStateInfo = function (newState, newParams) { - state = $state.get(newState, stateContext($element)); - params = newParams; + this.$$addStateInfo = function (newState, newParams) { + var state = $state.get(newState, stateContext($element)); + + states.push({ + state: state || { name: newState }, + params: newParams + }); + update(); }; @@ -244,18 +252,27 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) { // Update route state function update() { - if (isMatch()) { + if (anyMatch()) { $element.addClass(activeClass); } else { $element.removeClass(activeClass); } } - function isMatch() { + function anyMatch() { + for (var i = 0; i < states.length; i++) { + if (isMatch(states[i].state, states[i].params)) { + return true; + } + } + return false; + } + + function isMatch(state, params) { if (typeof $attrs.uiSrefActiveEq !== 'undefined') { - return state && $state.is(state.name, params); + return $state.is(state.name, params); } else { - return state && $state.includes(state.name, params); + return $state.includes(state.name, params); } } }] diff --git a/vendor/assets/components/angular-ui-router/src/urlMatcherFactory.js b/vendor/assets/components/angular-ui-router/src/urlMatcherFactory.js index a16e728c5..bf116f027 100644 --- a/vendor/assets/components/angular-ui-router/src/urlMatcherFactory.js +++ b/vendor/assets/components/angular-ui-router/src/urlMatcherFactory.js @@ -10,7 +10,7 @@ var $$UMFP; // reference to $UrlMatcherFactoryProvider * of search parameters. Multiple search parameter names are separated by '&'. Search parameters * do not influence whether or not a URL is matched, but their values are passed through into * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}. - * + * * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace * syntax, which optionally allows a regular expression for the parameter to be specified: * @@ -21,13 +21,13 @@ var $$UMFP; // reference to $UrlMatcherFactoryProvider * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash. * * Parameter names may contain only word characters (latin letters, digits, and underscore) and - * must be unique within the pattern (across both path and search parameters). For colon + * must be unique within the pattern (across both path and search parameters). For colon * placeholders or curly placeholders without an explicit regexp, a path parameter matches any * number of characters other than '/'. For catch-all placeholders the path parameter matches * any number of characters. - * + * * Examples: - * + * * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for * trailing slashes, and patterns have to match the entire path, not just a prefix. * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or @@ -60,7 +60,7 @@ var $$UMFP; // reference to $UrlMatcherFactoryProvider * * @property {string} sourceSearch The search portion of the source property * - * @property {string} regex The constructed regex that will be used to match against the url when + * @property {string} regex The constructed regex that will be used to match against the url when * it is time to determine which url will match. * * @returns {Object} New `UrlMatcher` object @@ -98,13 +98,13 @@ function UrlMatcher(pattern, config, parentMatcher) { return params[id]; } - function quoteRegExp(string, pattern, squash) { + function quoteRegExp(string, pattern, squash, optional) { var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); if (!pattern) return result; switch(squash) { - case false: surroundPattern = ['(', ')']; break; + case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break; case true: surroundPattern = ['?(', ')?']; break; - default: surroundPattern = ['(' + squash + "|", ')?']; break; + default: surroundPattern = ['(' + squash + "|", ')?']; break; } return result + surroundPattern[0] + pattern + surroundPattern[1]; } @@ -119,7 +119,7 @@ function UrlMatcher(pattern, config, parentMatcher) { cfg = config.params[id]; segment = pattern.substring(last, m.index); regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null); - type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp) }); + type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) }); return { id: id, regexp: regexp, segment: segment, type: type, cfg: cfg }; @@ -131,7 +131,7 @@ function UrlMatcher(pattern, config, parentMatcher) { if (p.segment.indexOf('?') >= 0) break; // we're into the search part param = addParameter(p.id, p.type, p.cfg, "path"); - compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash); + compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional); segments.push(p.segment); last = placeholder.lastIndex; } @@ -242,7 +242,7 @@ UrlMatcher.prototype.exec = function (path, searchParams) { function decodePathArray(string) { function reverseString(str) { return str.split("").reverse().join(""); } - function unquoteDashes(str) { return str.replace(/\\-/, "-"); } + function unquoteDashes(str) { return str.replace(/\\-/g, "-"); } var split = reverseString(string).split(/-(?!\\)/); var allReversed = map(split, reverseString); @@ -275,7 +275,7 @@ UrlMatcher.prototype.exec = function (path, searchParams) { * * @description * Returns the names of all path and search parameters of this pattern in an unspecified order. - * + * * @returns {Array.} An array of parameter names. Must be treated as read-only. If the * pattern has no parameters, an empty array is returned. */ @@ -480,6 +480,11 @@ Type.prototype.pattern = /.*/; Type.prototype.toString = function() { return "{Type:" + this.name + "}"; }; +/** Given an encoded string, or a decoded object, returns a decoded object */ +Type.prototype.$normalize = function(val) { + return this.is(val) ? val : this.decode(val); +}; + /* * Wraps an existing custom Type as an array of Type, depending on 'mode'. * e.g.: @@ -493,7 +498,6 @@ Type.prototype.toString = function() { return "{Type:" + this.name + "}"; }; Type.prototype.$asArray = function(mode, isSearch) { if (!mode) return this; if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only"); - return new ArrayType(this, mode); function ArrayType(type, mode) { function bindTo(type, callbackName) { @@ -542,8 +546,12 @@ Type.prototype.$asArray = function(mode, isSearch) { this.is = arrayHandler(bindTo(type, 'is'), true); this.equals = arrayEqualsHandler(bindTo(type, 'equals')); this.pattern = type.pattern; + this.$normalize = arrayHandler(bindTo(type, '$normalize')); + this.name = type.name; this.$arrayMode = mode; } + + return new ArrayType(this, mode); }; @@ -563,15 +571,14 @@ function $UrlMatcherFactory() { function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; } function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; } -// TODO: in 1.0, make string .is() return false if value is undefined by default. -// function regexpMatches(val) { /*jshint validthis:true */ return isDefined(val) && this.pattern.test(val); } - function regexpMatches(val) { /*jshint validthis:true */ return this.pattern.test(val); } var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = { string: { encode: valToString, decode: valFromString, - is: regexpMatches, + // TODO: in 1.0, make string .is() return false if value is undefined/null by default. + // In 0.2.x, string params are optional by default for backwards compat + is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; }, pattern: /[^/]*/ }, int: { @@ -615,7 +622,6 @@ function $UrlMatcherFactory() { any: { // does not encode/decode encode: angular.identity, decode: angular.identity, - is: angular.identity, equals: angular.equals, pattern: /.*/ } @@ -945,7 +951,10 @@ function $UrlMatcherFactory() { */ function $$getDefaultValue() { if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); - return injector.invoke(config.$$fn); + var defaultValue = injector.invoke(config.$$fn); + if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue)) + throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")"); + return defaultValue; } /** @@ -959,7 +968,7 @@ function $UrlMatcherFactory() { return replacement.length ? replacement[0] : value; } value = $replace(value); - return isDefined(value) ? self.type.decode(value) : $$getDefaultValue(); + return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value); } function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; } @@ -1015,15 +1024,20 @@ function $UrlMatcherFactory() { return equal; }, $$validates: function $$validate(paramValues) { - var result = true, isOptional, val, param, self = this; - - forEach(this.$$keys(), function(key) { - param = self[key]; - val = paramValues[key]; - isOptional = !val && param.isOptional; - result = result && (isOptional || !!param.type.is(val)); - }); - return result; + var keys = this.$$keys(), i, param, rawVal, normalized, encoded; + for (i = 0; i < keys.length; i++) { + param = this[keys[i]]; + rawVal = paramValues[keys[i]]; + if ((rawVal === undefined || rawVal === null) && param.isOptional) + break; // There was no parameter value, but the param is optional + normalized = param.type.$normalize(rawVal); + if (!param.type.is(normalized)) + return false; // The value was not of the correct Type, and could not be decoded to the correct Type + encoded = param.type.encode(normalized); + if (angular.isString(encoded) && !param.type.pattern.exec(encoded)) + return false; // The value was of the correct type, but when encoded, did not match the Type's regexp + } + return true; }, $$parent: undefined }; diff --git a/vendor/assets/components/angular-ui-router/src/urlRouter.js b/vendor/assets/components/angular-ui-router/src/urlRouter.js index 2b2293762..33c17090f 100644 --- a/vendor/assets/components/angular-ui-router/src/urlRouter.js +++ b/vendor/assets/components/angular-ui-router/src/urlRouter.js @@ -279,7 +279,8 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { if (evt && evt.defaultPrevented) return; var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl; lastPushedUrl = undefined; - if (ignoreUpdate) return true; + // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573 + //if (ignoreUpdate) return true; function check(rule) { var handled = rule($injector, $location); @@ -351,7 +352,14 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { }, push: function(urlMatcher, params, options) { - $location.url(urlMatcher.format(params || {})); + var url = urlMatcher.format(params || {}); + + // Handle the special hash param, if needed + if (url !== null && params && params['#']) { + url += '#' + params['#']; + } + + $location.url(url); lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined; if (options && options.replace) $location.replace(); }, @@ -395,6 +403,12 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { if (!isHtml5 && url !== null) { url = "#" + $locationProvider.hashPrefix() + url; } + + // Handle special hash param, if needed + if (url !== null && params && params['#']) { + url += '#' + params['#']; + } + url = appendBasePath(url, isHtml5, options.absolute); if (!options.absolute || !url) { diff --git a/vendor/assets/components/angular-ui-router/src/viewDirective.js b/vendor/assets/components/angular-ui-router/src/viewDirective.js index d3cf100a2..b70220779 100644 --- a/vendor/assets/components/angular-ui-router/src/viewDirective.js +++ b/vendor/assets/components/angular-ui-router/src/viewDirective.js @@ -274,6 +274,7 @@ function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate if (locals.$$controller) { locals.$scope = scope; + locals.$element = $element; var controller = $controller(locals.$$controller, locals); if (locals.$$controllerAs) { scope[locals.$$controllerAs] = controller; diff --git a/vendor/assets/components/angular-ui-router/src/viewScroll.js b/vendor/assets/components/angular-ui-router/src/viewScroll.js index dfe0a030d..81114e20d 100644 --- a/vendor/assets/components/angular-ui-router/src/viewScroll.js +++ b/vendor/assets/components/angular-ui-router/src/viewScroll.js @@ -42,7 +42,7 @@ function $ViewScrollProvider() { } return function ($element) { - $timeout(function () { + return $timeout(function () { $element[0].scrollIntoView(); }, 0, false); }; diff --git a/vendor/assets/components/angular-ui-select/.bower.json b/vendor/assets/components/angular-ui-select/.bower.json new file mode 100644 index 000000000..f147ea2e6 --- /dev/null +++ b/vendor/assets/components/angular-ui-select/.bower.json @@ -0,0 +1,42 @@ +{ + "name": "ui-select", + "version": "0.13.2", + "homepage": "https://github.com/angular-ui/ui-select", + "authors": [ + "AngularUI" + ], + "description": "AngularJS ui-select", + "main": [ + "dist/select.js", + "dist/select.css" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "src", + "test", + "gulpfile.js", + "karma.conf.js", + "examples" + ], + "dependencies": { + "angular": ">=1.2.18" + }, + "devDependencies": { + "jquery": "~1.11", + "angular-sanitize": ">=1.2.18", + "angular-mocks": ">=1.2.18" + }, + "_release": "0.13.2", + "_resolution": { + "type": "version", + "tag": "v0.13.2", + "commit": "e507f2fa3d821270f6c862f221c3e33379d1f74e" + }, + "_source": "git://github.com/angular-ui/ui-select.git", + "_target": "~0.13.2", + "_originalSource": "angular-ui-select", + "_direct": true +} \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-select/CHANGELOG.md b/vendor/assets/components/angular-ui-select/CHANGELOG.md new file mode 100644 index 000000000..1c8112275 --- /dev/null +++ b/vendor/assets/components/angular-ui-select/CHANGELOG.md @@ -0,0 +1,28 @@ +# Change Log +All notable changes to this project will be documented in this file. + +## [v0.13.1][v0.13.1] (2015-09-29) +### Fixed +- Remove hardcoded source name when using (key,value) syntax [#1217](https://github.com/angular-ui/ui-select/pull/1217) +- Modify regex to accept a full 'collection expression' when not using (key,value) syntax [#1216](https://github.com/angular-ui/ui-select/pull/1216) +- Avoid to recalculate position when set 'down' [#1214](https://github.com/angular-ui/ui-select/issues/1214#issuecomment-144271352) + +## [v0.13.0][v0.13.0] (2015-09-29) +### Added +- Allow to configure default dropdown position [#1213](https://github.com/angular-ui/ui-select/pull/1213) +- Can use object as source with (key,value) syntax [#1208](https://github.com/angular-ui/ui-select/pull/1208) +- CHANGELOG.md file created + +### Changed +- Do not run bower after install automatically [#982](https://github.com/angular-ui/ui-select/pull/982) +- Avoid setting activeItem on mouseenter to improve performance [#1211](https://github.com/angular-ui/ui-select/pull/1211) + +### Fixed +- Position dropdown UP or DOWN correctly depending on the available space [#1212](https://github.com/angular-ui/ui-select/pull/1212) +- Scroll to selected item [#976](https://github.com/angular-ui/ui-select/issues/976) +- Change `autocomplete='off'` to `autocomplete='false'` [#1210](https://github.com/angular-ui/ui-select/pull/1210) +- Fix to work correctly with debugInfoEnabled(false) [#1131](https://github.com/angular-ui/ui-select/pull/1131) +- Limit the maximum number of selections allowed in multiple mode [#1110](https://github.com/angular-ui/ui-select/pull/1110) + +[v0.13.1]: https://github.com/angular-ui/ui-select/compare/v0.13.0...v0.13.1 +[v0.13.0]: https://github.com/angular-ui/ui-select/compare/v0.12.1...v0.13.0 diff --git a/vendor/assets/components/angular-ui-select/LICENSE b/vendor/assets/components/angular-ui-select/LICENSE new file mode 100644 index 000000000..b62d482f8 --- /dev/null +++ b/vendor/assets/components/angular-ui-select/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013-2014 AngularUI + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/assets/components/angular-ui-select/README.md b/vendor/assets/components/angular-ui-select/README.md new file mode 100644 index 000000000..4375d9729 --- /dev/null +++ b/vendor/assets/components/angular-ui-select/README.md @@ -0,0 +1,46 @@ +# AngularJS ui-select [![Build Status](https://travis-ci.org/angular-ui/ui-select.svg?branch=master)](https://travis-ci.org/angular-ui/ui-select) + +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular-ui/ui-select?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +AngularJS-native version of [Select2](http://ivaynberg.github.io/select2/) and [Selectize](http://brianreavis.github.io/selectize.js/). + +- [Demo](http://plnkr.co/edit/a3KlK8dKH3wwiiksDSn2?p=preview) +- [Demo Multiselect](http://plnkr.co/edit/juqoNOt1z1Gb349XabQ2?p=preview) +- [Examples](https://github.com/angular-ui/ui-select/blob/master/examples) +- [Documentation](https://github.com/angular-ui/ui-select/wiki) + +## Last Changes + +- Check [CHANGELOG.md](/CHANGELOG.md) + +## Features + +- Search, Select, and Multi-select +- Themes: Bootstrap, Select2 and Selectize +- Keyboard support +- jQuery not required (except for old browsers) +- Small code base: 4.57KB min/gzipped vs 20KB for select2 + +For the roadmap, check [issue #3](https://github.com/angular-ui/ui-select/issues/3) and the [Wiki page](https://github.com/angular-ui/ui-select/wiki/Roadmap). + + +## Development + +### Prepare your environment +* Install [Node.js](http://nodejs.org/) and NPM (should come with) +* Install global dev dependencies: `npm install -g bower gulp` +* Install local dev dependencies: `npm install && bower install` in repository directory + +### Development Commands + +* `gulp` to jshint, build and test +* `gulp build` to jshint and build +* `gulp test` for one-time test with karma (also build and jshint) +* `gulp watch` to watch src files to jshin, build and test when changed + +## Contributing + +- Run the tests +- Try the [examples](https://github.com/angular-ui/ui-select/blob/master/examples) + +When issuing a pull request, please exclude changes from the "dist" folder to avoid merge conflicts. diff --git a/vendor/assets/components/angular-ui-select/bower.json b/vendor/assets/components/angular-ui-select/bower.json new file mode 100644 index 000000000..1f8ef74d0 --- /dev/null +++ b/vendor/assets/components/angular-ui-select/bower.json @@ -0,0 +1,29 @@ +{ + "name": "ui-select", + "version": "0.13.2", + "homepage": "https://github.com/angular-ui/ui-select", + "authors": [ + "AngularUI" + ], + "description": "AngularJS ui-select", + "main": ["dist/select.js", "dist/select.css"], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "src", + "test", + "gulpfile.js", + "karma.conf.js", + "examples" + ], + "dependencies": { + "angular": ">=1.2.18" + }, + "devDependencies": { + "jquery": "~1.11", + "angular-sanitize": ">=1.2.18", + "angular-mocks": ">=1.2.18" + } +} diff --git a/vendor/assets/components/angular-ui-select/dist/select.css b/vendor/assets/components/angular-ui-select/dist/select.css new file mode 100644 index 000000000..4cce226ba --- /dev/null +++ b/vendor/assets/components/angular-ui-select/dist/select.css @@ -0,0 +1,267 @@ +/*! + * ui-select + * http://github.com/angular-ui/ui-select + * Version: 0.13.2 - 2015-10-09T15:34:24.045Z + * License: MIT + */ + + +/* Style when highlighting a search. */ +.ui-select-highlight { + font-weight: bold; +} + +.ui-select-offscreen { + clip: rect(0 0 0 0) !important; + width: 1px !important; + height: 1px !important; + border: 0 !important; + margin: 0 !important; + padding: 0 !important; + overflow: hidden !important; + position: absolute !important; + outline: 0 !important; + left: 0px !important; + top: 0px !important; +} + + +.ui-select-choices-row:hover { + background-color: #f5f5f5; +} + +/* Select2 theme */ + +/* Mark invalid Select2 */ +.ng-dirty.ng-invalid > a.select2-choice { + border-color: #D44950; +} + +.select2-result-single { + padding-left: 0; +} + +.select2-locked > .select2-search-choice-close{ + display:none; +} + +.select-locked > .ui-select-match-close{ + display:none; +} + +body > .select2-container.open { + z-index: 9999; /* The z-index Select2 applies to the select2-drop */ +} + +/* Handle up direction Select2 */ +.ui-select-container[theme="select2"].direction-up .ui-select-match { + border-radius: 4px; /* FIXME hardcoded value :-/ */ + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.ui-select-container[theme="select2"].direction-up .ui-select-dropdown { + border-radius: 4px; /* FIXME hardcoded value :-/ */ + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + + border-top-width: 1px; /* FIXME hardcoded value :-/ */ + border-top-style: solid; + + box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); + + margin-top: -4px; /* FIXME hardcoded value :-/ */ +} +.ui-select-container[theme="select2"].direction-up .ui-select-dropdown .select2-search { + margin-top: 4px; /* FIXME hardcoded value :-/ */ +} +.ui-select-container[theme="select2"].direction-up.select2-dropdown-open .ui-select-match { + border-bottom-color: #5897fb; +} + +/* Selectize theme */ + +/* Helper class to show styles when focus */ +.selectize-input.selectize-focus{ + border-color: #007FBB !important; +} + +/* Fix input width for Selectize theme */ +.selectize-control > .selectize-input > input { + width: 100%; +} + +/* Fix dropdown width for Selectize theme */ +.selectize-control > .selectize-dropdown { + width: 100%; +} + +/* Mark invalid Selectize */ +.ng-dirty.ng-invalid > div.selectize-input { + border-color: #D44950; +} + +/* Handle up direction Selectize */ +.ui-select-container[theme="selectize"].direction-up .ui-select-dropdown { + box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); + + margin-top: -2px; /* FIXME hardcoded value :-/ */ +} + +/* Bootstrap theme */ + +/* Helper class to show styles when focus */ +.btn-default-focus { + color: #333; + background-color: #EBEBEB; + border-color: #ADADAD; + text-decoration: none; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.ui-select-bootstrap .ui-select-toggle { + position: relative; +} + +.ui-select-bootstrap .ui-select-toggle > .caret { + position: absolute; + height: 10px; + top: 50%; + right: 10px; + margin-top: -2px; +} + +/* Fix Bootstrap dropdown position when inside a input-group */ +.input-group > .ui-select-bootstrap.dropdown { + /* Instead of relative */ + position: static; +} + +.input-group > .ui-select-bootstrap > input.ui-select-search.form-control { + border-radius: 4px; /* FIXME hardcoded value :-/ */ + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group > .ui-select-bootstrap > input.ui-select-search.form-control.direction-up { + border-radius: 4px !important; /* FIXME hardcoded value :-/ */ + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +.ui-select-bootstrap > .ui-select-match > .btn{ + /* Instead of center because of .btn */ + text-align: left !important; +} + +.ui-select-bootstrap > .ui-select-match > .caret { + position: absolute; + top: 45%; + right: 15px; +} + +/* See Scrollable Menu with Bootstrap 3 http://stackoverflow.com/questions/19227496 */ +.ui-select-bootstrap > .ui-select-choices { + width: 100%; + height: auto; + max-height: 200px; + overflow-x: hidden; + margin-top: -1px; +} + +body > .ui-select-bootstrap.open { + z-index: 1000; /* Standard Bootstrap dropdown z-index */ +} + +.ui-select-multiple.ui-select-bootstrap { + height: auto; + padding: 3px 3px 0 3px; +} + +.ui-select-multiple.ui-select-bootstrap input.ui-select-search { + background-color: transparent !important; /* To prevent double background when disabled */ + border: none; + outline: none; + height: 1.666666em; + margin-bottom: 3px; +} + +.ui-select-multiple.ui-select-bootstrap .ui-select-match .close { + font-size: 1.6em; + line-height: 0.75; +} + +.ui-select-multiple.ui-select-bootstrap .ui-select-match-item { + outline: 0; + margin: 0 3px 3px 0; +} + +.ui-select-multiple .ui-select-match-item { + position: relative; +} + +.ui-select-multiple .ui-select-match-item.dropping-before:before { + content: ""; + position: absolute; + top: 0; + right: 100%; + height: 100%; + margin-right: 2px; + border-left: 1px solid #428bca; +} + +.ui-select-multiple .ui-select-match-item.dropping-after:after { + content: ""; + position: absolute; + top: 0; + left: 100%; + height: 100%; + margin-left: 2px; + border-right: 1px solid #428bca; +} + +.ui-select-bootstrap .ui-select-choices-row>a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: 400; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} + +.ui-select-bootstrap .ui-select-choices-row>a:hover, .ui-select-bootstrap .ui-select-choices-row>a:focus { + text-decoration: none; + color: #262626; + background-color: #f5f5f5; +} + +.ui-select-bootstrap .ui-select-choices-row.active>a { + color: #fff; + text-decoration: none; + outline: 0; + background-color: #428bca; +} + +.ui-select-bootstrap .ui-select-choices-row.disabled>a, +.ui-select-bootstrap .ui-select-choices-row.active.disabled>a { + color: #777; + cursor: not-allowed; + background-color: #fff; +} + +/* fix hide/show angular animation */ +.ui-select-match.ng-hide-add, +.ui-select-search.ng-hide-add { + display: none !important; +} + +/* Mark invalid Bootstrap */ +.ui-select-bootstrap.ng-dirty.ng-invalid > button.btn.ui-select-match { + border-color: #D44950; +} + +/* Handle up direction Bootstrap */ +.ui-select-container[theme="bootstrap"].direction-up .ui-select-dropdown { + box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); +} diff --git a/vendor/assets/components/angular-ui-select/dist/select.js b/vendor/assets/components/angular-ui-select/dist/select.js new file mode 100644 index 000000000..6349bae30 --- /dev/null +++ b/vendor/assets/components/angular-ui-select/dist/select.js @@ -0,0 +1,1926 @@ +/*! + * ui-select + * http://github.com/angular-ui/ui-select + * Version: 0.13.2 - 2015-10-09T15:34:24.040Z + * License: MIT + */ + + +(function () { +"use strict"; + +var KEY = { + TAB: 9, + ENTER: 13, + ESC: 27, + SPACE: 32, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + SHIFT: 16, + CTRL: 17, + ALT: 18, + PAGE_UP: 33, + PAGE_DOWN: 34, + HOME: 36, + END: 35, + BACKSPACE: 8, + DELETE: 46, + COMMAND: 91, + + MAP: { 91 : "COMMAND", 8 : "BACKSPACE" , 9 : "TAB" , 13 : "ENTER" , 16 : "SHIFT" , 17 : "CTRL" , 18 : "ALT" , 19 : "PAUSEBREAK" , 20 : "CAPSLOCK" , 27 : "ESC" , 32 : "SPACE" , 33 : "PAGE_UP", 34 : "PAGE_DOWN" , 35 : "END" , 36 : "HOME" , 37 : "LEFT" , 38 : "UP" , 39 : "RIGHT" , 40 : "DOWN" , 43 : "+" , 44 : "PRINTSCREEN" , 45 : "INSERT" , 46 : "DELETE", 48 : "0" , 49 : "1" , 50 : "2" , 51 : "3" , 52 : "4" , 53 : "5" , 54 : "6" , 55 : "7" , 56 : "8" , 57 : "9" , 59 : ";", 61 : "=" , 65 : "A" , 66 : "B" , 67 : "C" , 68 : "D" , 69 : "E" , 70 : "F" , 71 : "G" , 72 : "H" , 73 : "I" , 74 : "J" , 75 : "K" , 76 : "L", 77 : "M" , 78 : "N" , 79 : "O" , 80 : "P" , 81 : "Q" , 82 : "R" , 83 : "S" , 84 : "T" , 85 : "U" , 86 : "V" , 87 : "W" , 88 : "X" , 89 : "Y" , 90 : "Z", 96 : "0" , 97 : "1" , 98 : "2" , 99 : "3" , 100 : "4" , 101 : "5" , 102 : "6" , 103 : "7" , 104 : "8" , 105 : "9", 106 : "*" , 107 : "+" , 109 : "-" , 110 : "." , 111 : "/", 112 : "F1" , 113 : "F2" , 114 : "F3" , 115 : "F4" , 116 : "F5" , 117 : "F6" , 118 : "F7" , 119 : "F8" , 120 : "F9" , 121 : "F10" , 122 : "F11" , 123 : "F12", 144 : "NUMLOCK" , 145 : "SCROLLLOCK" , 186 : ";" , 187 : "=" , 188 : "," , 189 : "-" , 190 : "." , 191 : "/" , 192 : "`" , 219 : "[" , 220 : "\\" , 221 : "]" , 222 : "'" + }, + + isControl: function (e) { + var k = e.which; + switch (k) { + case KEY.COMMAND: + case KEY.SHIFT: + case KEY.CTRL: + case KEY.ALT: + return true; + } + + if (e.metaKey) return true; + + return false; + }, + isFunctionKey: function (k) { + k = k.which ? k.which : k; + return k >= 112 && k <= 123; + }, + isVerticalMovement: function (k){ + return ~[KEY.UP, KEY.DOWN].indexOf(k); + }, + isHorizontalMovement: function (k){ + return ~[KEY.LEFT,KEY.RIGHT,KEY.BACKSPACE,KEY.DELETE].indexOf(k); + } + }; + +/** + * Add querySelectorAll() to jqLite. + * + * jqLite find() is limited to lookups by tag name. + * TODO This will change with future versions of AngularJS, to be removed when this happens + * + * See jqLite.find - why not use querySelectorAll? https://github.com/angular/angular.js/issues/3586 + * See feat(jqLite): use querySelectorAll instead of getElementsByTagName in jqLite.find https://github.com/angular/angular.js/pull/3598 + */ +if (angular.element.prototype.querySelectorAll === undefined) { + angular.element.prototype.querySelectorAll = function(selector) { + return angular.element(this[0].querySelectorAll(selector)); + }; +} + +/** + * Add closest() to jqLite. + */ +if (angular.element.prototype.closest === undefined) { + angular.element.prototype.closest = function( selector) { + var elem = this[0]; + var matchesSelector = elem.matches || elem.webkitMatchesSelector || elem.mozMatchesSelector || elem.msMatchesSelector; + + while (elem) { + if (matchesSelector.bind(elem)(selector)) { + return elem; + } else { + elem = elem.parentElement; + } + } + return false; + }; +} + +var latestId = 0; + +var uis = angular.module('ui.select', []) + +.constant('uiSelectConfig', { + theme: 'bootstrap', + searchEnabled: true, + sortable: false, + placeholder: '', // Empty by default, like HTML tag "); + $compile(focusser)(scope); + $select.focusser = focusser; + + //Input that will handle focus + $select.focusInput = focusser; + + element.parent().append(focusser); + focusser.bind("focus", function(){ + scope.$evalAsync(function(){ + $select.focus = true; + }); + }); + focusser.bind("blur", function(){ + scope.$evalAsync(function(){ + $select.focus = false; + }); + }); + focusser.bind("keydown", function(e){ + + if (e.which === KEY.BACKSPACE) { + e.preventDefault(); + e.stopPropagation(); + $select.select(undefined); + scope.$apply(); + return; + } + + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { + return; + } + + if (e.which == KEY.DOWN || e.which == KEY.UP || e.which == KEY.ENTER || e.which == KEY.SPACE){ + e.preventDefault(); + e.stopPropagation(); + $select.activate(); + } + + scope.$digest(); + }); + + focusser.bind("keyup input", function(e){ + + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC || e.which == KEY.ENTER || e.which === KEY.BACKSPACE) { + return; + } + + $select.activate(focusser.val()); //User pressed some regular key, so we pass it to the search input + focusser.val(''); + scope.$digest(); + + }); + + + } + }; +}]); +// Make multiple matches sortable +uis.directive('uiSelectSort', ['$timeout', 'uiSelectConfig', 'uiSelectMinErr', function($timeout, uiSelectConfig, uiSelectMinErr) { + return { + require: '^uiSelect', + link: function(scope, element, attrs, $select) { + if (scope[attrs.uiSelectSort] === null) { + throw uiSelectMinErr('sort', "Expected a list to sort"); + } + + var options = angular.extend({ + axis: 'horizontal' + }, + scope.$eval(attrs.uiSelectSortOptions)); + + var axis = options.axis, + draggingClassName = 'dragging', + droppingClassName = 'dropping', + droppingBeforeClassName = 'dropping-before', + droppingAfterClassName = 'dropping-after'; + + scope.$watch(function(){ + return $select.sortable; + }, function(n){ + if (n) { + element.attr('draggable', true); + } else { + element.removeAttr('draggable'); + } + }); + + element.on('dragstart', function(e) { + element.addClass(draggingClassName); + + (e.dataTransfer || e.originalEvent.dataTransfer).setData('text/plain', scope.$index); + }); + + element.on('dragend', function() { + element.removeClass(draggingClassName); + }); + + var move = function(from, to) { + /*jshint validthis: true */ + this.splice(to, 0, this.splice(from, 1)[0]); + }; + + var dragOverHandler = function(e) { + e.preventDefault(); + + var offset = axis === 'vertical' ? e.offsetY || e.layerY || (e.originalEvent ? e.originalEvent.offsetY : 0) : e.offsetX || e.layerX || (e.originalEvent ? e.originalEvent.offsetX : 0); + + if (offset < (this[axis === 'vertical' ? 'offsetHeight' : 'offsetWidth'] / 2)) { + element.removeClass(droppingAfterClassName); + element.addClass(droppingBeforeClassName); + + } else { + element.removeClass(droppingBeforeClassName); + element.addClass(droppingAfterClassName); + } + }; + + var dropTimeout; + + var dropHandler = function(e) { + e.preventDefault(); + + var droppedItemIndex = parseInt((e.dataTransfer || e.originalEvent.dataTransfer).getData('text/plain'), 10); + + // prevent event firing multiple times in firefox + $timeout.cancel(dropTimeout); + dropTimeout = $timeout(function() { + _dropHandler(droppedItemIndex); + }, 20); + }; + + var _dropHandler = function(droppedItemIndex) { + var theList = scope.$eval(attrs.uiSelectSort), + itemToMove = theList[droppedItemIndex], + newIndex = null; + + if (element.hasClass(droppingBeforeClassName)) { + if (droppedItemIndex < scope.$index) { + newIndex = scope.$index - 1; + } else { + newIndex = scope.$index; + } + } else { + if (droppedItemIndex < scope.$index) { + newIndex = scope.$index; + } else { + newIndex = scope.$index + 1; + } + } + + move.apply(theList, [droppedItemIndex, newIndex]); + + scope.$apply(function() { + scope.$emit('uiSelectSort:change', { + array: theList, + item: itemToMove, + from: droppedItemIndex, + to: newIndex + }); + }); + + element.removeClass(droppingClassName); + element.removeClass(droppingBeforeClassName); + element.removeClass(droppingAfterClassName); + + element.off('drop', dropHandler); + }; + + element.on('dragenter', function() { + if (element.hasClass(draggingClassName)) { + return; + } + + element.addClass(droppingClassName); + + element.on('dragover', dragOverHandler); + element.on('drop', dropHandler); + }); + + element.on('dragleave', function(e) { + if (e.target != element) { + return; + } + element.removeClass(droppingClassName); + element.removeClass(droppingBeforeClassName); + element.removeClass(droppingAfterClassName); + + element.off('dragover', dragOverHandler); + element.off('drop', dropHandler); + }); + } + }; +}]); + +/** + * Parses "repeat" attribute. + * + * Taken from AngularJS ngRepeat source code + * See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L211 + * + * Original discussion about parsing "repeat" attribute instead of fully relying on ng-repeat: + * https://github.com/angular-ui/ui-select/commit/5dd63ad#commitcomment-5504697 + */ + +uis.service('uisRepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinErr, $parse) { + var self = this; + + /** + * Example: + * expression = "address in addresses | filter: {street: $select.search} track by $index" + * itemName = "address", + * source = "addresses | filter: {street: $select.search}", + * trackByExp = "$index", + */ + self.parse = function(expression) { + + + var match; + var isObjectCollection = /\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)/.test(expression); + // If an array is used as collection + + // if (isObjectCollection){ + //00000000000000000000000000000111111111000000000000000222222222222220033333333333333333333330000444444444444444444000000000000000556666660000077777777777755000000000000000000000088888880000000 + match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(([\w\.]+)?\s*(|\s*[\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); + + // 1 Alias + // 2 Item + // 3 Key on (key,value) + // 4 Value on (key,value) + // 5 Collection expresion (only used when using an array collection) + // 6 Object that will be converted to Array when using (key,value) syntax + // 7 Filters that will be applied to #6 when using (key,value) syntax + // 8 Track by + + if (!match) { + throw uiSelectMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", + expression); + } + if (!match[6] && isObjectCollection) { + throw uiSelectMinErr('iexp', "Expected expression in form of '_item_ as (_key_, _item_) in _ObjCollection_ [ track by _id_]' but got '{0}'.", + expression); + } + + return { + itemName: match[4] || match[2], // (lhs) Left-hand side, + keyName: match[3], //for (key, value) syntax + source: $parse(!match[3] ? match[5] : match[6]), + sourceName: match[6], + filters: match[7], + trackByExp: match[8], + modelMapper: $parse(match[1] || match[4] || match[2]), + repeatExpression: function (grouped) { + var expression = this.itemName + ' in ' + (grouped ? '$group.items' : '$select.items'); + if (this.trackByExp) { + expression += ' track by ' + this.trackByExp; + } + return expression; + } + }; + + }; + + self.getGroupNgRepeatExpression = function() { + return '$group in $select.groups'; + }; + +}]); + +}()); +angular.module("ui.select").run(["$templateCache", function($templateCache) {$templateCache.put("bootstrap/choices.tpl.html","
      0\">
    • 0\">
    "); +$templateCache.put("bootstrap/match-multiple.tpl.html"," × "); +$templateCache.put("bootstrap/match.tpl.html","
    {{$select.placeholder}}
    "); +$templateCache.put("bootstrap/select-multiple.tpl.html","
    "); +$templateCache.put("bootstrap/select.tpl.html","
    "); +$templateCache.put("selectize/choices.tpl.html","
    "); +$templateCache.put("selectize/match.tpl.html","
    "); +$templateCache.put("selectize/select.tpl.html","
    "); +$templateCache.put("select2/choices.tpl.html","
    "); +$templateCache.put("select2/match-multiple.tpl.html","
  • "); +$templateCache.put("select2/match.tpl.html","{{$select.placeholder}} "); +$templateCache.put("select2/select-multiple.tpl.html","
    "); +$templateCache.put("select2/select.tpl.html","
    ");}]); \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-select/dist/select.min.css b/vendor/assets/components/angular-ui-select/dist/select.min.css new file mode 100644 index 000000000..799f6da34 --- /dev/null +++ b/vendor/assets/components/angular-ui-select/dist/select.min.css @@ -0,0 +1,6 @@ +/*! + * ui-select + * http://github.com/angular-ui/ui-select + * Version: 0.13.2 - 2015-10-09T15:34:24.045Z + * License: MIT + */.ui-select-highlight{font-weight:700}.ui-select-offscreen{clip:rect(0 0 0 0)!important;width:1px!important;height:1px!important;border:0!important;margin:0!important;padding:0!important;overflow:hidden!important;position:absolute!important;outline:0!important;left:0!important;top:0!important}.ui-select-choices-row:hover{background-color:#f5f5f5}.ng-dirty.ng-invalid>a.select2-choice{border-color:#D44950}.select2-result-single{padding-left:0}.select-locked>.ui-select-match-close,.select2-locked>.select2-search-choice-close{display:none}body>.select2-container.open{z-index:9999}.ui-select-container[theme=select2].direction-up .ui-select-match{border-radius:0 0 4px 4px}.ui-select-container[theme=select2].direction-up .ui-select-dropdown{border-radius:4px 4px 0 0;border-top-width:1px;border-top-style:solid;box-shadow:0 -4px 8px rgba(0,0,0,.25);margin-top:-4px}.ui-select-container[theme=select2].direction-up .ui-select-dropdown .select2-search{margin-top:4px}.ui-select-container[theme=select2].direction-up.select2-dropdown-open .ui-select-match{border-bottom-color:#5897fb}.selectize-input.selectize-focus{border-color:#007FBB!important}.selectize-control>.selectize-dropdown,.selectize-control>.selectize-input>input{width:100%}.ng-dirty.ng-invalid>div.selectize-input{border-color:#D44950}.ui-select-container[theme=selectize].direction-up .ui-select-dropdown{box-shadow:0 -4px 8px rgba(0,0,0,.25);margin-top:-2px}.btn-default-focus{color:#333;background-color:#EBEBEB;border-color:#ADADAD;text-decoration:none;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.ui-select-bootstrap .ui-select-toggle{position:relative}.ui-select-bootstrap .ui-select-toggle>.caret{position:absolute;height:10px;top:50%;right:10px;margin-top:-2px}.input-group>.ui-select-bootstrap.dropdown{position:static}.input-group>.ui-select-bootstrap>input.ui-select-search.form-control{border-radius:4px 0 0 4px}.input-group>.ui-select-bootstrap>input.ui-select-search.form-control.direction-up{border-radius:4px 0 0 4px!important}.ui-select-bootstrap>.ui-select-match>.btn{text-align:left!important}.ui-select-bootstrap>.ui-select-match>.caret{position:absolute;top:45%;right:15px}.ui-select-bootstrap>.ui-select-choices{width:100%;height:auto;max-height:200px;overflow-x:hidden;margin-top:-1px}body>.ui-select-bootstrap.open{z-index:1000}.ui-select-multiple.ui-select-bootstrap{height:auto;padding:3px 3px 0}.ui-select-multiple.ui-select-bootstrap input.ui-select-search{background-color:transparent!important;border:none;outline:0;height:1.666666em;margin-bottom:3px}.ui-select-multiple.ui-select-bootstrap .ui-select-match .close{font-size:1.6em;line-height:.75}.ui-select-multiple.ui-select-bootstrap .ui-select-match-item{outline:0;margin:0 3px 3px 0}.ui-select-multiple .ui-select-match-item{position:relative}.ui-select-multiple .ui-select-match-item.dropping-before:before{content:"";position:absolute;top:0;right:100%;height:100%;margin-right:2px;border-left:1px solid #428bca}.ui-select-multiple .ui-select-match-item.dropping-after:after{content:"";position:absolute;top:0;left:100%;height:100%;margin-left:2px;border-right:1px solid #428bca}.ui-select-bootstrap .ui-select-choices-row>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.ui-select-bootstrap .ui-select-choices-row>a:focus,.ui-select-bootstrap .ui-select-choices-row>a:hover{text-decoration:none;color:#262626;background-color:#f5f5f5}.ui-select-bootstrap .ui-select-choices-row.active>a{color:#fff;text-decoration:none;outline:0;background-color:#428bca}.ui-select-bootstrap .ui-select-choices-row.active.disabled>a,.ui-select-bootstrap .ui-select-choices-row.disabled>a{color:#777;cursor:not-allowed;background-color:#fff}.ui-select-match.ng-hide-add,.ui-select-search.ng-hide-add{display:none!important}.ui-select-bootstrap.ng-dirty.ng-invalid>button.btn.ui-select-match{border-color:#D44950}.ui-select-container[theme=bootstrap].direction-up .ui-select-dropdown{box-shadow:0 -4px 8px rgba(0,0,0,.25)} \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-select/dist/select.min.js b/vendor/assets/components/angular-ui-select/dist/select.min.js new file mode 100644 index 000000000..f880cab1e --- /dev/null +++ b/vendor/assets/components/angular-ui-select/dist/select.min.js @@ -0,0 +1,8 @@ +/*! + * ui-select + * http://github.com/angular-ui/ui-select + * Version: 0.13.2 - 2015-10-09T15:34:24.040Z + * License: MIT + */ +!function(){"use strict";var e={TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,SHIFT:16,CTRL:17,ALT:18,PAGE_UP:33,PAGE_DOWN:34,HOME:36,END:35,BACKSPACE:8,DELETE:46,COMMAND:91,MAP:{91:"COMMAND",8:"BACKSPACE",9:"TAB",13:"ENTER",16:"SHIFT",17:"CTRL",18:"ALT",19:"PAUSEBREAK",20:"CAPSLOCK",27:"ESC",32:"SPACE",33:"PAGE_UP",34:"PAGE_DOWN",35:"END",36:"HOME",37:"LEFT",38:"UP",39:"RIGHT",40:"DOWN",43:"+",44:"PRINTSCREEN",45:"INSERT",46:"DELETE",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",61:"=",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NUMLOCK",145:"SCROLLLOCK",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},isControl:function(t){var i=t.which;switch(i){case e.COMMAND:case e.SHIFT:case e.CTRL:case e.ALT:return!0}return t.metaKey?!0:!1},isFunctionKey:function(e){return e=e.which?e.which:e,e>=112&&123>=e},isVerticalMovement:function(t){return~[e.UP,e.DOWN].indexOf(t)},isHorizontalMovement:function(t){return~[e.LEFT,e.RIGHT,e.BACKSPACE,e.DELETE].indexOf(t)}};void 0===angular.element.prototype.querySelectorAll&&(angular.element.prototype.querySelectorAll=function(e){return angular.element(this[0].querySelectorAll(e))}),void 0===angular.element.prototype.closest&&(angular.element.prototype.closest=function(e){for(var t=this[0],i=t.matches||t.webkitMatchesSelector||t.mozMatchesSelector||t.msMatchesSelector;t;){if(i.bind(t)(e))return t;t=t.parentElement}return!1});var t=0,i=angular.module("ui.select",[]).constant("uiSelectConfig",{theme:"bootstrap",searchEnabled:!0,sortable:!1,placeholder:"",refreshDelay:1e3,closeOnSelect:!0,dropdownPosition:"auto",generateId:function(){return t++},appendToBody:!1}).service("uiSelectMinErr",function(){var e=angular.$$minErr("ui.select");return function(){var t=e.apply(this,arguments),i=t.message.replace(new RegExp("\nhttp://errors.angularjs.org/.*"),"");return new Error(i)}}).directive("uisTranscludeAppend",function(){return{link:function(e,t,i,s,c){c(e,function(e){t.append(e)})}}}).filter("highlight",function(){function e(e){return e.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(t,i){return i&&t?t.replace(new RegExp(e(i),"gi"),'$&'):t}}).factory("uisOffset",["$document","$window",function(e,t){return function(i){var s=i[0].getBoundingClientRect();return{width:s.width||i.prop("offsetWidth"),height:s.height||i.prop("offsetHeight"),top:s.top+(t.pageYOffset||e[0].documentElement.scrollTop),left:s.left+(t.pageXOffset||e[0].documentElement.scrollLeft)}}}]);i.directive("uiSelectChoices",["uiSelectConfig","uisRepeatParser","uiSelectMinErr","$compile",function(e,t,i,s){return{restrict:"EA",require:"^uiSelect",replace:!0,transclude:!0,templateUrl:function(t){var i=t.parent().attr("theme")||e.theme;return i+"/choices.tpl.html"},compile:function(c,l){if(!l.repeat)throw i("repeat","Expected 'repeat' expression.");return function(c,l,n,a,r){var o=n.groupBy,u=n.groupFilter;if(a.parseRepeatAttr(n.repeat,o,u),a.disableChoiceExpression=n.uiDisableChoice,a.onHighlightCallback=n.onHighlight,a.dropdownPosition=n.position?n.position.toLowerCase():e.dropdownPosition,o){var d=l.querySelectorAll(".ui-select-choices-group");if(1!==d.length)throw i("rows","Expected 1 .ui-select-choices-group but got '{0}'.",d.length);d.attr("ng-repeat",t.getGroupNgRepeatExpression())}var p=l.querySelectorAll(".ui-select-choices-row");if(1!==p.length)throw i("rows","Expected 1 .ui-select-choices-row but got '{0}'.",p.length);p.attr("ng-repeat",a.parserResult.repeatExpression(o)).attr("ng-if","$select.open").attr("ng-click","$select.select("+a.parserResult.itemName+",false,$event)");var h=l.querySelectorAll(".ui-select-choices-row-inner");if(1!==h.length)throw i("rows","Expected 1 .ui-select-choices-row-inner but got '{0}'.",h.length);h.attr("uis-transclude-append",""),s(l,r)(c),c.$watch("$select.search",function(e){e&&!a.open&&a.multiple&&a.activate(!1,!0),a.activeIndex=a.tagging.isActivated?-1:0,a.refresh(n.refresh)}),n.$observe("refreshDelay",function(){var t=c.$eval(n.refreshDelay);a.refreshDelay=void 0!==t?t:e.refreshDelay})}}}}]),i.controller("uiSelectCtrl",["$scope","$element","$timeout","$filter","uisRepeatParser","uiSelectMinErr","uiSelectConfig","$parse",function(t,i,s,c,l,n,a,r){function o(){(h.resetSearchInput||void 0===h.resetSearchInput&&a.resetSearchInput)&&(h.search=g,h.selected&&h.items.length&&!h.multiple&&(h.activeIndex=h.items.indexOf(h.selected)))}function u(e,t){var i,s,c=[];for(i=0;i0||0===h.search.length&&h.tagging.isActivated&&h.activeIndex>-1)&&h.activeIndex--;break;case e.TAB:(!h.multiple||h.open)&&h.select(h.items[h.activeIndex],!0);break;case e.ENTER:h.open&&(h.tagging.isActivated||h.activeIndex>=0)?h.select(h.items[h.activeIndex]):h.activate(!1,!0);break;case e.ESC:h.close();break;default:i=!1}return i}function p(){var e=i.querySelectorAll(".ui-select-choices-content"),t=e.querySelectorAll(".ui-select-choices-row");if(t.length<1)throw n("choices","Expected multiple .ui-select-choices-row but got '{0}'.",t.length);if(!(h.activeIndex<0)){var s=t[h.activeIndex],c=s.offsetTop+s.clientHeight-e[0].scrollTop,l=e[0].offsetHeight;c>l?e[0].scrollTop+=c-l:c=h.items.length?0:h.activeIndex,-1===h.activeIndex&&h.taggingLabel!==!1&&(h.activeIndex=0),s(function(){h.search=e||h.search,h.searchInput[0].focus(),!h.tagging.isActivated&&h.items.length>1&&p()}))},h.findGroupByName=function(e){return h.groups&&h.groups.filter(function(t){return t.name===e})[0]},h.parseRepeatAttr=function(e,i,s){function c(e){var c=t.$eval(i);if(h.groups=[],angular.forEach(e,function(e){var t=angular.isFunction(c)?c(e):e[c],i=h.findGroupByName(t);i?i.items.push(e):h.groups.push({name:t,items:[e]})}),s){var l=t.$eval(s);angular.isFunction(l)?h.groups=l(h.groups):angular.isArray(l)&&(h.groups=u(h.groups,l))}h.items=[],h.groups.forEach(function(e){h.items=h.items.concat(e.items)})}function a(e){h.items=e}h.setItemsFn=i?c:a,h.parserResult=l.parse(e),h.isGrouped=!!i,h.itemProperty=h.parserResult.itemName;var o=h.parserResult.source,d=function(){var e=o(t);t.$uisSource=Object.keys(e).map(function(t){var i={};return i[h.parserResult.keyName]=t,i.value=e[t],i})};h.parserResult.keyName&&(d(),h.parserResult.source=r("$uisSource"+h.parserResult.filters),t.$watch(o,function(e,t){e!==t&&d()},!0)),h.refreshItems=function(e){e=e||h.parserResult.source(t);var i=h.selected;if(h.isEmpty()||angular.isArray(i)&&!i.length||!h.removeSelected)h.setItemsFn(e);else if(void 0!==e){var s=e.filter(function(e){return i&&i.indexOf(e)<0});h.setItemsFn(s)}("auto"===h.dropdownPosition||"up"===h.dropdownPosition)&&t.calculateDropdownPos()},t.$watchCollection(h.parserResult.source,function(e){if(void 0===e||null===e)h.items=[];else{if(!angular.isArray(e))throw n("items","Expected an array but got '{0}'.",e);h.refreshItems(e),h.ngModel.$modelValue=null}})};var f;h.refresh=function(e){void 0!==e&&(f&&s.cancel(f),f=s(function(){t.$eval(e)},h.refreshDelay))},h.isActive=function(e){if(!h.open)return!1;var t=h.items.indexOf(e[h.itemProperty]),i=t===h.activeIndex;return!i||0>t&&h.taggingLabel!==!1||0>t&&h.taggingLabel===!1?!1:(i&&!angular.isUndefined(h.onHighlightCallback)&&e.$eval(h.onHighlightCallback),i)},h.isDisabled=function(e){if(h.open){var t,i=h.items.indexOf(e[h.itemProperty]),s=!1;return i>=0&&!angular.isUndefined(h.disableChoiceExpression)&&(t=h.items[i],s=!!e.$eval(h.disableChoiceExpression),t._uiSelectChoiceDisabled=s),s}},h.select=function(e,i,c){if(void 0===e||!e._uiSelectChoiceDisabled){if(!h.items&&!h.search)return;if(!e||!e._uiSelectChoiceDisabled){if(h.tagging.isActivated){if(h.taggingLabel===!1)if(h.activeIndex<0){if(e=void 0!==h.tagging.fct?h.tagging.fct(h.search):h.search,!e||angular.equals(h.items[0],e))return}else e=h.items[h.activeIndex];else if(0===h.activeIndex){if(void 0===e)return;if(void 0!==h.tagging.fct&&"string"==typeof e){if(e=h.tagging.fct(h.search),!e)return}else"string"==typeof e&&(e=e.replace(h.taggingLabel,"").trim())}if(h.selected&&angular.isArray(h.selected)&&h.selected.filter(function(t){return angular.equals(t,e)}).length>0)return h.close(i),void 0}t.$broadcast("uis:select",e);var l={};l[h.parserResult.itemName]=e,s(function(){h.onSelectCallback(t,{$item:e,$model:h.parserResult.modelMapper(t,l)})}),h.closeOnSelect&&h.close(i),c&&"click"===c.type&&(h.clickTriggeredSelect=!0)}}},h.close=function(e){h.open&&(h.ngModel&&h.ngModel.$setTouched&&h.ngModel.$setTouched(),o(),h.open=!1,t.$broadcast("uis:close",e))},h.setFocus=function(){h.focus||h.focusInput[0].focus()},h.clear=function(e){h.select(void 0),e.stopPropagation(),s(function(){h.focusser[0].focus()},0,!1)},h.toggle=function(e){h.open?(h.close(),e.preventDefault(),e.stopPropagation()):h.activate()},h.isLocked=function(e,t){var i,s=h.selected[t];return s&&!angular.isUndefined(h.lockChoiceExpression)&&(i=!!e.$eval(h.lockChoiceExpression),s._uiSelectChoiceLocked=i),i};var v=null;h.sizeSearchInput=function(){var e=h.searchInput[0],i=h.searchInput.parent().parent()[0],c=function(){return i.clientWidth*!!e.offsetParent},l=function(t){if(0===t)return!1;var i=t-e.offsetLeft-10;return 50>i&&(i=t),h.searchInput.css("width",i+"px"),!0};h.searchInput.css("width","10px"),s(function(){null!==v||l(c())||(v=t.$watch(c,function(e){l(e)&&(v(),v=null)}))})},h.searchInput.on("keydown",function(i){var c=i.which;t.$apply(function(){var t=!1;if((h.items.length>0||h.tagging.isActivated)&&(d(c),h.taggingTokens.isActivated)){for(var l=0;l0&&(t=!0);t&&s(function(){h.searchInput.triggerHandler("tagged");var t=h.search.replace(e.MAP[i.keyCode],"").trim();h.tagging.fct&&(t=h.tagging.fct(t)),t&&h.select(t,!0)})}}),e.isVerticalMovement(c)&&h.items.length>0&&p(),(c===e.ENTER||c===e.ESC)&&(i.preventDefault(),i.stopPropagation())}),h.searchInput.on("paste",function(e){var t=e.originalEvent.clipboardData.getData("text/plain");if(t&&t.length>0&&h.taggingTokens.isActivated&&h.tagging.fct){var i=t.split(h.taggingTokens.tokens[0]);i&&i.length>0&&(angular.forEach(i,function(e){var t=h.tagging.fct(e);t&&h.select(t,!0)}),e.preventDefault(),e.stopPropagation())}}),h.searchInput.on("tagged",function(){s(function(){o()})}),t.$on("$destroy",function(){h.searchInput.off("keyup keydown tagged blur paste")})}]),i.directive("uiSelect",["$document","uiSelectConfig","uiSelectMinErr","uisOffset","$compile","$parse","$timeout",function(e,t,i,s,c,l,n){return{restrict:"EA",templateUrl:function(e,i){var s=i.theme||t.theme;return s+(angular.isDefined(i.multiple)?"/select-multiple.tpl.html":"/select.tpl.html")},replace:!0,transclude:!0,require:["uiSelect","^ngModel"],scope:!0,controller:"uiSelectCtrl",controllerAs:"$select",compile:function(c,a){return angular.isDefined(a.multiple)?c.append("").removeAttr("multiple"):c.append(""),a.inputId&&(c.querySelectorAll("input.ui-select-search")[0].id=a.inputId),function(c,a,r,o,u){function d(e){if(g.open){var t=!1;if(t=window.jQuery?window.jQuery.contains(a[0],e.target):a[0].contains(e.target),!t&&!g.clickTriggeredSelect){var i=["input","button","textarea"],s=angular.element(e.target).controller("uiSelect"),l=s&&s!==g;l||(l=~i.indexOf(e.target.tagName.toLowerCase())),g.close(l),c.$digest()}g.clickTriggeredSelect=!1}}function p(){var t=s(a);m=angular.element('
    '),m[0].style.width=t.width+"px",m[0].style.height=t.height+"px",a.after(m),$=a[0].style.width,e.find("body").append(a),a[0].style.position="absolute",a[0].style.left=t.left+"px",a[0].style.top=t.top+"px",a[0].style.width=t.width+"px"}function h(){null!==m&&(m.replaceWith(a),m=null,a[0].style.position="",a[0].style.left="",a[0].style.top="",a[0].style.width=$)}var g=o[0],f=o[1];g.generatedId=t.generateId(),g.baseTitle=r.title||"Select box",g.focusserTitle=g.baseTitle+" focus",g.focusserId="focusser-"+g.generatedId,g.closeOnSelect=function(){return angular.isDefined(r.closeOnSelect)?l(r.closeOnSelect)():t.closeOnSelect}(),g.onSelectCallback=l(r.onSelect),g.onRemoveCallback=l(r.onRemove),g.limit=angular.isDefined(r.limit)?parseInt(r.limit,10):void 0,g.ngModel=f,g.choiceGrouped=function(e){return g.isGrouped&&e&&e.name},r.tabindex&&r.$observe("tabindex",function(e){g.focusInput.attr("tabindex",e),a.removeAttr("tabindex")}),c.$watch("searchEnabled",function(){var e=c.$eval(r.searchEnabled);g.searchEnabled=void 0!==e?e:t.searchEnabled}),c.$watch("sortable",function(){var e=c.$eval(r.sortable);g.sortable=void 0!==e?e:t.sortable}),r.$observe("disabled",function(){g.disabled=void 0!==r.disabled?r.disabled:!1}),r.$observe("resetSearchInput",function(){var e=c.$eval(r.resetSearchInput);g.resetSearchInput=void 0!==e?e:!0}),r.$observe("tagging",function(){if(void 0!==r.tagging){var e=c.$eval(r.tagging);g.tagging={isActivated:!0,fct:e!==!0?e:void 0}}else g.tagging={isActivated:!1,fct:void 0}}),r.$observe("taggingLabel",function(){void 0!==r.tagging&&(g.taggingLabel="false"===r.taggingLabel?!1:void 0!==r.taggingLabel?r.taggingLabel:"(new)")}),r.$observe("taggingTokens",function(){if(void 0!==r.tagging){var e=void 0!==r.taggingTokens?r.taggingTokens.split("|"):[",","ENTER"];g.taggingTokens={isActivated:!0,tokens:e}}}),angular.isDefined(r.autofocus)&&n(function(){g.setFocus()}),angular.isDefined(r.focusOn)&&c.$on(r.focusOn,function(){n(function(){g.setFocus()})}),e.on("click",d),c.$on("$destroy",function(){e.off("click",d)}),u(c,function(e){var t=angular.element("
    ").append(e),s=t.querySelectorAll(".ui-select-match");if(s.removeAttr("ui-select-match"),s.removeAttr("data-ui-select-match"),1!==s.length)throw i("transcluded","Expected 1 .ui-select-match but got '{0}'.",s.length);a.querySelectorAll(".ui-select-match").replaceWith(s);var c=t.querySelectorAll(".ui-select-choices");if(c.removeAttr("ui-select-choices"),c.removeAttr("data-ui-select-choices"),1!==c.length)throw i("transcluded","Expected 1 .ui-select-choices but got '{0}'.",c.length);a.querySelectorAll(".ui-select-choices").replaceWith(c)});var v=c.$eval(r.appendToBody);(void 0!==v?v:t.appendToBody)&&(c.$watch("$select.open",function(e){e?p():h()}),c.$on("$destroy",function(){h()}));var m=null,$="",b=null,w="direction-up";c.$watch("$select.open",function(){("auto"===g.dropdownPosition||"up"===g.dropdownPosition)&&c.calculateDropdownPos()});var x=function(e,t){e=e||s(a),t=t||s(b),b[0].style.position="absolute",b[0].style.top=-1*t.height+"px",a.addClass(w)},y=function(e,t){a.removeClass(w),e=e||s(a),t=t||s(b),b[0].style.position="",b[0].style.top=""};c.calculateDropdownPos=function(){if(g.open){if(b=angular.element(a).querySelectorAll(".ui-select-dropdown"),0===b.length)return;b[0].style.opacity=0,n(function(){if("up"===g.dropdownPosition)x(t,i);else{a.removeClass(w);var t=s(a),i=s(b),c=e[0].documentElement.scrollTop||e[0].body.scrollTop;t.top+t.height+i.height>c+e[0].documentElement.clientHeight?x(t,i):y(t,i)}b[0].style.opacity=1})}else{if(null===b||0===b.length)return;b[0].style.position="",b[0].style.top="",a.removeClass(w)}}}}}}]),i.directive("uiSelectMatch",["uiSelectConfig",function(e){return{restrict:"EA",require:"^uiSelect",replace:!0,transclude:!0,templateUrl:function(t){var i=t.parent().attr("theme")||e.theme,s=t.parent().attr("multiple");return i+(s?"/match-multiple.tpl.html":"/match.tpl.html")},link:function(t,i,s,c){function l(e){c.allowClear=angular.isDefined(e)?""===e?!0:"true"===e.toLowerCase():!1}c.lockChoiceExpression=s.uiLockChoice,s.$observe("placeholder",function(t){c.placeholder=void 0!==t?t:e.placeholder}),s.$observe("allowClear",l),l(s.allowClear),c.multiple&&c.sizeSearchInput()}}}]),i.directive("uiSelectMultiple",["uiSelectMinErr","$timeout",function(t,i){return{restrict:"EA",require:["^uiSelect","^ngModel"],controller:["$scope","$timeout",function(e,t){var i,s=this,c=e.$select;e.$evalAsync(function(){i=e.ngModel}),s.activeMatchIndex=-1,s.updateModel=function(){i.$setViewValue(Date.now()),s.refreshComponent()},s.refreshComponent=function(){c.refreshItems(),c.sizeSearchInput()},s.removeChoice=function(i){var l=c.selected[i];if(!l._uiSelectChoiceLocked){var n={};n[c.parserResult.itemName]=l,c.selected.splice(i,1),s.activeMatchIndex=-1,c.sizeSearchInput(),t(function(){c.onRemoveCallback(e,{$item:l,$model:c.parserResult.modelMapper(e,n)})}),s.updateModel()}},s.getPlaceholder=function(){return c.selected&&c.selected.length?void 0:c.placeholder}}],controllerAs:"$selectMultiple",link:function(s,c,l,n){function a(e){return angular.isNumber(e.selectionStart)?e.selectionStart:e.value.length}function r(t){function i(){switch(t){case e.LEFT:return~h.activeMatchIndex?u:n;case e.RIGHT:return~h.activeMatchIndex&&r!==n?o:(d.activate(),!1);case e.BACKSPACE:return~h.activeMatchIndex?(h.removeChoice(r),u):n;case e.DELETE:return~h.activeMatchIndex?(h.removeChoice(h.activeMatchIndex),r):!1}}var s=a(d.searchInput[0]),c=d.selected.length,l=0,n=c-1,r=h.activeMatchIndex,o=h.activeMatchIndex+1,u=h.activeMatchIndex-1,p=r;return s>0||d.search.length&&t==e.RIGHT?!1:(d.close(),p=i(),h.activeMatchIndex=d.selected.length&&p!==!1?Math.min(n,Math.max(l,p)):-1,!0)}function o(e){if(void 0===e||void 0===d.search)return!1;var t=e.filter(function(e){return void 0===d.search.toUpperCase()||void 0===e?!1:e.toUpperCase()===d.search.toUpperCase()}).length>0;return t}function u(e,t){var i=-1;if(angular.isArray(e))for(var s=angular.copy(e),c=0;c=0;c--)t={},t[d.parserResult.itemName]=d.selected[c],e=d.parserResult.modelMapper(s,t),i.unshift(e);return i}),p.$formatters.unshift(function(e){var t,i=d.parserResult.source(s,{$select:{search:""}}),c={};if(!i)return e;var l=[],n=function(e,i){if(e&&e.length){for(var n=e.length-1;n>=0;n--){if(c[d.parserResult.itemName]=e[n],t=d.parserResult.modelMapper(s,c),d.parserResult.trackByExp){var a=/\.(.+)/.exec(d.parserResult.trackByExp);if(a.length>0&&t[a[1]]==i[a[1]])return l.unshift(e[n]),!0}if(angular.equals(t,i))return l.unshift(e[n]),!0}return!1}};if(!e)return l;for(var a=e.length-1;a>=0;a--)n(d.selected,e[a])||n(i,e[a])||l.unshift(e[a]);return l}),s.$watchCollection(function(){return p.$modelValue},function(e,t){t!=e&&(p.$modelValue=null,h.refreshComponent())}),p.$render=function(){if(!angular.isArray(p.$viewValue)){if(!angular.isUndefined(p.$viewValue)&&null!==p.$viewValue)throw t("multiarr","Expected model value to be array but got '{0}'",p.$viewValue);d.selected=[]}d.selected=p.$viewValue,s.$evalAsync()},s.$on("uis:select",function(e,t){d.selected.length>=d.limit||(d.selected.push(t),h.updateModel())}),s.$on("uis:activate",function(){h.activeMatchIndex=-1}),s.$watch("$select.disabled",function(e,t){t&&!e&&d.sizeSearchInput()}),d.searchInput.on("keydown",function(t){var i=t.which;s.$apply(function(){var s=!1;e.isHorizontalMovement(i)&&(s=r(i)),s&&i!=e.TAB&&(t.preventDefault(),t.stopPropagation())})}),d.searchInput.on("keyup",function(t){if(e.isVerticalMovement(t.which)||s.$evalAsync(function(){d.activeIndex=d.taggingLabel===!1?-1:0}),d.tagging.isActivated&&d.search.length>0){if(t.which===e.TAB||e.isControl(t)||e.isFunctionKey(t)||t.which===e.ESC||e.isVerticalMovement(t.which))return;if(d.activeIndex=d.taggingLabel===!1?-1:0,d.taggingLabel===!1)return;var i,c,l,n,a=angular.copy(d.items),r=angular.copy(d.items),p=!1,h=-1;if(void 0!==d.tagging.fct){if(l=d.$filter("filter")(a,{isTag:!0}),l.length>0&&(n=l[0]),a.length>0&&n&&(p=!0,a=a.slice(1,a.length),r=r.slice(1,r.length)),i=d.tagging.fct(d.search),i.isTag=!0,r.filter(function(e){return angular.equals(e,d.tagging.fct(d.search))}).length>0)return;i.isTag=!0}else{if(l=d.$filter("filter")(a,function(e){return e.match(d.taggingLabel)}),l.length>0&&(n=l[0]),c=a[0],void 0!==c&&a.length>0&&n&&(p=!0,a=a.slice(1,a.length),r=r.slice(1,r.length)),i=d.search+" "+d.taggingLabel,u(d.selected,d.search)>-1)return;if(o(r.concat(d.selected)))return p&&(a=r,s.$evalAsync(function(){d.activeIndex=0,d.items=a})),void 0;if(o(r))return p&&(d.items=r.slice(1,r.length)),void 0}p&&(h=u(d.selected,i)),h>-1?a=a.slice(h+1,a.length-1):(a=[],a.push(i),a=a.concat(r)),s.$evalAsync(function(){d.activeIndex=0,d.items=a})}}),d.searchInput.on("blur",function(){i(function(){h.activeMatchIndex=-1})})}}}]),i.directive("uiSelectSingle",["$timeout","$compile",function(t,i){return{restrict:"EA",require:["^uiSelect","^ngModel"],link:function(s,c,l,n){var a=n[0],r=n[1];r.$parsers.unshift(function(e){var t,i={};return i[a.parserResult.itemName]=e,t=a.parserResult.modelMapper(s,i)}),r.$formatters.unshift(function(e){var t,i=a.parserResult.source(s,{$select:{search:""}}),c={};if(i){var l=function(i){return c[a.parserResult.itemName]=i,t=a.parserResult.modelMapper(s,c),t==e};if(a.selected&&l(a.selected))return a.selected;for(var n=i.length-1;n>=0;n--)if(l(i[n]))return i[n]}return e}),s.$watch("$select.selected",function(e){r.$viewValue!==e&&r.$setViewValue(e)}),r.$render=function(){a.selected=r.$viewValue},s.$on("uis:select",function(e,t){a.selected=t}),s.$on("uis:close",function(e,i){t(function(){a.focusser.prop("disabled",!1),i||a.focusser[0].focus()},0,!1)}),s.$on("uis:activate",function(){o.prop("disabled",!0)});var o=angular.element("");i(o)(s),a.focusser=o,a.focusInput=o,c.parent().append(o),o.bind("focus",function(){s.$evalAsync(function(){a.focus=!0})}),o.bind("blur",function(){s.$evalAsync(function(){a.focus=!1})}),o.bind("keydown",function(t){return t.which===e.BACKSPACE?(t.preventDefault(),t.stopPropagation(),a.select(void 0),s.$apply(),void 0):(t.which===e.TAB||e.isControl(t)||e.isFunctionKey(t)||t.which===e.ESC||((t.which==e.DOWN||t.which==e.UP||t.which==e.ENTER||t.which==e.SPACE)&&(t.preventDefault(),t.stopPropagation(),a.activate()),s.$digest()),void 0)}),o.bind("keyup input",function(t){t.which===e.TAB||e.isControl(t)||e.isFunctionKey(t)||t.which===e.ESC||t.which==e.ENTER||t.which===e.BACKSPACE||(a.activate(o.val()),o.val(""),s.$digest())})}}}]),i.directive("uiSelectSort",["$timeout","uiSelectConfig","uiSelectMinErr",function(e,t,i){return{require:"^uiSelect",link:function(t,s,c,l){if(null===t[c.uiSelectSort])throw i("sort","Expected a list to sort");var n=angular.extend({axis:"horizontal"},t.$eval(c.uiSelectSortOptions)),a=n.axis,r="dragging",o="dropping",u="dropping-before",d="dropping-after";t.$watch(function(){return l.sortable},function(e){e?s.attr("draggable",!0):s.removeAttr("draggable")}),s.on("dragstart",function(e){s.addClass(r),(e.dataTransfer||e.originalEvent.dataTransfer).setData("text/plain",t.$index)}),s.on("dragend",function(){s.removeClass(r)});var p,h=function(e,t){this.splice(t,0,this.splice(e,1)[0])},g=function(e){e.preventDefault();var t="vertical"===a?e.offsetY||e.layerY||(e.originalEvent?e.originalEvent.offsetY:0):e.offsetX||e.layerX||(e.originalEvent?e.originalEvent.offsetX:0);t
  • '),e.put("bootstrap/match-multiple.tpl.html",' × '),e.put("bootstrap/match.tpl.html",'
    {{$select.placeholder}}
    '),e.put("bootstrap/select-multiple.tpl.html",''),e.put("bootstrap/select.tpl.html",''),e.put("selectize/choices.tpl.html",'
    '),e.put("selectize/match.tpl.html",'
    '),e.put("selectize/select.tpl.html",'
    '),e.put("select2/choices.tpl.html",'
    '),e.put("select2/match-multiple.tpl.html",'
  • '),e.put("select2/match.tpl.html",'{{$select.placeholder}} '),e.put("select2/select-multiple.tpl.html",'
    '),e.put("select2/select.tpl.html",'
    ') +}]); \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-select/package.json b/vendor/assets/components/angular-ui-select/package.json new file mode 100644 index 000000000..9782528f2 --- /dev/null +++ b/vendor/assets/components/angular-ui-select/package.json @@ -0,0 +1,39 @@ +{ + "name": "ui-select", + "main": "dist/select.js", + "author": "http://github.com/angular-ui/ui-select/graphs/contributors", + "homepage": "http://github.com/angular-ui/ui-select", + "repository": { + "url": "git://github.com/angular-ui/ui-select.git" + }, + "version": "0.13.2", + "devDependencies": { + "bower": "~1.3", + "del": "~0.1.1", + "event-stream": "~3.1.0", + "gulp": "~3.8.5", + "gulp-angular-templatecache": "~1.2.1", + "gulp-concat": "~2.1.7", + "gulp-header": "~1.0.2", + "gulp-footer": "~1.0.5", + "gulp-jshint": "1.6.4", + "gulp-minify-css": "~0.3.6", + "gulp-minify-html": "~0.1.0", + "gulp-plumber": "^0.6.3", + "gulp-rename": "~0.2.2", + "gulp-uglify": "~0.3.1", + "gulp-util": "^2.2.19", + "jshint-stylish": "~0.3.0", + "karma": "^0.12.16", + "karma-chrome-launcher": "^0.1.3", + "karma-firefox-launcher": "~0.1", + "karma-jasmine": "~0.2", + "karma-ng-html2js-preprocessor": "^0.1.0", + "karma-phantomjs-launcher": "~0.1.4", + "karma-coverage": "~0.2" + }, + "scripts": { + "test": "gulp test" + }, + "license": "MIT" +} diff --git a/vendor/assets/components/angular-ui-select2/.bower.json b/vendor/assets/components/angular-ui-select2/.bower.json deleted file mode 100644 index 570a2967d..000000000 --- a/vendor/assets/components/angular-ui-select2/.bower.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "author": "AngularUI", - "name": "angular-ui-select2", - "version": "0.0.5", - "homepage": "http://angular-ui.github.com", - "keywords": [ - "angular", - "angularui", - "select2" - ], - "main": "./src/select2.js", - "dependencies": { - "angular": ">=1.2.0", - "select2": "~3.4", - "jquery": ">=1.6.4" - }, - "devDependencies": { - "angular-mocks": ">=1.0.2" - }, - "_release": "0.0.5", - "_resolution": { - "type": "version", - "tag": "v0.0.5", - "commit": "680bf4abb3dba0835f0d5002fe111e3396ae617e" - }, - "_source": "git://github.com/angular-ui/ui-select2.git", - "_target": ">=0.0.5", - "_originalSource": "angular-ui-select2" -} \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-select2/.bowerrc b/vendor/assets/components/angular-ui-select2/.bowerrc deleted file mode 100644 index deceb62e1..000000000 --- a/vendor/assets/components/angular-ui-select2/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "bower_components" -} \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-select2/.jshintrc b/vendor/assets/components/angular-ui-select2/.jshintrc deleted file mode 100644 index 1b83bfb44..000000000 --- a/vendor/assets/components/angular-ui-select2/.jshintrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "curly": true, - "eqeqeq": true, - "immed": true, - "latedef": true, - "newcap": true, - "noarg": true, - "sub": true, - "boss": true, - "eqnull": true -} diff --git a/vendor/assets/components/angular-ui-select2/.travis.yml b/vendor/assets/components/angular-ui-select2/.travis.yml deleted file mode 100644 index 7865159e2..000000000 --- a/vendor/assets/components/angular-ui-select2/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ - language: node_js - node_js: - - "0.8" - - "0.10" - - before_install: - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start - - npm install -g karma bower grunt-cli - - bower install - - npm install - - script: "grunt" diff --git a/vendor/assets/components/angular-ui-select2/CONTRIBUTING.md b/vendor/assets/components/angular-ui-select2/CONTRIBUTING.md deleted file mode 100644 index cb66f02b0..000000000 --- a/vendor/assets/components/angular-ui-select2/CONTRIBUTING.md +++ /dev/null @@ -1,8 +0,0 @@ -CONTRIBUTING -============ - -* Open a [Pull Request (PR)](https://github.com/angular-ui/ui-select2/pull/new/master) -* Make sure your PR is on a **new branch** you created off of the latest version of master -* Do **not** open a PR from your master branch -* Open a PR to start a discussion even if the code isn't finished (easier to collect feedback this way) -* Make sure all previous tests pass and add new tests for added behaviors diff --git a/vendor/assets/components/angular-ui-select2/Gruntfile.js b/vendor/assets/components/angular-ui-select2/Gruntfile.js deleted file mode 100644 index ea8dc33c6..000000000 --- a/vendor/assets/components/angular-ui-select2/Gruntfile.js +++ /dev/null @@ -1,56 +0,0 @@ -module.exports = function (grunt) { - 'use strict'; - - var initConfig; - - // Loading external tasks - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-karma'); - - // Project configuration. - initConfig = { - bower: 'bower_components', - pkg: grunt.file.readJSON('package.json'), - watch: { - test: { - // Lint & run unit tests in Karma - // Just running `$ grunt watch` will only lint your code; to run tests - // on watch, use `$ grunt watch:karma` to start a Karma server first - files: ['src/select2.js', 'test/select2Spec.js'], - tasks: ['jshint', 'karma:unit:run'] - } - }, - karma: { - options: { - configFile: 'test/karma.conf.js', - browsers: ['Firefox', 'PhantomJS'] - }, - unit: { - singleRun: true - }, - watch: { - autoWatch: true - }, - server: { - background: true - } - }, - jshint: { - all:[ - 'gruntFile.js', - 'src/**/*.js', - 'test/**/*Spec.js' - ], - options: { - jshintrc: '.jshintrc' - } - }, - }; - - // Register tasks - grunt.registerTask('default', ['jshint', 'karma:unit']); - grunt.registerTask('watch', ['jshint', 'karma:watch']); - - grunt.initConfig(initConfig); -}; diff --git a/vendor/assets/components/angular-ui-select2/README.md b/vendor/assets/components/angular-ui-select2/README.md deleted file mode 100644 index feb362608..000000000 --- a/vendor/assets/components/angular-ui-select2/README.md +++ /dev/null @@ -1,165 +0,0 @@ -ui-select2 [![Build Status](https://travis-ci.org/angular-ui/ui-select2.png)](https://travis-ci.org/angular-ui/ui-select2) -========== -This directive allows you to enhance your select elements with behaviour from the [select2](http://ivaynberg.github.io/select2/) library. - -# Requirements - -- [AngularJS](http://angularjs.org/) -- [JQuery](http://jquery.com/) -- [Select2](http://ivaynberg.github.io/select2/) - -## Setup - -1. Install **Karma**, **Grunt** and **Bower** - `$ npm install -g karma grunt-cli bower` -2. Install development dependencies - `$ npm install` -3. Install components - `$ bower install` -4. ??? -5. Profit! - -## Testing - -We use [Grunt](http://gruntjs.com/) to check for JavaScript syntax errors and execute all unit tests. To run Grunt, simply execute: - -`$ grunt` - -This will lint and test the code, then exit. To have Grunt stay open and automatically lint and test your files whenever you make a code change, use: - -`$ grunt karma:server watch` - -This will start a Karma server in the background and run unit tests in Firefox and PhantomJS whenever the source code or spec file is saved. - -# Usage - -We use [bower](https://github.com/bower/bower) for dependency management. Install AngularUI Select2 into your project by running the command - -`$ bower install angular-ui-select2` - -If you use a `bower.json` file in your project, you can have Bower save ui-select2 as a dependency by passing the `--save` or `--save-dev` flag with the above command. - -This will copy the ui-select2 files into your `bower_components` folder, along with its dependencies. Load the script files in your application: -```html - - - - - -``` - -(Note that `jquery` must be loaded before `angular` so that it doesn't use `jqLite` internally) - - -Add the select2 module as a dependency to your application module: - -```javascript -var myAppModule = angular.module('MyApp', ['ui.select2']); -``` - -Apply the directive to your form elements: - -```html - -``` - -## Options - -All the select2 options can be passed through the directive. You can read more about the supported list of options and what they do on the [Select2 Documentation Page](http://ivaynberg.github.com/select2/) - -```javascript -myAppModule.controller('MyController', function($scope) { - $scope.select2Options = { - allowClear:true - }; -}); -``` - -```html - -``` - -Some time it may make sense to specify the options in the template file. - -```html - -``` - -## Working with ng-model - -The ui-select2 directive plays nicely with ng-model and validation directives such as ng-required. - -If you add the ng-model directive to same the element as ui-select2 then the picked option is automatically synchronized with the model value. - -## Working with dynamic options -`ui-select2` is incompatible with ` - - - -``` - -## Working with placeholder text -In order to properly support the Select2 placeholder, create an empty ` - - - - -``` - -## ng-required directive - -If you apply the required directive to element then the form element is invalid until an option is selected. - -Note: Remember that the ng-required directive must be explicitly set, i.e. to "true". This is especially true on divs: - -```html - -``` - -## Using simple tagging mode - -When AngularJS View-Model tags are stored as a list of strings, setting -the ui-select2 specific option `simple_tags` will allow to keep the model -as a list of strings, and not convert it into a list of Select2 tag objects. - -```html - -``` - -```javascript -myAppModule.controller('MyController', function($scope) { - $scope.list_of_string = ['tag1', 'tag2'] - $scope.select2Options = { - 'multiple': true, - 'simple_tags': true, - 'tags': ['tag1', 'tag2', 'tag3', 'tag4'] // Can be empty list. - }; -}); -``` diff --git a/vendor/assets/components/angular-ui-select2/bower.json b/vendor/assets/components/angular-ui-select2/bower.json deleted file mode 100644 index 2cf5041e1..000000000 --- a/vendor/assets/components/angular-ui-select2/bower.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "author": "AngularUI", - "name": "angular-ui-select2", - "version": "0.0.5", - "homepage": "http://angular-ui.github.com", - "keywords": [ - "angular", - "angularui", - "select2" - ], - "main": "./src/select2.js", - "dependencies": { - "angular": ">=1.2.0", - "select2": "~3.4", - "jquery": ">=1.6.4" - }, - "devDependencies": { - "angular-mocks": ">=1.0.2" - } -} diff --git a/vendor/assets/components/angular-ui-select2/docs/index.html b/vendor/assets/components/angular-ui-select2/docs/index.html deleted file mode 100644 index ff373e782..000000000 --- a/vendor/assets/components/angular-ui-select2/docs/index.html +++ /dev/null @@ -1,47 +0,0 @@ -
    - -
    -
    -

    Demo

    -
    -

    Value is: {{select2}} (choose second)

    - -
    - -
    -

    Value is: {{select2multiple}} (choose second)

    - -
    -
    -
    -

    Options

    -

    You can pass an object to Select2 as the expression: ui-select2="{allowClear:true}" that will be passed directly to $.fn.select2(). You can read more about the supported list of options and what they do on the Select2 Documentation Page. AngularUI will leverage properties passed to Select2 for any complex behavior, there are no parameters necessary for that are specific to AngularUI.

    -
    -
    -

    ui-select2 is incompatible with <select ng-options>. For the best results use <option ng-repeat> instead

    -

    In order to properly support the Select2 placeholder, create an empty <option> tag at the top of the <select> and either set a data-placeholder on the select element or pass a placeholder option to Select2.

    - -

    How?

    -
    -<p>Value is: {{select2}} <a ng-click="select2='two'">(choose second)</a></p>
    -<select ui-select2 ng-model="select2">
    -<option value="">Pick a number</option>
    -<option value="one">First</option>
    -<option value="two">Second</option>
    -<option value="three">Third</option>
    -</select>
    -
    -

    Or try playing around with this sandbox demo to see how AJAX works

    -
    \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-select2/docs/styles.css b/vendor/assets/components/angular-ui-select2/docs/styles.css deleted file mode 100644 index 08611a479..000000000 --- a/vendor/assets/components/angular-ui-select2/docs/styles.css +++ /dev/null @@ -1,4 +0,0 @@ - -#directives-select2 select { - width: 200px; -} \ No newline at end of file diff --git a/vendor/assets/components/angular-ui-select2/package.json b/vendor/assets/components/angular-ui-select2/package.json deleted file mode 100644 index fc0ccd6c8..000000000 --- a/vendor/assets/components/angular-ui-select2/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "author": "https://github.com/angular-ui/ui-select2/graphs/contributors", - "name": "angular-ui-select2", - "keywords": [ - "angular", - "angularui", - "select2" - ], - "description": "AngularUI - The companion suite for AngularJS", - "version": "0.0.5", - "homepage": "http://angular-ui.github.com", - "repository": { - "type": "git", - "url": "git://github.com/angular-ui/ui-select2.git" - }, - "engines": { - "node": ">= 0.8.4" - }, - "dependencies": {}, - "devDependencies": { - "async": "0.1.x", - "grunt": "~0.4.1", - "grunt-contrib-jshint": "~0.6.4", - "grunt-contrib-watch": "~0.5.3", - "grunt-karma": "~0.6.2", - "karma": "~0.10.2" - } -} diff --git a/vendor/assets/components/angular-ui-select2/src/select2.js b/vendor/assets/components/angular-ui-select2/src/select2.js deleted file mode 100644 index f81731c2e..000000000 --- a/vendor/assets/components/angular-ui-select2/src/select2.js +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Enhanced Select2 Dropmenus - * - * @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2 - * This change is so that you do not have to do an additional query yourself on top of Select2's own query - * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation - */ -angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) { - var options = {}; - if (uiSelect2Config) { - angular.extend(options, uiSelect2Config); - } - return { - require: 'ngModel', - priority: 1, - compile: function (tElm, tAttrs) { - var watch, - repeatOption, - repeatAttr, - isSelect = tElm.is('select'), - isMultiple = angular.isDefined(tAttrs.multiple); - - // Enable watching of the options dataset if in use - if (tElm.is('select')) { - repeatOption = tElm.find('option[ng-repeat], option[data-ng-repeat]'); - - if (repeatOption.length) { - repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat'); - watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop(); - } - } - - return function (scope, elm, attrs, controller) { - // instance-specific options - var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2)); - - /* - Convert from Select2 view-model to Angular view-model. - */ - var convertToAngularModel = function(select2_data) { - var model; - if (opts.simple_tags) { - model = []; - angular.forEach(select2_data, function(value, index) { - model.push(value.id); - }); - } else { - model = select2_data; - } - return model; - }; - - /* - Convert from Angular view-model to Select2 view-model. - */ - var convertToSelect2Model = function(angular_data) { - var model = []; - if (!angular_data) { - return model; - } - - if (opts.simple_tags) { - model = []; - angular.forEach( - angular_data, - function(value, index) { - model.push({'id': value, 'text': value}); - }); - } else { - model = angular_data; - } - return model; - }; - - if (isSelect) { - // Use element', function () { - describe('compiling this directive', function () { - it('should throw an error if we have no model defined', function () { - expect(function(){ - compile(''); - }).toThrow(); - }); - it('should create proper DOM structure', function () { - var element = compile(''); - expect(element.siblings().is('div.select2-container')).toBe(true); - }); - it('should not modify the model if there is no initial value', function(){ - //TODO - }); - }); - describe('when model is changed programmatically', function(){ - describe('for single select', function(){ - it('should set select2 to the value', function(){ - scope.foo = 'First'; - var element = compile(''); - expect(element.select2('val')).toBe('First'); - scope.$apply('foo = "Second"'); - expect(element.select2('val')).toBe('Second'); - }); - it('should handle falsey values', function(){ - scope.foo = 'First'; - var element = compile(''); - expect(element.select2('val')).toBe('First'); - scope.$apply('foo = false'); - expect(element.select2('val')).toBe(null); - scope.$apply('foo = "Second"'); - scope.$apply('foo = null'); - expect(element.select2('val')).toBe(null); - scope.$apply('foo = "Second"'); - scope.$apply('foo = undefined'); - expect(element.select2('val')).toBe(null); - }); - }); - describe('for multiple select', function(){ - it('should set select2 to multiple value', function(){ - scope.foo = ['First']; - var element = compile(''); - expect(element.select2('val')).toEqual(['First']); - scope.$apply('foo = ["Second"]'); - expect(element.select2('val')).toEqual(['Second']); - scope.$apply('foo = ["Second","Third"]'); - expect(element.select2('val')).toEqual(['Second','Third']); - }); - it('should handle falsey values', function(){ - scope.foo = ['First']; - var element = compile(''); - expect(element.val()).toEqual(['First']); - scope.$apply('foo = ["Second"]'); - scope.$apply('foo = false'); - expect(element.select2('val')).toEqual([]); - scope.$apply('foo = ["Second"]'); - scope.$apply('foo = null'); - expect(element.select2('val')).toEqual([]); - scope.$apply('foo = ["Second"]'); - scope.$apply('foo = undefined'); - expect(element.select2('val')).toEqual([]); - }); - }); - }); - it('should observe the disabled attribute', function () { - var element = compile(''); - expect(element.siblings().hasClass('select2-container-disabled')).toBe(false); - scope.$apply('disabled = true'); - expect(element.siblings().hasClass('select2-container-disabled')).toBe(true); - scope.$apply('disabled = false'); - expect(element.siblings().hasClass('select2-container-disabled')).toBe(false); - }); - it('should observe the multiple attribute', function () { - var element = $compile('')(scope); - - expect(element.siblings().hasClass('select2-container-multi')).toBe(false); - scope.$apply('multiple = true'); - expect(element.siblings().hasClass('select2-container-multi')).toBe(true); - scope.$apply('multiple = false'); - expect(element.siblings().hasClass('select2-container-multi')).toBe(false); - }); - it('should observe an option with ng-repeat for changes', function(){ - scope.items = ['first', 'second', 'third']; - scope.foo = 'fourth'; - var element = compile(''); - expect(element.select2('val')).toBe(null); - scope.$apply('foo="fourth";items=["fourth"]'); - $timeout.flush(); - expect(element.select2('val')).toBe('fourth'); - }); - }); - describe('with an element', function () { - describe('compiling this directive', function () { - it('should throw an error if we have no model defined', function () { - expect(function() { - compile(''); - }).toThrow(); - }); - it('should create proper DOM structure', function () { - var element = compile(''); - expect(element.siblings().is('div.select2-container')).toBe(true); - }); - it('should not modify the model if there is no initial value', function(){ - //TODO - }); - }); - describe('when model is changed programmatically', function(){ - describe('for single-select', function(){ - it('should call select2(data, ...) for objects', function(){ - var element = compile(''); - spyOn($.fn, 'select2'); - scope.$apply('foo={ id: 1, text: "first" }'); - expect(element.select2).toHaveBeenCalledWith('data', { id: 1, text: "first" }); - }); - it('should call select2(val, ...) for strings', function(){ - var element = compile(''); - spyOn($.fn, 'select2'); - scope.$apply('foo="first"'); - expect(element.select2).toHaveBeenCalledWith('val', 'first'); - }); - }); - describe('for multi-select', function(){ - it('should call select2(data, ...) for arrays', function(){ - var element = compile(''); - spyOn($.fn, 'select2'); - scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]'); - expect(element.select2).toHaveBeenCalledWith('data', [{ id: 1, text: "first" },{ id: 2, text: "second" }]); - }); - it('should call select2(data, []) for falsey values', function(){ - var element = compile(''); - spyOn($.fn, 'select2'); - scope.$apply('foo=[]'); - expect(element.select2).toHaveBeenCalledWith('data', []); - }); - xit('should call select2(val, ...) for strings', function(){ - var element = compile(''); - spyOn($.fn, 'select2'); - scope.$apply('foo="first,second"'); - expect(element.select2).toHaveBeenCalledWith('val', 'first,second'); - }); - }); - }); - describe('consumers of ngModel should correctly use $viewValue', function() { - it('should use any formatters if present (select - single select)', function(){ - scope.foo = 'First'; - var element = compile(''); - expect(element.select2('val')).toBe('First - I\'ve been formatted'); - scope.$apply('foo = "Second"'); - expect(element.select2('val')).toBe('Second - I\'ve been formatted'); - }); - - // isMultiple && falsey - it('should use any formatters if present (input multi select - falsey value)', function() { - // need special function to hit this case - // old code checked modelValue... can't just pass undefined to model value because view value will be the same - scope.transformers.fromModel = function(modelValue) { - if (modelValue === "magic") { - return undefined; - } - - return modelValue; - }; - - var element = compile(''); - spyOn($.fn, 'select2'); - scope.$apply('foo="magic"'); - expect(element.select2).toHaveBeenCalledWith('data', []); - }); - // isMultiple && isArray - it('should use any formatters if present (input multi select)', function() { - var element = compile(''); - spyOn($.fn, 'select2'); - scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]'); - expect(element.select2).toHaveBeenCalledWith('data', [{ id: 1, text: "first - I've been formatted" },{ id: 2, text: "second - I've been formatted" }]); - }); - // isMultiple... - xit('should use any formatters if present (input multi select - non array)', function() { - var element = compile(''); - spyOn($.fn, 'select2'); - scope.$apply('foo={ id: 1, text: "first" }'); - expect(element.select2).toHaveBeenCalledWith('val', { id: 1, text: "first - I've been formatted" }); - }); - - // !isMultiple - it('should use any formatters if present (input - single select - object)', function() { - var element = compile(''); - spyOn($.fn, 'select2'); - scope.$apply('foo={ id: 1, text: "first" }'); - expect(element.select2).toHaveBeenCalledWith('data', { id: 1, text: "first - I've been formatted" }); - }); - it('should use any formatters if present (input - single select - non object)', function() { - var element = compile(''); - spyOn($.fn, 'select2'); - scope.$apply('foo="first"'); - expect(element.select2).toHaveBeenCalledWith('val', "first - I've been formatted"); - }); - - it('should not set the default value using scope.$eval', function() { - // testing directive instantiation - change order of test - spyOn($.fn, 'select2'); - spyOn($.fn, 'val'); - scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]'); - - var element = compile(''); - expect(element.val).not.toHaveBeenCalledWith([{ id: 1, text: "first" },{ id: 2, text: "second" }]); - }); - it('should expect a default value to be set with a call to the render method', function() { - // this should monitor the events after init, when the timeout callback executes - var opts = angular.copy(scope.options); - opts.multiple = true; - - scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]'); - - spyOn($.fn, 'select2'); - var element = compile(''); - - // select 2 init - expect(element.select2).toHaveBeenCalledWith(opts); - - // callback setting - expect(element.select2).toHaveBeenCalledWith('data', [{ id: 1, text: "first - I've been formatted" },{ id: 2, text: "second - I've been formatted" }]); - - // retieve data - expect(element.select2).toHaveBeenCalledWith('data'); - }); - - }); - it('should set the model when the user selects an item', function(){ - var element = compile(''); - // TODO: programmactically select an option - // expect(scope.foo).toBe(/* selected val */) ; - }); - - it('updated the view when model changes with complex object', function(){ - scope.foo = [{'id': '0', 'text': '0'}]; - scope.options['multiple'] = true; - var element = compile(''); - scope.$digest(); - - scope.foo.push({'id': '1', 'text': '1'}); - scope.$digest(); - - expect(element.select2('data')).toEqual( - [{'id': '0', 'text': '0'}, {'id': '1', 'text': '1'}]); - }); - - - describe('simple_tags', function() { - - beforeEach(function() { - scope.options['multiple'] = true; - scope.options['simple_tags'] = true; - scope.options['tags'] = []; - }); - - it('Initialize the select2 view based on list of strings.', function() { - scope.foo = ['tag1', 'tag2']; - - var element = compile(''); - scope.$digest(); - - expect(element.select2('data')).toEqual([ - {'id': 'tag1', 'text': 'tag1'}, - {'id': 'tag2', 'text': 'tag2'} - ]); - }); - - it( - 'When list is empty select2 view model is also initialized as empty', - function() { - scope.foo = []; - - var element = compile(''); - scope.$digest(); - - expect(element.select2('data')).toEqual([]); - }); - - it( - 'Updating the model with a string will update the select2 view model.', - function() { - scope.foo = []; - var element = compile(''); - scope.$digest(); - - scope.foo.push('tag1'); - scope.$digest(); - - expect(element.select2('data')).toEqual([ - {'id': 'tag1', 'text': 'tag1'} - ]); - }); - - it( - 'Updating the select2 model will update AngularJS model with a string.', - function() { - scope.foo = []; - var element = compile(''); - scope.$digest(); - - element.select2('data', [ - {'id':'tag1', 'text': 'tag1'}, - {'id':'tag2', 'text': 'tag2'} - ]); - element.trigger('change'); - - expect(scope.foo).toEqual(['tag1', 'tag2']); - }); - - }); - - }); -}); \ No newline at end of file diff --git a/vendor/assets/components/angular/.bower.json b/vendor/assets/components/angular/.bower.json index 0b97749ec..cc906a95c 100644 --- a/vendor/assets/components/angular/.bower.json +++ b/vendor/assets/components/angular/.bower.json @@ -1,17 +1,17 @@ { "name": "angular", - "version": "1.2.28", + "version": "1.3.20", "main": "./angular.js", "ignore": [], "dependencies": {}, "homepage": "https://github.com/angular/bower-angular", - "_release": "1.2.28", + "_release": "1.3.20", "_resolution": { "type": "version", - "tag": "v1.2.28", - "commit": "d1369fe05d3a7d85961a2223292b67ee82b9f80a" + "tag": "v1.3.20", + "commit": "0cd10f27471310fe07167b00f8a4242c6bba4df2" }, "_source": "git://github.com/angular/bower-angular.git", - "_target": ">=1.2.0", + "_target": "1.3.20", "_originalSource": "angular" } \ No newline at end of file diff --git a/vendor/assets/components/angular/README.md b/vendor/assets/components/angular/README.md index 897fb7f01..d1bc0eddf 100644 --- a/vendor/assets/components/angular/README.md +++ b/vendor/assets/components/angular/README.md @@ -20,10 +20,7 @@ Then add a ` ``` -Note that this package is not in CommonJS format, so doing `require('angular')` will return `undefined`. -If you're using [Browserify](https://github.com/substack/node-browserify), you can use -[exposify](https://github.com/thlorenz/exposify) to have `require('angular')` return the `angular` -global. +Or `require('angular')` from your code. ### bower @@ -46,7 +43,7 @@ Documentation is available on the The MIT License -Copyright (c) 2010-2012 Google, Inc. http://angularjs.org +Copyright (c) 2010-2015 Google, Inc. http://angularjs.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/assets/components/angular/angular-csp.css b/vendor/assets/components/angular/angular-csp.css index 3abb3a0e6..0ce9d864c 100644 --- a/vendor/assets/components/angular/angular-csp.css +++ b/vendor/assets/components/angular/angular-csp.css @@ -4,21 +4,10 @@ [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak, -.ng-hide { +.ng-hide:not(.ng-hide-animate) { display: none !important; } ng\:form { display: block; } - -.ng-animate-block-transitions { - transition:0s all!important; - -webkit-transition:0s all!important; -} - -/* show the element during a show/hide animation when the - * animation is ongoing, but the .ng-hide class is active */ -.ng-hide-add-active, .ng-hide-remove { - display: block!important; -} diff --git a/vendor/assets/components/angular/angular.js b/vendor/assets/components/angular/angular.js index ccf3b4baf..2445ab55e 100644 --- a/vendor/assets/components/angular/angular.js +++ b/vendor/assets/components/angular/angular.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.2.28 + * @license AngularJS v1.3.20 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ @@ -30,138 +30,130 @@ * should all be static strings, not variables or general expressions. * * @param {string} module The namespace to use for the new minErr instance. + * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning + * error from returned function, for cases when a particular type of error is useful. * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance */ -function minErr(module) { - return function () { +function minErr(module, ErrorConstructor) { + ErrorConstructor = ErrorConstructor || Error; + return function() { var code = arguments[0], prefix = '[' + (module ? module + ':' : '') + code + '] ', template = arguments[1], templateArgs = arguments, - stringify = function (obj) { - if (typeof obj === 'function') { - return obj.toString().replace(/ \{[\s\S]*$/, ''); - } else if (typeof obj === 'undefined') { - return 'undefined'; - } else if (typeof obj !== 'string') { - return JSON.stringify(obj); - } - return obj; - }, + message, i; - message = prefix + template.replace(/\{\d+\}/g, function (match) { + message = prefix + template.replace(/\{\d+\}/g, function(match) { var index = +match.slice(1, -1), arg; if (index + 2 < templateArgs.length) { - arg = templateArgs[index + 2]; - if (typeof arg === 'function') { - return arg.toString().replace(/ ?\{[\s\S]*$/, ''); - } else if (typeof arg === 'undefined') { - return 'undefined'; - } else if (typeof arg !== 'string') { - return toJson(arg); - } - return arg; + return toDebugString(templateArgs[index + 2]); } return match; }); - message = message + '\nhttp://errors.angularjs.org/1.2.28/' + + message = message + '\nhttp://errors.angularjs.org/1.3.20/' + (module ? module + '/' : '') + code; for (i = 2; i < arguments.length; i++) { - message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + - encodeURIComponent(stringify(arguments[i])); + message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' + + encodeURIComponent(toDebugString(arguments[i])); } - - return new Error(message); + return new ErrorConstructor(message); }; } /* We need to tell jshint what variables are being exported */ /* global angular: true, - msie: true, - jqLite: true, - jQuery: true, - slice: true, - push: true, - toString: true, - ngMinErr: true, - angularModule: true, - nodeName_: true, - uid: true, - VALIDITY_STATE_PROPERTY: true, + msie: true, + jqLite: true, + jQuery: true, + slice: true, + splice: true, + push: true, + toString: true, + ngMinErr: true, + angularModule: true, + uid: true, + REGEX_STRING_REGEXP: true, + VALIDITY_STATE_PROPERTY: true, - lowercase: true, - uppercase: true, - manualLowercase: true, - manualUppercase: true, - nodeName_: true, - isArrayLike: true, - forEach: true, - sortedKeys: true, - forEachSorted: true, - reverseParams: true, - nextUid: true, - setHashKey: true, - extend: true, - int: true, - inherit: true, - noop: true, - identity: true, - valueFn: true, - isUndefined: true, - isDefined: true, - isObject: true, - isString: true, - isNumber: true, - isDate: true, - isArray: true, - isFunction: true, - isRegExp: true, - isWindow: true, - isScope: true, - isFile: true, - isBlob: true, - isBoolean: true, - isPromiseLike: true, - trim: true, - isElement: true, - makeMap: true, - map: true, - size: true, - includes: true, - indexOf: true, - arrayRemove: true, - isLeafNode: true, - copy: true, - shallowCopy: true, - equals: true, - csp: true, - concat: true, - sliceArgs: true, - bind: true, - toJsonReplacer: true, - toJson: true, - fromJson: true, - toBoolean: true, - startingTag: true, - tryDecodeURIComponent: true, - parseKeyValue: true, - toKeyValue: true, - encodeUriSegment: true, - encodeUriQuery: true, - angularInit: true, - bootstrap: true, - snake_case: true, - bindJQuery: true, - assertArg: true, - assertArgFn: true, - assertNotHasOwnProperty: true, - getter: true, - getBlockElements: true, - hasOwnProperty: true, + lowercase: true, + uppercase: true, + manualLowercase: true, + manualUppercase: true, + nodeName_: true, + isArrayLike: true, + forEach: true, + sortedKeys: true, + forEachSorted: true, + reverseParams: true, + nextUid: true, + setHashKey: true, + extend: true, + int: true, + inherit: true, + noop: true, + identity: true, + valueFn: true, + isUndefined: true, + isDefined: true, + isObject: true, + isString: true, + isNumber: true, + isDate: true, + isArray: true, + isFunction: true, + isRegExp: true, + isWindow: true, + isScope: true, + isFile: true, + isFormData: true, + isBlob: true, + isBoolean: true, + isPromiseLike: true, + trim: true, + escapeForRegexp: true, + isElement: true, + makeMap: true, + includes: true, + arrayRemove: true, + copy: true, + shallowCopy: true, + equals: true, + csp: true, + concat: true, + sliceArgs: true, + bind: true, + toJsonReplacer: true, + toJson: true, + fromJson: true, + startingTag: true, + tryDecodeURIComponent: true, + parseKeyValue: true, + toKeyValue: true, + encodeUriSegment: true, + encodeUriQuery: true, + angularInit: true, + bootstrap: true, + getTestability: true, + snake_case: true, + bindJQuery: true, + assertArg: true, + assertArgFn: true, + assertNotHasOwnProperty: true, + getter: true, + getBlockNodes: true, + hasOwnProperty: true, + createMap: true, + + NODE_TYPE_ELEMENT: true, + NODE_TYPE_ATTRIBUTE: true, + NODE_TYPE_TEXT: true, + NODE_TYPE_COMMENT: true, + NODE_TYPE_DOCUMENT: true, + NODE_TYPE_DOCUMENT_FRAGMENT: true, */ //////////////////////////////////// @@ -181,6 +173,8 @@ function minErr(module) { *
    */ +var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; + // The name of a form control's ValidityState property. // This is used so that it's possible for internal tests to create mock ValidityStates. var VALIDITY_STATE_PROPERTY = 'validity'; @@ -195,7 +189,7 @@ var VALIDITY_STATE_PROPERTY = 'validity'; * @param {string} string String to be converted to lowercase. * @returns {string} Lowercased string. */ -var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; +var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; var hasOwnProperty = Object.prototype.hasOwnProperty; /** @@ -208,7 +202,7 @@ var hasOwnProperty = Object.prototype.hasOwnProperty; * @param {string} string String to be converted to uppercase. * @returns {string} Uppercased string. */ -var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; +var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; var manualLowercase = function(s) { @@ -239,6 +233,7 @@ var jqLite, // delay binding since jQuery could be loaded after us. jQuery, // delay binding slice = [].slice, + splice = [].splice, push = [].push, toString = Object.prototype.toString, ngMinErr = minErr('ng'), @@ -246,17 +241,13 @@ var /** @name angular */ angular = window.angular || (window.angular = {}), angularModule, - nodeName_, - uid = ['0', '0', '0']; + uid = 0; /** - * IE 11 changed the format of the UserAgent string. - * See http://msdn.microsoft.com/en-us/library/ms537503.aspx + * documentMode is an IE-only property + * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx */ -msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); -if (isNaN(msie)) { - msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); -} +msie = document.documentMode; /** @@ -270,9 +261,11 @@ function isArrayLike(obj) { return false; } - var length = obj.length; + // Support: iOS 8.2 (not reproducible in simulator) + // "length" in obj used to prevent JIT error (gh-11508) + var length = "length" in Object(obj) && obj.length; - if (obj.nodeType === 1 && length) { + if (obj.nodeType === NODE_TYPE_ELEMENT && length) { return true; } @@ -288,12 +281,17 @@ function isArrayLike(obj) { * * @description * Invokes the `iterator` function once for each item in `obj` collection, which can be either an - * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` - * is the value of an object property or an array element and `key` is the object property key or - * array element index. Specifying a `context` for the function is optional. + * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` + * is the value of an object property or an array element, `key` is the object property key or + * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. * * It is worth noting that `.forEach` does not iterate over inherited properties because it filters * using the `hasOwnProperty` method. + * + * Unlike ES262's + * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), + * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just + * return the value provided. * ```js var values = {name: 'misko', gender: 'male'}; @@ -309,27 +307,31 @@ function isArrayLike(obj) { * @param {Object=} context Object to become context (`this`) for the iterator function. * @returns {Object|Array} Reference to `obj`. */ + function forEach(obj, iterator, context) { - var key; + var key, length; if (obj) { if (isFunction(obj)) { for (key in obj) { // Need to check if hasOwnProperty exists, // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { - iterator.call(context, obj[key], key); + iterator.call(context, obj[key], key, obj); } } } else if (isArray(obj) || isArrayLike(obj)) { - for (key = 0; key < obj.length; key++) { - iterator.call(context, obj[key], key); + var isPrimitive = typeof obj !== 'object'; + for (key = 0, length = obj.length; key < length; key++) { + if (isPrimitive || key in obj) { + iterator.call(context, obj[key], key, obj); + } } } else if (obj.forEach && obj.forEach !== forEach) { - obj.forEach(iterator, context); + obj.forEach(iterator, context, obj); } else { for (key in obj) { if (obj.hasOwnProperty(key)) { - iterator.call(context, obj[key], key); + iterator.call(context, obj[key], key, obj); } } } @@ -338,18 +340,12 @@ function forEach(obj, iterator, context) { } function sortedKeys(obj) { - var keys = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - keys.push(key); - } - } - return keys.sort(); + return Object.keys(obj).sort(); } function forEachSorted(obj, iterator, context) { var keys = sortedKeys(obj); - for ( var i = 0; i < keys.length; i++) { + for (var i = 0; i < keys.length; i++) { iterator.call(context, obj[keys[i]], keys[i]); } return keys; @@ -366,33 +362,17 @@ function reverseParams(iteratorFn) { } /** - * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric - * characters such as '012ABC'. The reason why we are not using simply a number counter is that - * the number string gets longer over time, and it can also overflow, where as the nextId - * will grow much slower, it is a string, and it will never overflow. + * A consistent way of creating unique IDs in angular. * - * @returns {string} an unique alpha-numeric string + * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before + * we hit number precision issues in JavaScript. + * + * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M + * + * @returns {number} an unique alpha-numeric string */ function nextUid() { - var index = uid.length; - var digit; - - while(index) { - index--; - digit = uid[index].charCodeAt(0); - if (digit == 57 /*'9'*/) { - uid[index] = 'A'; - return uid.join(''); - } - if (digit == 90 /*'Z'*/) { - uid[index] = '0'; - } else { - uid[index] = String.fromCharCode(digit + 1); - return uid.join(''); - } - } - uid.unshift('0'); - return uid.join(''); + return ++uid; } @@ -404,8 +384,7 @@ function nextUid() { function setHashKey(obj, h) { if (h) { obj.$$hashKey = h; - } - else { + } else { delete obj.$$hashKey; } } @@ -418,7 +397,9 @@ function setHashKey(obj, h) { * * @description * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) - * to `dst`. You can specify multiple `src` objects. + * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so + * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. + * Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy). * * @param {Object} dst Destination object. * @param {...Object} src Source object(s). @@ -426,15 +407,19 @@ function setHashKey(obj, h) { */ function extend(dst) { var h = dst.$$hashKey; - forEach(arguments, function(obj) { - if (obj !== dst) { - forEach(obj, function(value, key) { - dst[key] = value; - }); - } - }); - setHashKey(dst,h); + for (var i = 1, ii = arguments.length; i < ii; i++) { + var obj = arguments[i]; + if (obj) { + var keys = Object.keys(obj); + for (var j = 0, jj = keys.length; j < jj; j++) { + var key = keys[j]; + dst[key] = obj[key]; + } + } + } + + setHashKey(dst, h); return dst; } @@ -444,7 +429,7 @@ function int(str) { function inherit(parent, extra) { - return extend(new (extend(function() {}, {prototype:parent}))(), extra); + return extend(Object.create(parent), extra); } /** @@ -482,6 +467,8 @@ noop.$inject = []; return (transformationFn || angular.identity)(value); }; ``` + * @param {*} value to be returned. + * @returns {*} the value passed in. */ function identity($) {return $;} identity.$inject = []; @@ -501,7 +488,7 @@ function valueFn(value) {return function() {return value;};} * @param {*} value Reference to check. * @returns {boolean} True if `value` is undefined. */ -function isUndefined(value){return typeof value === 'undefined';} +function isUndefined(value) {return typeof value === 'undefined';} /** @@ -516,7 +503,7 @@ function isUndefined(value){return typeof value === 'undefined';} * @param {*} value Reference to check. * @returns {boolean} True if `value` is defined. */ -function isDefined(value){return typeof value !== 'undefined';} +function isDefined(value) {return typeof value !== 'undefined';} /** @@ -532,7 +519,10 @@ function isDefined(value){return typeof value !== 'undefined';} * @param {*} value Reference to check. * @returns {boolean} True if `value` is an `Object` but not `null`. */ -function isObject(value){return value != null && typeof value === 'object';} +function isObject(value) { + // http://jsperf.com/isobject4 + return value !== null && typeof value === 'object'; +} /** @@ -547,7 +537,7 @@ function isObject(value){return value != null && typeof value === 'object';} * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `String`. */ -function isString(value){return typeof value === 'string';} +function isString(value) {return typeof value === 'string';} /** @@ -559,10 +549,16 @@ function isString(value){return typeof value === 'string';} * @description * Determines if a reference is a `Number`. * + * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. + * + * If you wish to exclude these then you can use the native + * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) + * method. + * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Number`. */ -function isNumber(value){return typeof value === 'number';} +function isNumber(value) {return typeof value === 'number';} /** @@ -594,14 +590,7 @@ function isDate(value) { * @param {*} value Reference to check. * @returns {boolean} True if `value` is an `Array`. */ -var isArray = (function() { - if (!isFunction(Array.isArray)) { - return function(value) { - return toString.call(value) === '[object Array]'; - }; - } - return Array.isArray; -})(); +var isArray = Array.isArray; /** * @ngdoc function @@ -615,7 +604,7 @@ var isArray = (function() { * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Function`. */ -function isFunction(value){return typeof value === 'function';} +function isFunction(value) {return typeof value === 'function';} /** @@ -638,7 +627,7 @@ function isRegExp(value) { * @returns {boolean} True if `obj` is a window obj. */ function isWindow(obj) { - return obj && obj.document && obj.location && obj.alert && obj.setInterval; + return obj && obj.window === obj; } @@ -652,6 +641,11 @@ function isFile(obj) { } +function isFormData(obj) { + return toString.call(obj) === '[object FormData]'; +} + + function isBlob(obj) { return toString.call(obj) === '[object Blob]'; } @@ -667,19 +661,17 @@ function isPromiseLike(obj) { } -var trim = (function() { - // native trim is way faster: http://jsperf.com/angular-trim-test - // but IE doesn't have it... :-( - // TODO: we should move this into IE/ES5 polyfill - if (!String.prototype.trim) { - return function(value) { - return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; - }; - } - return function(value) { - return isString(value) ? value.trim() : value; - }; -})(); +var trim = function(value) { + return isString(value) ? value.trim() : value; +}; + +// Copied from: +// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 +// Prereq: s is a string. +var escapeForRegexp = function(s) { + return s.replace(/([-()\[\]{}+?*.$\^|,:#=0) + var index = array.indexOf(value); + if (index >= 0) array.splice(index, 1); return value; } -function isLeafNode (node) { - if (node) { - switch (node.nodeName) { - case "OPTION": - case "PRE": - case "TITLE": - return true; - } - } - return false; -} - /** * @ngdoc function * @name angular.copy @@ -803,7 +729,7 @@ function isLeafNode (node) { * Creates a deep copy of `source`, which should be an object or an array. * * * If no destination is supplied, a copy of the object or array is created. - * * If a destination is provided, all of its elements (for array) or properties (for objects) + * * If a destination is provided, all of its elements (for arrays) or properties (for objects) * are deleted and then all elements/properties from the source are copied to it. * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. * * If `source` is identical to 'destination' an exception will be thrown. @@ -868,7 +794,8 @@ function copy(source, destination, stackSource, stackDest) { destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); destination.lastIndex = source.lastIndex; } else if (isObject(source)) { - destination = copy(source, {}, stackSource, stackDest); + var emptyObject = Object.create(Object.getPrototypeOf(source)); + destination = copy(source, emptyObject, stackSource, stackDest); } } } else { @@ -879,7 +806,7 @@ function copy(source, destination, stackSource, stackDest) { stackDest = stackDest || []; if (isObject(source)) { - var index = indexOf(stackSource, source); + var index = stackSource.indexOf(source); if (index !== -1) return stackDest[index]; stackSource.push(source); @@ -889,7 +816,7 @@ function copy(source, destination, stackSource, stackDest) { var result; if (isArray(source)) { destination.length = 0; - for ( var i = 0; i < source.length; i++) { + for (var i = 0; i < source.length; i++) { result = copy(source[i], null, stackSource, stackDest); if (isObject(source[i])) { stackSource.push(source[i]); @@ -906,13 +833,15 @@ function copy(source, destination, stackSource, stackDest) { delete destination[key]; }); } - for ( var key in source) { - result = copy(source[key], null, stackSource, stackDest); - if (isObject(source[key])) { - stackSource.push(source[key]); - stackDest.push(result); + for (var key in source) { + if (source.hasOwnProperty(key)) { + result = copy(source[key], null, stackSource, stackDest); + if (isObject(source[key])) { + stackSource.push(source[key]); + stackDest.push(result); + } + destination[key] = result; } - destination[key] = result; } setHashKey(destination,h); } @@ -922,20 +851,22 @@ function copy(source, destination, stackSource, stackDest) { } /** - * Creates a shallow copy of an object, an array or a primitive + * Creates a shallow copy of an object, an array or a primitive. + * + * Assumes that there are no proto properties for objects. */ function shallowCopy(src, dst) { if (isArray(src)) { dst = dst || []; - for ( var i = 0; i < src.length; i++) { + for (var i = 0, ii = src.length; i < ii; i++) { dst[i] = src[i]; } } else if (isObject(src)) { dst = dst || {}; for (var key in src) { - if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { + if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { dst[key] = src[key]; } } @@ -984,25 +915,26 @@ function equals(o1, o2) { if (isArray(o1)) { if (!isArray(o2)) return false; if ((length = o1.length) == o2.length) { - for(key=0; key').append(element).html(); try { - return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : + return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : elemHtml. match(/^(<[^>]+>)/)[1]. replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); - } catch(e) { + } catch (e) { return lowercase(elemHtml); } @@ -1191,7 +1113,7 @@ function startingTag(element) { function tryDecodeURIComponent(value) { try { return decodeURIComponent(value); - } catch(e) { + } catch (e) { // Ignore any invalid uri component } } @@ -1204,14 +1126,14 @@ function tryDecodeURIComponent(value) { function parseKeyValue(/**string*/keyValue) { var obj = {}, key_value, key; forEach((keyValue || "").split('&'), function(keyValue) { - if ( keyValue ) { + if (keyValue) { key_value = keyValue.replace(/\+/g,'%20').split('='); key = tryDecodeURIComponent(key_value[0]); - if ( isDefined(key) ) { + if (isDefined(key)) { var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; if (!hasOwnProperty.call(obj, key)) { obj[key] = val; - } else if(isArray(obj[key])) { + } else if (isArray(obj[key])) { obj[key].push(val); } else { obj[key] = [obj[key],val]; @@ -1275,9 +1197,23 @@ function encodeUriQuery(val, pctEncodeSpaces) { replace(/%3A/gi, ':'). replace(/%24/g, '$'). replace(/%2C/gi, ','). + replace(/%3B/gi, ';'). replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); } +var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; + +function getNgAttribute(element, ngAttr) { + var attr, i, ii = ngAttrPrefixes.length; + element = jqLite(element); + for (i = 0; i < ii; ++i) { + attr = ngAttrPrefixes[i] + ngAttr; + if (isString(attr = element.attr(attr))) { + return attr; + } + } + return null; +} /** * @ngdoc directive @@ -1287,6 +1223,11 @@ function encodeUriQuery(val, pctEncodeSpaces) { * @element ANY * @param {angular.Module} ngApp an optional application * {@link angular.module module} name to load. + * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be + * created in "strict-di" mode. This means that the application will fail to invoke functions which + * do not use explicit function annotation (and are thus unsuitable for minification), as described + * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in + * tracking down the root of these bugs. * * @description * @@ -1300,7 +1241,7 @@ function encodeUriQuery(val, pctEncodeSpaces) { * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. * * You can specify an **AngularJS module** to be used as the root module for the application. This - * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and + * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It * should contain the application code needed or have dependencies on other modules that will * contain the code. See {@link angular.module} for more information. * @@ -1308,7 +1249,7 @@ function encodeUriQuery(val, pctEncodeSpaces) { * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` * would not be resolved to `3`. * - * `ngApp` is the easiest, and most common, way to bootstrap an application. + * `ngApp` is the easiest, and most common way to bootstrap an application. * @@ -1324,48 +1265,109 @@ function encodeUriQuery(val, pctEncodeSpaces) { * + * Using `ngStrictDi`, you would see something like this: + * + + +
    +
    + I can add: {{a}} + {{b}} = {{ a+b }} + +

    This renders because the controller does not fail to + instantiate, by using explicit annotation style (see + script.js for details) +

    +
    + +
    + Name:
    + Hello, {{name}}! + +

    This renders because the controller does not fail to + instantiate, by using explicit annotation style + (see script.js for details) +

    +
    + +
    + I can add: {{a}} + {{b}} = {{ a+b }} + +

    The controller could not be instantiated, due to relying + on automatic function annotations (which are disabled in + strict mode). As such, the content of this section is not + interpolated, and there should be an error in your web console. +

    +
    +
    +
    + + angular.module('ngAppStrictDemo', []) + // BadController will fail to instantiate, due to relying on automatic function annotation, + // rather than an explicit annotation + .controller('BadController', function($scope) { + $scope.a = 1; + $scope.b = 2; + }) + // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, + // due to using explicit annotations using the array style and $inject property, respectively. + .controller('GoodController1', ['$scope', function($scope) { + $scope.a = 1; + $scope.b = 2; + }]) + .controller('GoodController2', GoodController2); + function GoodController2($scope) { + $scope.name = "World"; + } + GoodController2.$inject = ['$scope']; + + + div[ng-controller] { + margin-bottom: 1em; + -webkit-border-radius: 4px; + border-radius: 4px; + border: 1px solid; + padding: .5em; + } + div[ng-controller^=Good] { + border-color: #d6e9c6; + background-color: #dff0d8; + color: #3c763d; + } + div[ng-controller^=Bad] { + border-color: #ebccd1; + background-color: #f2dede; + color: #a94442; + margin-bottom: 0; + } + +
    */ function angularInit(element, bootstrap) { - var elements = [element], - appElement, + var appElement, module, - names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], - NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; + config = {}; - function append(element) { - element && elements.push(element); - } + // The element `element` has priority over any other element + forEach(ngAttrPrefixes, function(prefix) { + var name = prefix + 'app'; - forEach(names, function(name) { - names[name] = true; - append(document.getElementById(name)); - name = name.replace(':', '\\:'); - if (element.querySelectorAll) { - forEach(element.querySelectorAll('.' + name), append); - forEach(element.querySelectorAll('.' + name + '\\:'), append); - forEach(element.querySelectorAll('[' + name + ']'), append); + if (!appElement && element.hasAttribute && element.hasAttribute(name)) { + appElement = element; + module = element.getAttribute(name); } }); + forEach(ngAttrPrefixes, function(prefix) { + var name = prefix + 'app'; + var candidate; - forEach(elements, function(element) { - if (!appElement) { - var className = ' ' + element.className + ' '; - var match = NG_APP_CLASS_REGEXP.exec(className); - if (match) { - appElement = element; - module = (match[2] || '').replace(/\s+/g, ','); - } else { - forEach(element.attributes, function(attr) { - if (!appElement && names[attr.name]) { - appElement = element; - module = attr.value; - } - }); - } + if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { + appElement = candidate; + module = candidate.getAttribute(name); } }); if (appElement) { - bootstrap(appElement, module ? [module] : []); + config.strictDi = getNgAttribute(appElement, "strict-di") !== null; + bootstrap(appElement, module ? [module] : [], config); } } @@ -1378,7 +1380,7 @@ function angularInit(element, bootstrap) { * * See: {@link guide/bootstrap Bootstrap} * - * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. + * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. * They must use {@link ng.directive:ngApp ngApp}. * * Angular will detect if it has been loaded into the browser more than once and only allow the @@ -1386,44 +1388,45 @@ function angularInit(element, bootstrap) { * each of the subsequent scripts. This prevents strange results in applications, where otherwise * multiple instances of Angular try to work on the DOM. * - * - * - * - *
    - * - * - * - * - * - * - * - *
    {{heading}}
    {{fill}}
    + * ```html + * + * + * + *
    + * {{greeting}} *
    - * - * - * var app = angular.module('multi-bootstrap', []) * - * .controller('BrokenTable', function($scope) { - * $scope.headings = ['One', 'Two', 'Three']; - * $scope.fillings = [[1, 2, 3], ['A', 'B', 'C'], [7, 8, 9]]; - * }); - * - * - * it('should only insert one table cell for each item in $scope.fillings', function() { - * expect(element.all(by.css('td')).count()) - * .toBe(9); - * }); - * - * + * + * + * + * + * ``` * * @param {DOMElement} element DOM element which is the root of angular application. * @param {Array=} modules an array of modules to load into the application. * Each item in the array should be the name of a predefined module or a (DI annotated) - * function that will be invoked by the injector as a run block. + * function that will be invoked by the injector as a `config` block. * See: {@link angular.module modules} + * @param {Object=} config an object for defining configuration options for the application. The + * following keys are supported: + * + * * `strictDi` - disable automatic function annotation for the application. This is meant to + * assist in finding bugs which break minified code. Defaults to `false`. + * * @returns {auto.$injector} Returns the newly created injector for this app. */ -function bootstrap(element, modules) { +function bootstrap(element, modules, config) { + if (!isObject(config)) config = {}; + var defaultConfig = { + strictDi: false + }; + config = extend(defaultConfig, config); var doBootstrap = function() { element = jqLite(element); @@ -1440,10 +1443,18 @@ function bootstrap(element, modules) { modules.unshift(['$provide', function($provide) { $provide.value('$rootElement', element); }]); + + if (config.debugInfoEnabled) { + // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. + modules.push(['$compileProvider', function($compileProvider) { + $compileProvider.debugInfoEnabled(true); + }]); + } + modules.unshift('ng'); - var injector = createInjector(modules); - injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', - function(scope, element, compile, injector, animate) { + var injector = createInjector(modules, config.strictDi); + injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', + function bootstrapApply(scope, element, compile, injector) { scope.$apply(function() { element.data('$injector', injector); compile(element)(scope); @@ -1453,8 +1464,14 @@ function bootstrap(element, modules) { return injector; }; + var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; + if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { + config.debugInfoEnabled = true; + window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); + } + if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { return doBootstrap(); } @@ -1464,8 +1481,44 @@ function bootstrap(element, modules) { forEach(extraModules, function(module) { modules.push(module); }); - doBootstrap(); + return doBootstrap(); }; + + if (isFunction(angular.resumeDeferredBootstrap)) { + angular.resumeDeferredBootstrap(); + } +} + +/** + * @ngdoc function + * @name angular.reloadWithDebugInfo + * @module ng + * @description + * Use this function to reload the current application with debug information turned on. + * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. + * + * See {@link ng.$compileProvider#debugInfoEnabled} for more. + */ +function reloadWithDebugInfo() { + window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; + window.location.reload(); +} + +/** + * @name angular.getTestability + * @module ng + * @description + * Get the testability service for the instance of Angular on the given + * element. + * @param {DOMElement} element DOM element which is the root of angular application. + */ +function getTestability(rootElement) { + var injector = angular.element(rootElement).injector(); + if (!injector) { + throw ngMinErr('test', + 'no injector found for element argument to getTestability'); + } + return injector.get('$$testability'); } var SNAKE_CASE_REGEXP = /[A-Z]/g; @@ -1476,11 +1529,21 @@ function snake_case(name, separator) { }); } +var bindJQueryFired = false; +var skipDestroyOnNextJQueryCleanData; function bindJQuery() { + var originalCleanData; + + if (bindJQueryFired) { + return; + } + // bind to jQuery if present; jQuery = window.jQuery; // Use jQuery if it exists with proper functionality, otherwise default to us. - // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support. + // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. + // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older + // versions. It will not work for sure with jQuery <1.7, though. if (jQuery && jQuery.fn.on) { jqLite = jQuery; extend(jQuery.fn, { @@ -1490,15 +1553,33 @@ function bindJQuery() { injector: JQLitePrototype.injector, inheritedData: JQLitePrototype.inheritedData }); - // Method signature: - // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) - jqLitePatchJQueryRemove('remove', true, true, false); - jqLitePatchJQueryRemove('empty', false, false, false); - jqLitePatchJQueryRemove('html', false, false, true); + + // All nodes removed from the DOM via various jQuery APIs like .remove() + // are passed through jQuery.cleanData. Monkey-patch this method to fire + // the $destroy event on all removed nodes. + originalCleanData = jQuery.cleanData; + jQuery.cleanData = function(elems) { + var events; + if (!skipDestroyOnNextJQueryCleanData) { + for (var i = 0, elem; (elem = elems[i]) != null; i++) { + events = jQuery._data(elem, "events"); + if (events && events.$destroy) { + jQuery(elem).triggerHandler('$destroy'); + } + } + } else { + skipDestroyOnNextJQueryCleanData = false; + } + originalCleanData(elems); + }; } else { jqLite = JQLite; } + angular.element = jqLite; + + // Prevent double-proxying. + bindJQueryFired = true; } /** @@ -1562,27 +1643,47 @@ function getter(obj, path, bindFnToScope) { /** * Return the DOM siblings between the first and last node in the given array. * @param {Array} array like object - * @returns {DOMElement} object containing the elements + * @returns {jqLite} jqLite collection containing the nodes */ -function getBlockElements(nodes) { - var startNode = nodes[0], - endNode = nodes[nodes.length - 1]; - if (startNode === endNode) { - return jqLite(startNode); - } - - var element = startNode; - var elements = [element]; +function getBlockNodes(nodes) { + // TODO(perf): just check if all items in `nodes` are siblings and if they are return the original + // collection, otherwise update the original collection. + var node = nodes[0]; + var endNode = nodes[nodes.length - 1]; + var blockNodes = [node]; do { - element = element.nextSibling; - if (!element) break; - elements.push(element); - } while (element !== endNode); + node = node.nextSibling; + if (!node) break; + blockNodes.push(node); + } while (node !== endNode); - return jqLite(elements); + return jqLite(blockNodes); } + +/** + * Creates a new object without a prototype. This object is useful for lookup without having to + * guard against prototypically inherited properties via hasOwnProperty. + * + * Related micro-benchmarks: + * - http://jsperf.com/object-create2 + * - http://jsperf.com/proto-map-lookup/2 + * - http://jsperf.com/for-in-vs-object-keys2 + * + * @returns {Object} + */ +function createMap() { + return Object.create(null); +} + +var NODE_TYPE_ELEMENT = 1; +var NODE_TYPE_ATTRIBUTE = 2; +var NODE_TYPE_TEXT = 3; +var NODE_TYPE_COMMENT = 8; +var NODE_TYPE_DOCUMENT = 9; +var NODE_TYPE_DOCUMENT_FRAGMENT = 11; + /** * @ngdoc type * @name angular.Module @@ -1682,15 +1783,19 @@ function setupModuleLoader(window) { /** @type {!Array.>} */ var invokeQueue = []; + /** @type {!Array.} */ + var configBlocks = []; + /** @type {!Array.} */ var runBlocks = []; - var config = invokeLater('$injector', 'invoke'); + var config = invokeLater('$injector', 'invoke', 'push', configBlocks); /** @type {angular.Module} */ var moduleInstance = { // Private state _invokeQueue: invokeQueue, + _configBlocks: configBlocks, _runBlocks: runBlocks, /** @@ -1801,7 +1906,7 @@ function setupModuleLoader(window) { * }) * ``` * - * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and + * See {@link ng.$animateProvider#register $animateProvider.register()} and * {@link ngAnimate ngAnimate module} for more information. */ animation: invokeLater('$animateProvider', 'register'), @@ -1810,10 +1915,17 @@ function setupModuleLoader(window) { * @ngdoc method * @name angular.Module#filter * @module ng - * @param {string} name Filter name. + * @param {string} name Filter name - this must be a valid angular expression identifier * @param {Function} filterFactory Factory function for creating new instance of filter. * @description * See {@link ng.$filterProvider#register $filterProvider.register()}. + * + *
    + * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. + * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace + * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores + * (`myapp_subsection_filterx`). + *
    */ filter: invokeLater('$filterProvider', 'register'), @@ -1851,7 +1963,7 @@ function setupModuleLoader(window) { * @description * Use this method to register work which needs to be performed on module loading. * For more about how to configure services, see - * {@link providers#providers_provider-recipe Provider Recipe}. + * {@link providers#provider-recipe Provider Recipe}. */ config: config, @@ -1875,7 +1987,7 @@ function setupModuleLoader(window) { config(configFn); } - return moduleInstance; + return moduleInstance; /** * @param {string} provider @@ -1883,9 +1995,10 @@ function setupModuleLoader(window) { * @param {String=} insertMethod * @returns {angular.Module} */ - function invokeLater(provider, method, insertMethod) { + function invokeLater(provider, method, insertMethod, queue) { + if (!queue) queue = invokeQueue; return function() { - invokeQueue[insertMethod || 'push']([provider, method, arguments]); + queue[insertMethod || 'push']([provider, method, arguments]); return moduleInstance; }; } @@ -1895,80 +2008,119 @@ function setupModuleLoader(window) { } +/* global: toDebugString: true */ + +function serializeObject(obj) { + var seen = []; + + return JSON.stringify(obj, function(key, val) { + val = toJsonReplacer(key, val); + if (isObject(val)) { + + if (seen.indexOf(val) >= 0) return '<>'; + + seen.push(val); + } + return val; + }); +} + +function toDebugString(obj) { + if (typeof obj === 'function') { + return obj.toString().replace(/ \{[\s\S]*$/, ''); + } else if (typeof obj === 'undefined') { + return 'undefined'; + } else if (typeof obj !== 'string') { + return serializeObject(obj); + } + return obj; +} + /* global angularModule: true, version: true, $LocaleProvider, $CompileProvider, - htmlAnchorDirective, - inputDirective, - inputDirective, - formDirective, - scriptDirective, - selectDirective, - styleDirective, - optionDirective, - ngBindDirective, - ngBindHtmlDirective, - ngBindTemplateDirective, - ngClassDirective, - ngClassEvenDirective, - ngClassOddDirective, - ngCspDirective, - ngCloakDirective, - ngControllerDirective, - ngFormDirective, - ngHideDirective, - ngIfDirective, - ngIncludeDirective, - ngIncludeFillContentDirective, - ngInitDirective, - ngNonBindableDirective, - ngPluralizeDirective, - ngRepeatDirective, - ngShowDirective, - ngStyleDirective, - ngSwitchDirective, - ngSwitchWhenDirective, - ngSwitchDefaultDirective, - ngOptionsDirective, - ngTranscludeDirective, - ngModelDirective, - ngListDirective, - ngChangeDirective, - requiredDirective, - requiredDirective, - ngValueDirective, - ngAttributeAliasDirectives, - ngEventDirectives, + htmlAnchorDirective, + inputDirective, + inputDirective, + formDirective, + scriptDirective, + selectDirective, + styleDirective, + optionDirective, + ngBindDirective, + ngBindHtmlDirective, + ngBindTemplateDirective, + ngClassDirective, + ngClassEvenDirective, + ngClassOddDirective, + ngCspDirective, + ngCloakDirective, + ngControllerDirective, + ngFormDirective, + ngHideDirective, + ngIfDirective, + ngIncludeDirective, + ngIncludeFillContentDirective, + ngInitDirective, + ngNonBindableDirective, + ngPluralizeDirective, + ngRepeatDirective, + ngShowDirective, + ngStyleDirective, + ngSwitchDirective, + ngSwitchWhenDirective, + ngSwitchDefaultDirective, + ngOptionsDirective, + ngTranscludeDirective, + ngModelDirective, + ngListDirective, + ngChangeDirective, + patternDirective, + patternDirective, + requiredDirective, + requiredDirective, + minlengthDirective, + minlengthDirective, + maxlengthDirective, + maxlengthDirective, + ngValueDirective, + ngModelOptionsDirective, + ngAttributeAliasDirectives, + ngEventDirectives, - $AnchorScrollProvider, - $AnimateProvider, - $BrowserProvider, - $CacheFactoryProvider, - $ControllerProvider, - $DocumentProvider, - $ExceptionHandlerProvider, - $FilterProvider, - $InterpolateProvider, - $IntervalProvider, - $HttpProvider, - $HttpBackendProvider, - $LocationProvider, - $LogProvider, - $ParseProvider, - $RootScopeProvider, - $QProvider, - $$SanitizeUriProvider, - $SceProvider, - $SceDelegateProvider, - $SnifferProvider, - $TemplateCacheProvider, - $TimeoutProvider, - $$RAFProvider, - $$AsyncCallbackProvider, - $WindowProvider + $AnchorScrollProvider, + $AnimateProvider, + $BrowserProvider, + $CacheFactoryProvider, + $ControllerProvider, + $DocumentProvider, + $ExceptionHandlerProvider, + $FilterProvider, + $InterpolateProvider, + $IntervalProvider, + $HttpProvider, + $HttpBackendProvider, + $LocationProvider, + $LogProvider, + $ParseProvider, + $RootScopeProvider, + $QProvider, + $$QProvider, + $$SanitizeUriProvider, + $SceProvider, + $SceDelegateProvider, + $SnifferProvider, + $TemplateCacheProvider, + $TemplateRequestProvider, + $$TestabilityProvider, + $TimeoutProvider, + $$RAFProvider, + $$AsyncCallbackProvider, + $WindowProvider, + $$jqLiteProvider */ @@ -1987,15 +2139,15 @@ function setupModuleLoader(window) { * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.2.28', // all of these placeholder strings will be replaced by grunt's + full: '1.3.20', // all of these placeholder strings will be replaced by grunt's major: 1, // package task - minor: 2, - dot: 28, - codeName: 'finnish-disembarkation' + minor: 3, + dot: 20, + codeName: 'shallow-translucence' }; -function publishExternalAPI(angular){ +function publishExternalAPI(angular) { extend(angular, { 'bootstrap': bootstrap, 'copy': copy, @@ -2022,8 +2174,10 @@ function publishExternalAPI(angular){ 'lowercase': lowercase, 'uppercase': uppercase, 'callbacks': {counter: 0}, + 'getTestability': getTestability, '$$minErr': minErr, - '$$csp': csp + '$$csp': csp, + 'reloadWithDebugInfo': reloadWithDebugInfo }); angularModule = setupModuleLoader(window); @@ -2075,9 +2229,16 @@ function publishExternalAPI(angular){ ngModel: ngModelDirective, ngList: ngListDirective, ngChange: ngChangeDirective, + pattern: patternDirective, + ngPattern: patternDirective, required: requiredDirective, ngRequired: requiredDirective, - ngValue: ngValueDirective + minlength: minlengthDirective, + ngMinlength: minlengthDirective, + maxlength: maxlengthDirective, + ngMaxlength: maxlengthDirective, + ngValue: ngValueDirective, + ngModelOptions: ngModelOptionsDirective }). directive({ ngInclude: ngIncludeFillContentDirective @@ -2102,23 +2263,39 @@ function publishExternalAPI(angular){ $parse: $ParseProvider, $rootScope: $RootScopeProvider, $q: $QProvider, + $$q: $$QProvider, $sce: $SceProvider, $sceDelegate: $SceDelegateProvider, $sniffer: $SnifferProvider, $templateCache: $TemplateCacheProvider, + $templateRequest: $TemplateRequestProvider, + $$testability: $$TestabilityProvider, $timeout: $TimeoutProvider, $window: $WindowProvider, $$rAF: $$RAFProvider, - $$asyncCallback : $$AsyncCallbackProvider + $$asyncCallback: $$AsyncCallbackProvider, + $$jqLite: $$jqLiteProvider }); } ]); } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + /* global JQLitePrototype: true, addEventListenerFn: true, removeEventListenerFn: true, - BOOLEAN_ATTR: true + BOOLEAN_ATTR: true, + ALIASED_ATTR: true, */ ////////////////////////////////// @@ -2142,7 +2319,7 @@ function publishExternalAPI(angular){ * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most * commonly needed functionality with the goal of having a very small footprint.
    * - * To use jQuery, simply load it before `DOMContentLoaded` event fired. + * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. * *
    **Note:** all element references in Angular are always wrapped with jQuery or * jqLite; they are never raw DOM references.
    @@ -2153,13 +2330,14 @@ function publishExternalAPI(angular){ * - [`addClass()`](http://api.jquery.com/addClass/) * - [`after()`](http://api.jquery.com/after/) * - [`append()`](http://api.jquery.com/append/) - * - [`attr()`](http://api.jquery.com/attr/) + * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData * - [`children()`](http://api.jquery.com/children/) - Does not support selectors * - [`clone()`](http://api.jquery.com/clone/) * - [`contents()`](http://api.jquery.com/contents/) - * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()` + * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'. * - [`data()`](http://api.jquery.com/data/) + * - [`detach()`](http://api.jquery.com/detach/) * - [`empty()`](http://api.jquery.com/empty/) * - [`eq()`](http://api.jquery.com/eq/) * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name @@ -2200,10 +2378,12 @@ function publishExternalAPI(angular){ * `'ngModel'`). * - `injector()` - retrieves the injector of the current element or its parent. * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current - * element or its parent. + * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to + * be enabled. * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the * current element. This getter should be used only on elements that contain a directive which starts a new isolate * scope. Calling `scope()` on this element always returns the original non-isolate scope. + * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. * @@ -2215,17 +2395,17 @@ JQLite.expando = 'ng339'; var jqCache = JQLite.cache = {}, jqId = 1, - addEventListenerFn = (window.document.addEventListener - ? function(element, type, fn) {element.addEventListener(type, fn, false);} - : function(element, type, fn) {element.attachEvent('on' + type, fn);}), - removeEventListenerFn = (window.document.removeEventListener - ? function(element, type, fn) {element.removeEventListener(type, fn, false); } - : function(element, type, fn) {element.detachEvent('on' + type, fn); }); + addEventListenerFn = function(element, type, fn) { + element.addEventListener(type, fn, false); + }, + removeEventListenerFn = function(element, type, fn) { + element.removeEventListener(type, fn, false); + }; /* * !!! This is an undocumented "private" function !!! */ -var jqData = JQLite._data = function(node) { +JQLite._data = function(node) { //jQuery always returns an object on cache miss return this.cache[node[this.expando]] || {}; }; @@ -2235,6 +2415,7 @@ function jqNextId() { return ++jqId; } var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; var MOZ_HACK_REGEXP = /^moz([A-Z])/; +var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; var jqLiteMinErr = minErr('jqLite'); /** @@ -2250,49 +2431,6 @@ function camelCase(name) { replace(MOZ_HACK_REGEXP, 'Moz$1'); } -///////////////////////////////////////////// -// jQuery mutation patch -// -// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a -// $destroy event on all DOM nodes being removed. -// -///////////////////////////////////////////// - -function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { - var originalJqFn = jQuery.fn[name]; - originalJqFn = originalJqFn.$original || originalJqFn; - removePatch.$original = originalJqFn; - jQuery.fn[name] = removePatch; - - function removePatch(param) { - // jshint -W040 - var list = filterElems && param ? [this.filter(param)] : [this], - fireEvent = dispatchThis, - set, setIndex, setLength, - element, childIndex, childLength, children; - - if (!getterIfNoArguments || param != null) { - while(list.length) { - set = list.shift(); - for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { - element = jqLite(set[setIndex]); - if (fireEvent) { - element.triggerHandler('$destroy'); - } else { - fireEvent = !fireEvent; - } - for(childIndex = 0, childLength = (children = element.children()).length; - childIndex < childLength; - childIndex++) { - list.push(jQuery(children[childIndex])); - } - } - } - } - return originalJqFn.apply(this, arguments); - } -} - var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; var HTML_REGEXP = /<|&#?\w+;/; var TAG_NAME_REGEXP = /<([\w:]+)/; @@ -2312,26 +2450,32 @@ wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; + function jqLiteIsTextNode(html) { return !HTML_REGEXP.test(html); } +function jqLiteAcceptsData(node) { + // The window object can accept data but has no nodeType + // Otherwise we are only interested in elements (1) and documents (9) + var nodeType = node.nodeType; + return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; +} + function jqLiteBuildFragment(html, context) { - var elem, tmp, tag, wrap, + var tmp, tag, wrap, fragment = context.createDocumentFragment(), - nodes = [], i, j, jj; + nodes = [], i; if (jqLiteIsTextNode(html)) { // Convert non-html into a text node nodes.push(context.createTextNode(html)); } else { - tmp = fragment.appendChild(context.createElement('div')); // Convert html into DOM nodes + tmp = tmp || fragment.appendChild(context.createElement("div")); tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); wrap = wrapMap[tag] || wrapMap._default; - tmp.innerHTML = '
     
    ' + - wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>") + wrap[2]; - tmp.removeChild(tmp.firstChild); + tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>") + wrap[2]; // Descend through wrappers to the right content i = wrap[0]; @@ -2339,7 +2483,7 @@ function jqLiteBuildFragment(html, context) { tmp = tmp.lastChild; } - for (j=0, jj=tmp.childNodes.length; j 0) { + return; + } } + + removeEventListenerFn(element, type, handle); + delete events[type]; }); } } function jqLiteRemoveData(element, name) { - var expandoId = element.ng339, - expandoStore = jqCache[expandoId]; + var expandoId = element.ng339; + var expandoStore = expandoId && jqCache[expandoId]; if (expandoStore) { if (name) { - delete jqCache[expandoId].data[name]; + delete expandoStore.data[name]; return; } if (expandoStore.handle) { - expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); + if (expandoStore.events.$destroy) { + expandoStore.handle({}, '$destroy'); + } jqLiteOff(element); } delete jqCache[expandoId]; @@ -2441,43 +2608,42 @@ function jqLiteRemoveData(element, name) { } } -function jqLiteExpandoStore(element, key, value) { - var expandoId = element.ng339, - expandoStore = jqCache[expandoId || -1]; - if (isDefined(value)) { - if (!expandoStore) { - element.ng339 = expandoId = jqNextId(); - expandoStore = jqCache[expandoId] = {}; - } - expandoStore[key] = value; - } else { - return expandoStore && expandoStore[key]; +function jqLiteExpandoStore(element, createIfNecessary) { + var expandoId = element.ng339, + expandoStore = expandoId && jqCache[expandoId]; + + if (createIfNecessary && !expandoStore) { + element.ng339 = expandoId = jqNextId(); + expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; } + + return expandoStore; } + function jqLiteData(element, key, value) { - var data = jqLiteExpandoStore(element, 'data'), - isSetter = isDefined(value), - keyDefined = !isSetter && isDefined(key), - isSimpleGetter = keyDefined && !isObject(key); + if (jqLiteAcceptsData(element)) { - if (!data && !isSimpleGetter) { - jqLiteExpandoStore(element, 'data', data = {}); - } + var isSimpleSetter = isDefined(value); + var isSimpleGetter = !isSimpleSetter && key && !isObject(key); + var massGetter = !key; + var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); + var data = expandoStore && expandoStore.data; - if (isSetter) { - data[key] = value; - } else { - if (keyDefined) { - if (isSimpleGetter) { - // don't create data in this case. - return data && data[key]; - } else { - extend(data, key); - } + if (isSimpleSetter) { // data('key', value) + data[key] = value; } else { - return data; + if (massGetter) { // data() + return data; + } else { + if (isSimpleGetter) { // data('key') + // don't force creation of expandoStore if it doesn't exist yet + return data && data[key]; + } else { // mass-setter: data({key1: val1, key2: val2}) + extend(data, key); + } + } } } } @@ -2485,7 +2651,7 @@ function jqLiteData(element, key, value) { function jqLiteHasClass(element, selector) { if (!element.getAttribute) return false; return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). - indexOf( " " + selector + " " ) > -1); + indexOf(" " + selector + " ") > -1); } function jqLiteRemoveClass(element, cssClasses) { @@ -2516,25 +2682,41 @@ function jqLiteAddClass(element, cssClasses) { } } + function jqLiteAddNodes(root, elements) { + // THIS CODE IS VERY HOT. Don't make changes without benchmarking. + if (elements) { - elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) - ? elements - : [ elements ]; - for(var i=0; i < elements.length; i++) { - root.push(elements[i]); + + // if a Node (the most common case) + if (elements.nodeType) { + root[root.length++] = elements; + } else { + var length = elements.length; + + // if an Array or NodeList and not a Window + if (typeof length === 'number' && elements.window !== elements) { + if (length) { + for (var i = 0; i < length; i++) { + root[root.length++] = elements[i]; + } + } + } else { + root[root.length++] = elements; + } } } } + function jqLiteController(element, name) { - return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); + return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); } function jqLiteInheritedData(element, name, value) { // if element is the document object work with the html element instead // this makes $(document).scope() possible - if(element.nodeType == 9) { + if (element.nodeType == NODE_TYPE_DOCUMENT) { element = element.documentElement; } var names = isArray(name) ? name : [name]; @@ -2547,19 +2729,37 @@ function jqLiteInheritedData(element, name, value) { // If dealing with a document fragment node with a host element, and no parent, use the host // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM // to lookup parent controllers. - element = element.parentNode || (element.nodeType === 11 && element.host); + element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); } } function jqLiteEmpty(element) { - for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { - jqLiteDealoc(childNodes[i]); - } + jqLiteDealoc(element, true); while (element.firstChild) { element.removeChild(element.firstChild); } } +function jqLiteRemove(element, keepData) { + if (!keepData) jqLiteDealoc(element); + var parent = element.parentNode; + if (parent) parent.removeChild(element); +} + + +function jqLiteDocumentLoaded(action, win) { + win = win || window; + if (win.document.readyState === 'complete') { + // Force the action to be run async for consistent behaviour + // from the action's point of view + // i.e. it will definitely not be in a $apply + win.setTimeout(action); + } else { + // No need to unbind this handler as load is only ever called once + jqLite(win).on('load', action); + } +} + ////////////////////////////////////////// // Functions which are declared directly. ////////////////////////////////////////// @@ -2573,8 +2773,8 @@ var JQLitePrototype = JQLite.prototype = { fn(); } - // check if document already is loaded - if (document.readyState === 'complete'){ + // check if document is already loaded + if (document.readyState === 'complete') { setTimeout(trigger); } else { this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 @@ -2586,7 +2786,7 @@ var JQLitePrototype = JQLite.prototype = { }, toString: function() { var value = []; - forEach(this, function(e){ value.push('' + e);}); + forEach(this, function(e) { value.push('' + e);}); return '[' + value.join(', ') + ']'; }, @@ -2611,15 +2811,27 @@ forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), }); var BOOLEAN_ELEMENTS = {}; forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { - BOOLEAN_ELEMENTS[uppercase(value)] = true; + BOOLEAN_ELEMENTS[value] = true; }); +var ALIASED_ATTR = { + 'ngMinlength': 'minlength', + 'ngMaxlength': 'maxlength', + 'ngMin': 'min', + 'ngMax': 'max', + 'ngPattern': 'pattern' +}; function getBooleanAttrName(element, name) { // check dom last since we will most likely fail on name var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; // booleanAttr is here twice to minimize DOM access - return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; + return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; +} + +function getAliasedAttrName(element, name) { + var nodeName = element.nodeName; + return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name]; } forEach({ @@ -2649,7 +2861,7 @@ forEach({ return jqLiteInheritedData(element, '$injector'); }, - removeAttr: function(element,name) { + removeAttr: function(element, name) { element.removeAttribute(name); }, @@ -2661,26 +2873,15 @@ forEach({ if (isDefined(value)) { element.style[name] = value; } else { - var val; - - if (msie <= 8) { - // this is some IE specific weirdness that jQuery 1.6.4 does not sure why - val = element.currentStyle && element.currentStyle[name]; - if (val === '') val = 'auto'; - } - - val = val || element.style[name]; - - if (msie <= 8) { - // jquery weirdness :-/ - val = (val === '') ? undefined : val; - } - - return val; + return element.style[name]; } }, - attr: function(element, name, value){ + attr: function(element, name, value) { + var nodeType = element.nodeType; + if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { + return; + } var lowercasedName = lowercase(name); if (BOOLEAN_ATTR[lowercasedName]) { if (isDefined(value)) { @@ -2693,7 +2894,7 @@ forEach({ } } else { return (element[name] || - (element.attributes.getNamedItem(name)|| noop).specified) + (element.attributes.getNamedItem(name) || noop).specified) ? lowercasedName : undefined; } @@ -2717,31 +2918,23 @@ forEach({ }, text: (function() { - var NODE_TYPE_TEXT_PROPERTY = []; - if (msie < 9) { - NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ - NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ - } else { - NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ - NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ - } getText.$dv = ''; return getText; function getText(element, value) { - var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; if (isUndefined(value)) { - return textProp ? element[textProp] : ''; + var nodeType = element.nodeType; + return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; } - element[textProp] = value; + element.textContent = value; } })(), val: function(element, value) { if (isUndefined(value)) { - if (nodeName_(element) === 'SELECT' && element.multiple) { + if (element.multiple && nodeName_(element) === 'select') { var result = []; - forEach(element.options, function (option) { + forEach(element.options, function(option) { if (option.selected) { result.push(option.value || option.text); } @@ -2757,14 +2950,12 @@ forEach({ if (isUndefined(value)) { return element.innerHTML; } - for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { - jqLiteDealoc(childNodes[i]); - } + jqLiteDealoc(element, true); element.innerHTML = value; }, empty: jqLiteEmpty -}, function(fn, name){ +}, function(fn, name) { /** * Properties: writes return selection, reads return first value */ @@ -2816,57 +3007,50 @@ forEach({ }); function createEventHandler(element, events) { - var eventHandler = function (event, type) { - if (!event.preventDefault) { - event.preventDefault = function() { - event.returnValue = false; //ie - }; - } - - if (!event.stopPropagation) { - event.stopPropagation = function() { - event.cancelBubble = true; //ie - }; - } - - if (!event.target) { - event.target = event.srcElement || document; - } - - if (isUndefined(event.defaultPrevented)) { - var prevent = event.preventDefault; - event.preventDefault = function() { - event.defaultPrevented = true; - prevent.call(event); - }; - event.defaultPrevented = false; - } - + var eventHandler = function(event, type) { + // jQuery specific api event.isDefaultPrevented = function() { - return event.defaultPrevented || event.returnValue === false; + return event.defaultPrevented; + }; + + var eventFns = events[type || event.type]; + var eventFnsLength = eventFns ? eventFns.length : 0; + + if (!eventFnsLength) return; + + if (isUndefined(event.immediatePropagationStopped)) { + var originalStopImmediatePropagation = event.stopImmediatePropagation; + event.stopImmediatePropagation = function() { + event.immediatePropagationStopped = true; + + if (event.stopPropagation) { + event.stopPropagation(); + } + + if (originalStopImmediatePropagation) { + originalStopImmediatePropagation.call(event); + } + }; + } + + event.isImmediatePropagationStopped = function() { + return event.immediatePropagationStopped === true; }; // Copy event handlers in case event handlers array is modified during execution. - var eventHandlersCopy = shallowCopy(events[type || event.type] || []); + if ((eventFnsLength > 1)) { + eventFns = shallowCopy(eventFns); + } - forEach(eventHandlersCopy, function(fn) { - fn.call(element, event); - }); - - // Remove monkey-patched methods (IE), - // as they would cause memory leaks in IE8. - if (msie <= 8) { - // IE7/8 does not allow to delete property on native object - event.preventDefault = null; - event.stopPropagation = null; - event.isDefaultPrevented = null; - } else { - // It shouldn't affect normal browsers (native methods are defined on prototype). - delete event.preventDefault; - delete event.stopPropagation; - delete event.isDefaultPrevented; + for (var i = 0; i < eventFnsLength; i++) { + if (!event.isImmediatePropagationStopped()) { + eventFns[i].call(element, event); + } } }; + + // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all + // events on `element` eventHandler.elem = element; return eventHandler; } @@ -2879,68 +3063,56 @@ function createEventHandler(element, events) { forEach({ removeData: jqLiteRemoveData, - dealoc: jqLiteDealoc, - - on: function onFn(element, type, fn, unsupported){ + on: function jqLiteOn(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); - var events = jqLiteExpandoStore(element, 'events'), - handle = jqLiteExpandoStore(element, 'handle'); + // Do not add event handlers to non-elements because they will not be cleaned up. + if (!jqLiteAcceptsData(element)) { + return; + } - if (!events) jqLiteExpandoStore(element, 'events', events = {}); - if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); + var expandoStore = jqLiteExpandoStore(element, true); + var events = expandoStore.events; + var handle = expandoStore.handle; - forEach(type.split(' '), function(type){ + if (!handle) { + handle = expandoStore.handle = createEventHandler(element, events); + } + + // http://jsperf.com/string-indexof-vs-split + var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; + var i = types.length; + + while (i--) { + type = types[i]; var eventFns = events[type]; if (!eventFns) { - if (type == 'mouseenter' || type == 'mouseleave') { - var contains = document.body.contains || document.body.compareDocumentPosition ? - function( a, b ) { - // jshint bitwise: false - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - events[type] = []; + events[type] = []; + if (type === 'mouseenter' || type === 'mouseleave') { // Refer to jQuery's implementation of mouseenter & mouseleave // Read about mouseenter and mouseleave: // http://www.quirksmode.org/js/events_mouse.html#link8 - var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; - onFn(element, eventmap[type], function(event) { + jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) { var target = this, related = event.relatedTarget; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !contains(target, related)) ){ + if (!related || (related !== target && !target.contains(related))) { handle(event, type); } }); } else { - addEventListenerFn(element, type, handle); - events[type] = []; + if (type !== '$destroy') { + addEventListenerFn(element, type, handle); + } } eventFns = events[type]; } eventFns.push(fn); - }); + } }, off: jqLiteOff, @@ -2961,7 +3133,7 @@ forEach({ replaceWith: function(element, replaceNode) { var index, parent = element.parentNode; jqLiteDealoc(element); - forEach(new JQLite(replaceNode), function(node){ + forEach(new JQLite(replaceNode), function(node) { if (index) { parent.insertBefore(node, index.nextSibling); } else { @@ -2973,8 +3145,8 @@ forEach({ children: function(element) { var children = []; - forEach(element.childNodes, function(element){ - if (element.nodeType === 1) + forEach(element.childNodes, function(element) { + if (element.nodeType === NODE_TYPE_ELEMENT) children.push(element); }); return children; @@ -2985,24 +3157,28 @@ forEach({ }, append: function(element, node) { - forEach(new JQLite(node), function(child){ - if (element.nodeType === 1 || element.nodeType === 11) { - element.appendChild(child); - } - }); + var nodeType = element.nodeType; + if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; + + node = new JQLite(node); + + for (var i = 0, ii = node.length; i < ii; i++) { + var child = node[i]; + element.appendChild(child); + } }, prepend: function(element, node) { - if (element.nodeType === 1) { + if (element.nodeType === NODE_TYPE_ELEMENT) { var index = element.firstChild; - forEach(new JQLite(node), function(child){ + forEach(new JQLite(node), function(child) { element.insertBefore(child, index); }); } }, wrap: function(element, wrapNode) { - wrapNode = jqLite(wrapNode)[0]; + wrapNode = jqLite(wrapNode).eq(0).clone()[0]; var parent = element.parentNode; if (parent) { parent.replaceChild(wrapNode, element); @@ -3010,18 +3186,21 @@ forEach({ wrapNode.appendChild(element); }, - remove: function(element) { - jqLiteDealoc(element); - var parent = element.parentNode; - if (parent) parent.removeChild(element); + remove: jqLiteRemove, + + detach: function(element) { + jqLiteRemove(element, true); }, after: function(element, newElement) { var index = element, parent = element.parentNode; - forEach(new JQLite(newElement), function(node){ + newElement = new JQLite(newElement); + + for (var i = 0, ii = newElement.length; i < ii; i++) { + var node = newElement[i]; parent.insertBefore(node, index.nextSibling); index = node; - }); + } }, addClass: jqLiteAddClass, @@ -3029,7 +3208,7 @@ forEach({ toggleClass: function(element, selector, condition) { if (selector) { - forEach(selector.split(' '), function(className){ + forEach(selector.split(' '), function(className) { var classCondition = condition; if (isUndefined(classCondition)) { classCondition = !jqLiteHasClass(element, className); @@ -3041,20 +3220,11 @@ forEach({ parent: function(element) { var parent = element.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; + return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; }, next: function(element) { - if (element.nextElementSibling) { - return element.nextElementSibling; - } - - // IE8 doesn't have nextElementSibling - var elm = element.nextSibling; - while (elm != null && elm.nodeType !== 1) { - elm = elm.nextSibling; - } - return elm; + return element.nextElementSibling; }, find: function(element, selector) { @@ -3071,14 +3241,17 @@ forEach({ var dummyEvent, eventFnsCopy, handlerArgs; var eventName = event.type || event; - var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; + var expandoStore = jqLiteExpandoStore(element); + var events = expandoStore && expandoStore.events; + var eventFns = events && events[eventName]; if (eventFns) { - // Create a dummy event to pass to the handlers dummyEvent = { preventDefault: function() { this.defaultPrevented = true; }, isDefaultPrevented: function() { return this.defaultPrevented === true; }, + stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, + isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, stopPropagation: noop, type: eventName, target: element @@ -3094,18 +3267,20 @@ forEach({ handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; forEach(eventFnsCopy, function(fn) { - fn.apply(element, handlerArgs); + if (!dummyEvent.isImmediatePropagationStopped()) { + fn.apply(element, handlerArgs); + } }); - } } -}, function(fn, name){ +}, function(fn, name) { /** * chaining functions */ JQLite.prototype[name] = function(arg1, arg2, arg3) { var value; - for(var i=0; i < this.length; i++) { + + for (var i = 0, ii = this.length; i < ii; i++) { if (isUndefined(value)) { value = fn(this[i], arg1, arg2, arg3); if (isDefined(value)) { @@ -3124,6 +3299,27 @@ forEach({ JQLite.prototype.unbind = JQLite.prototype.off; }); + +// Provider for private $$jqLite service +function $$jqLiteProvider() { + this.$get = function $$jqLite() { + return extend(JQLite, { + hasClass: function(node, classes) { + if (node.attr) node = node[0]; + return jqLiteHasClass(node, classes); + }, + addClass: function(node, classes) { + if (node.attr) node = node[0]; + return jqLiteAddClass(node, classes); + }, + removeClass: function(node, classes) { + if (node.attr) node = node[0]; + return jqLiteRemoveClass(node, classes); + } + }); + }; +} + /** * Computes a hash of an 'obj'. * Hash of a: @@ -3137,21 +3333,23 @@ forEach({ * The resulting string key is in 'type:hashKey' format. */ function hashKey(obj, nextUidFn) { - var objType = typeof obj, - key; + var key = obj && obj.$$hashKey; - if (objType == 'function' || (objType == 'object' && obj !== null)) { - if (typeof (key = obj.$$hashKey) == 'function') { - // must invoke on object to keep the right this + if (key) { + if (typeof key === 'function') { key = obj.$$hashKey(); - } else if (key === undefined) { - key = obj.$$hashKey = (nextUidFn || nextUid)(); } - } else { - key = obj; + return key; } - return objType + ':' + key; + var objType = typeof obj; + if (objType == 'function' || (objType == 'object' && obj !== null)) { + key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); + } else { + key = objType + ':' + obj; + } + + return key; } /** @@ -3205,9 +3403,10 @@ HashMap.prototype = { * Creates an injector object that can be used for retrieving services as well as for * dependency injection (see {@link guide/di dependency injection}). * - * @param {Array.} modules A list of module functions or their aliases. See - * {@link angular.module}. The `ng` module must be explicitly added. + * {@link angular.module}. The `ng` module must be explicitly added. + * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which + * disallows argument name annotation inference. * @returns {injector} Injector object. See {@link auto.$injector $injector}. * * @example @@ -3218,7 +3417,7 @@ HashMap.prototype = { * * // use the injector to kick off your application * // use the type inference to auto inject arguments, or use implicit injection - * $injector.invoke(function($rootScope, $compile, $document){ + * $injector.invoke(function($rootScope, $compile, $document) { * $compile($document)($rootScope); * $rootScope.$digest(); * }); @@ -3261,7 +3460,19 @@ var FN_ARG_SPLIT = /,/; var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; var $injectorMinErr = minErr('$injector'); -function annotate(fn) { + +function anonFn(fn) { + // For anonymous functions, showing at the very least the function signature can help in + // debugging. + var fnText = fn.toString().replace(STRIP_COMMENTS, ''), + args = fnText.match(FN_ARGS); + if (args) { + return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; + } + return 'fn'; +} + +function annotate(fn, strictDi, name) { var $inject, fnText, argDecl, @@ -3271,10 +3482,17 @@ function annotate(fn) { if (!($inject = fn.$inject)) { $inject = []; if (fn.length) { + if (strictDi) { + if (!isString(name) || !name) { + name = fn.name || anonFn(fn); + } + throw $injectorMinErr('strictdi', + '{0} is not using explicit annotation and cannot be invoked in strict mode', name); + } fnText = fn.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS); - forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ - arg.replace(FN_ARG, function(all, underscore, name){ + forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { + arg.replace(FN_ARG, function(all, underscore, name) { $inject.push(name); }); }); @@ -3308,7 +3526,7 @@ function annotate(fn) { * ```js * var $injector = angular.injector(); * expect($injector.get('$injector')).toBe($injector); - * expect($injector.invoke(function($injector){ + * expect($injector.invoke(function($injector) { * return $injector; * })).toBe($injector); * ``` @@ -3334,8 +3552,10 @@ function annotate(fn) { * ## Inference * * In JavaScript calling `toString()` on a function returns the function definition. The definition - * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with - * minification, and obfuscation tools since these tools change the argument names. + * can then be parsed and the function arguments can be extracted. This method of discovering + * annotations is disallowed when the injector is in strict mode. + * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the + * argument names. * * ## `$inject` Annotation * By adding an `$inject` property onto a function the injection parameters can be specified. @@ -3352,6 +3572,7 @@ function annotate(fn) { * Return an instance of the service. * * @param {string} name The name of the instance to retrieve. + * @param {string=} caller An optional string to provide the origin of the function call for error messages. * @return {*} The instance. */ @@ -3362,8 +3583,8 @@ function annotate(fn) { * @description * Invoke the method and supply the method arguments from the `$injector`. * - * @param {!Function} fn The function to invoke. Function parameters are injected according to the - * {@link guide/di $inject Annotation} rules. + * @param {Function|Array.} fn The injectable function to invoke. Function parameters are + * injected according to the {@link guide/di $inject Annotation} rules. * @param {Object=} self The `this` for the invoked method. * @param {Object=} locals Optional object. If preset then any argument names are read from this * object first, before the `$injector` is consulted. @@ -3420,6 +3641,8 @@ function annotate(fn) { * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); * ``` * + * You can disallow this method by using strict injection mode. + * * This method does not work with code minification / obfuscation. For this reason the following * annotation strategies are supported. * @@ -3472,6 +3695,8 @@ function annotate(fn) { * @param {Function|Array.} fn Function for which dependent service names need to * be retrieved as described above. * + * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. + * * @returns {Array.} The names of the services which the function requires. */ @@ -3626,8 +3851,8 @@ function annotate(fn) { * configure your service in a provider. * * @param {string} name The name of the instance. - * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand - * for `$provide.provider(name, {$get: $getFn})`. + * @param {Function|Array.} $getFn The injectable $getFn for the instance creation. + * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. * @returns {Object} registered provider instance * * @example @@ -3662,7 +3887,8 @@ function annotate(fn) { * as a type/class. * * @param {string} name The name of the instance. - * @param {Function} constructor A class (constructor function) that will be instantiated. + * @param {Function|Array.} constructor An injectable class (constructor function) + * that will be instantiated. * @returns {Object} registered provider instance * * @example @@ -3761,7 +3987,7 @@ function annotate(fn) { * object which replaces or wraps and delegates to the original service. * * @param {string} name The name of the service to decorate. - * @param {function()} decorator This function will be invoked when the service needs to be + * @param {Function|Array.} decorator This function will be invoked when the service needs to be * instantiated and should return the decorated service instance. The function is called using * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. * Local injection arguments: @@ -3781,7 +4007,8 @@ function annotate(fn) { */ -function createInjector(modulesToLoad) { +function createInjector(modulesToLoad, strictDi) { + strictDi = (strictDi === true); var INSTANTIATING = {}, providerSuffix = 'Provider', path = [], @@ -3797,14 +4024,17 @@ function createInjector(modulesToLoad) { } }, providerInjector = (providerCache.$injector = - createInternalInjector(providerCache, function() { + createInternalInjector(providerCache, function(serviceName, caller) { + if (angular.isString(caller)) { + path.push(caller); + } throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); })), instanceCache = {}, instanceInjector = (instanceCache.$injector = - createInternalInjector(instanceCache, function(servicename) { - var provider = providerInjector.get(servicename + providerSuffix); - return instanceInjector.invoke(provider.$get, provider); + createInternalInjector(instanceCache, function(serviceName, caller) { + var provider = providerInjector.get(serviceName + providerSuffix, caller); + return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); })); @@ -3837,7 +4067,21 @@ function createInjector(modulesToLoad) { return providerCache[name + providerSuffix] = provider_; } - function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } + function enforceReturnValue(name, factory) { + return function enforcedReturnValue() { + var result = instanceInjector.invoke(factory, this); + if (isUndefined(result)) { + throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); + } + return result; + }; + } + + function factory(name, factoryFn, enforce) { + return provider(name, { + $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn + }); + } function service(name, constructor) { return factory(name, ['$injector', function($injector) { @@ -3845,7 +4089,7 @@ function createInjector(modulesToLoad) { }]); } - function value(name, val) { return factory(name, valueFn(val)); } + function value(name, val) { return factory(name, valueFn(val), false); } function constant(name, value) { assertNotHasOwnProperty(name, 'constant'); @@ -3866,23 +4110,28 @@ function createInjector(modulesToLoad) { //////////////////////////////////// // Module Loading //////////////////////////////////// - function loadModules(modulesToLoad){ - var runBlocks = [], moduleFn, invokeQueue, i, ii; + function loadModules(modulesToLoad) { + var runBlocks = [], moduleFn; forEach(modulesToLoad, function(module) { if (loadedModules.get(module)) return; loadedModules.put(module, true); + function runInvokeQueue(queue) { + var i, ii; + for (i = 0, ii = queue.length; i < ii; i++) { + var invokeArgs = queue[i], + provider = providerInjector.get(invokeArgs[0]); + + provider[invokeArgs[1]].apply(provider, invokeArgs[2]); + } + } + try { if (isString(module)) { moduleFn = angularModule(module); runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); - - for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { - var invokeArgs = invokeQueue[i], - provider = providerInjector.get(invokeArgs[0]); - - provider[invokeArgs[1]].apply(provider, invokeArgs[2]); - } + runInvokeQueue(moduleFn._invokeQueue); + runInvokeQueue(moduleFn._configBlocks); } else if (isFunction(module)) { runBlocks.push(providerInjector.invoke(module)); } else if (isArray(module)) { @@ -3915,7 +4164,7 @@ function createInjector(modulesToLoad) { function createInternalInjector(cache, factory) { - function getService(serviceName) { + function getService(serviceName, caller) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorMinErr('cdep', 'Circular dependency found: {0}', @@ -3926,7 +4175,7 @@ function createInjector(modulesToLoad) { try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; - return cache[serviceName] = factory(serviceName); + return cache[serviceName] = factory(serviceName, caller); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; @@ -3938,13 +4187,18 @@ function createInjector(modulesToLoad) { } } - function invoke(fn, self, locals){ + function invoke(fn, self, locals, serviceName) { + if (typeof locals === 'string') { + serviceName = locals; + locals = null; + } + var args = [], - $inject = annotate(fn), + $inject = createInjector.$$annotate(fn, strictDi, serviceName), length, i, key; - for(i = 0, length = $inject.length; i < length; i++) { + for (i = 0, length = $inject.length; i < length; i++) { key = $inject[i]; if (typeof key !== 'string') { throw $injectorMinErr('itkn', @@ -3953,7 +4207,7 @@ function createInjector(modulesToLoad) { args.push( locals && locals.hasOwnProperty(key) ? locals[key] - : getService(key) + : getService(key, serviceName) ); } if (isArray(fn)) { @@ -3965,15 +4219,12 @@ function createInjector(modulesToLoad) { return fn.apply(self, args); } - function instantiate(Type, locals) { - var Constructor = function() {}, - instance, returnedValue; - + function instantiate(Type, locals, serviceName) { // Check if Type is annotated and use just the given function at n-1 as parameter // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); - Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; - instance = new Constructor(); - returnedValue = invoke(Type, instance, locals); + // Object creation: http://jsperf.com/create-constructor/2 + var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null); + var returnedValue = invoke(Type, instance, locals, serviceName); return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; } @@ -3982,7 +4233,7 @@ function createInjector(modulesToLoad) { invoke: invoke, instantiate: instantiate, get: getService, - annotate: annotate, + annotate: createInjector.$$annotate, has: function(name) { return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); } @@ -3990,54 +4241,15 @@ function createInjector(modulesToLoad) { } } +createInjector.$$annotate = annotate; + /** - * @ngdoc service - * @name $anchorScroll - * @kind function - * @requires $window - * @requires $location - * @requires $rootScope + * @ngdoc provider + * @name $anchorScrollProvider * * @description - * When called, it checks current value of `$location.hash()` and scrolls to the related element, - * according to rules specified in - * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). - * - * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. - * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. - * - * @example - - -
    - Go to bottom - You're at the bottom! -
    -
    - - function ScrollCtrl($scope, $location, $anchorScroll) { - $scope.gotoBottom = function (){ - // set the location.hash to the id of - // the element you wish to scroll to. - $location.hash('bottom'); - - // call $anchorScroll() - $anchorScroll(); - }; - } - - - #scrollArea { - height: 350px; - overflow: auto; - } - - #bottom { - display: block; - margin-top: 2000px; - } - -
    + * Use `$anchorScrollProvider` to disable automatic scrolling whenever + * {@link ng.$location#hash $location.hash()} changes. */ function $AnchorScrollProvider() { @@ -4060,43 +4272,236 @@ function $AnchorScrollProvider() { autoScrollingEnabled = false; }; + /** + * @ngdoc service + * @name $anchorScroll + * @kind function + * @requires $window + * @requires $location + * @requires $rootScope + * + * @description + * When called, it checks the current value of {@link ng.$location#hash $location.hash()} and + * scrolls to the related element, according to the rules specified in the + * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). + * + * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to + * match any anchor whenever it changes. This can be disabled by calling + * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. + * + * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a + * vertical scroll-offset (either fixed or dynamic). + * + * @property {(number|function|jqLite)} yOffset + * If set, specifies a vertical scroll-offset. This is often useful when there are fixed + * positioned elements at the top of the page, such as navbars, headers etc. + * + * `yOffset` can be specified in various ways: + * - **number**: A fixed number of pixels to be used as offset.

    + * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return + * a number representing the offset (in pixels).

    + * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from + * the top of the page to the element's bottom will be used as offset.
    + * **Note**: The element will be taken into account only as long as its `position` is set to + * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust + * their height and/or positioning according to the viewport's size. + * + *
    + *
    + * In order for `yOffset` to work properly, scrolling should take place on the document's root and + * not some child element. + *
    + * + * @example + + +
    + Go to bottom + You're at the bottom! +
    +
    + + angular.module('anchorScrollExample', []) + .controller('ScrollController', ['$scope', '$location', '$anchorScroll', + function ($scope, $location, $anchorScroll) { + $scope.gotoBottom = function() { + // set the location.hash to the id of + // the element you wish to scroll to. + $location.hash('bottom'); + + // call $anchorScroll() + $anchorScroll(); + }; + }]); + + + #scrollArea { + height: 280px; + overflow: auto; + } + + #bottom { + display: block; + margin-top: 2000px; + } + +
    + * + *
    + * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). + * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. + * + * @example + + + +
    + Anchor {{x}} of 5 +
    +
    + + angular.module('anchorScrollOffsetExample', []) + .run(['$anchorScroll', function($anchorScroll) { + $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels + }]) + .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', + function ($anchorScroll, $location, $scope) { + $scope.gotoAnchor = function(x) { + var newHash = 'anchor' + x; + if ($location.hash() !== newHash) { + // set the $location.hash to `newHash` and + // $anchorScroll will automatically scroll to it + $location.hash('anchor' + x); + } else { + // call $anchorScroll() explicitly, + // since $location.hash hasn't changed + $anchorScroll(); + } + }; + } + ]); + + + body { + padding-top: 50px; + } + + .anchor { + border: 2px dashed DarkOrchid; + padding: 10px 10px 200px 10px; + } + + .fixed-header { + background-color: rgba(0, 0, 0, 0.2); + height: 50px; + position: fixed; + top: 0; left: 0; right: 0; + } + + .fixed-header > a { + display: inline-block; + margin: 5px 15px; + } + +
    + */ this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { var document = $window.document; - // helper function to get first anchor from a NodeList - // can't use filter.filter, as it accepts only instances of Array - // and IE can't convert NodeList to an array using [].slice - // TODO(vojta): use filter if we change it to accept lists as well + // Helper function to get first anchor from a NodeList + // (using `Array#some()` instead of `angular#forEach()` since it's more performant + // and working in all supported browsers.) function getFirstAnchor(list) { var result = null; - forEach(list, function(element) { - if (!result && lowercase(element.nodeName) === 'a') result = element; + Array.prototype.some.call(list, function(element) { + if (nodeName_(element) === 'a') { + result = element; + return true; + } }); return result; } + function getYOffset() { + + var offset = scroll.yOffset; + + if (isFunction(offset)) { + offset = offset(); + } else if (isElement(offset)) { + var elem = offset[0]; + var style = $window.getComputedStyle(elem); + if (style.position !== 'fixed') { + offset = 0; + } else { + offset = elem.getBoundingClientRect().bottom; + } + } else if (!isNumber(offset)) { + offset = 0; + } + + return offset; + } + + function scrollTo(elem) { + if (elem) { + elem.scrollIntoView(); + + var offset = getYOffset(); + + if (offset) { + // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. + // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the + // top of the viewport. + // + // IF the number of pixels from the top of `elem` to the end of the page's content is less + // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some + // way down the page. + // + // This is often the case for elements near the bottom of the page. + // + // In such cases we do not need to scroll the whole `offset` up, just the difference between + // the top of the element and the offset, which is enough to align the top of `elem` at the + // desired position. + var elemTop = elem.getBoundingClientRect().top; + $window.scrollBy(0, elemTop - offset); + } + } else { + $window.scrollTo(0, 0); + } + } + function scroll() { var hash = $location.hash(), elm; // empty hash, scroll to the top of the page - if (!hash) $window.scrollTo(0, 0); + if (!hash) scrollTo(null); // element with given id - else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); + else if ((elm = document.getElementById(hash))) scrollTo(elm); // first anchor with given name :-D - else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); + else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); // no element and hash == 'top', scroll to the top of the page - else if (hash === 'top') $window.scrollTo(0, 0); + else if (hash === 'top') scrollTo(null); } // does not scroll when user clicks on anchor link that is currently on // (no url change, no $location.hash() change), browser native does scroll if (autoScrollingEnabled) { $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, - function autoScrollWatchAction() { - $rootScope.$evalAsync(scroll); + function autoScrollWatchAction(newVal, oldVal) { + // skip the initial scroll if $location.hash is empty + if (newVal === oldVal && newVal === '') return; + + jqLiteDocumentLoaded(function() { + $rootScope.$evalAsync(scroll); + }); }); } @@ -4179,16 +4584,81 @@ var $AnimateProvider = ['$provide', function($provide) { * @return {RegExp} The current CSS className expression value. If null then there is no expression value */ this.classNameFilter = function(expression) { - if(arguments.length === 1) { + if (arguments.length === 1) { this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; } return this.$$classNameFilter; }; - this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) { + this.$get = ['$$q', '$$asyncCallback', '$rootScope', function($$q, $$asyncCallback, $rootScope) { - function async(fn) { - fn && $$asyncCallback(fn); + var currentDefer; + + function runAnimationPostDigest(fn) { + var cancelFn, defer = $$q.defer(); + defer.promise.$$cancelFn = function ngAnimateMaybeCancel() { + cancelFn && cancelFn(); + }; + + $rootScope.$$postDigest(function ngAnimatePostDigest() { + cancelFn = fn(function ngAnimateNotifyComplete() { + defer.resolve(); + }); + }); + + return defer.promise; + } + + function resolveElementClasses(element, classes) { + var toAdd = [], toRemove = []; + + var hasClasses = createMap(); + forEach((element.attr('class') || '').split(/\s+/), function(className) { + hasClasses[className] = true; + }); + + forEach(classes, function(status, className) { + var hasClass = hasClasses[className]; + + // If the most recent class manipulation (via $animate) was to remove the class, and the + // element currently has the class, the class is scheduled for removal. Otherwise, if + // the most recent class manipulation (via $animate) was to add the class, and the + // element does not currently have the class, the class is scheduled to be added. + if (status === false && hasClass) { + toRemove.push(className); + } else if (status === true && !hasClass) { + toAdd.push(className); + } + }); + + return (toAdd.length + toRemove.length) > 0 && + [toAdd.length ? toAdd : null, toRemove.length ? toRemove : null]; + } + + function cachedClassManipulation(cache, classes, op) { + for (var i=0, ii = classes.length; i < ii; ++i) { + var className = classes[i]; + cache[className] = op; + } + } + + function asyncPromise() { + // only serve one instance of a promise in order to save CPU cycles + if (!currentDefer) { + currentDefer = $$q.defer(); + $$asyncCallback(function() { + currentDefer.resolve(); + currentDefer = null; + }); + } + return currentDefer.promise; + } + + function applyStyles(element, options) { + if (angular.isObject(options)) { + var styles = extend(options.from || {}, options.to || {}); + element.css(styles); + } } /** @@ -4209,32 +4679,32 @@ var $AnimateProvider = ['$provide', function($provide) { * page}. */ return { + animate: function(element, from, to) { + applyStyles(element, { from: from, to: to }); + return asyncPromise(); + }, /** * * @ngdoc method * @name $animate#enter * @kind function - * @description Inserts the element into the DOM either after the `after` element or within - * the `parent` element. Once complete, the done() callback will be fired (if provided). + * @description Inserts the element into the DOM either after the `after` element or + * as the first child within the `parent` element. When the function is called a promise + * is returned that will be resolved at a later time. * @param {DOMElement} element the element which will be inserted into the DOM * @param {DOMElement} parent the parent element which will append the element as * a child (if the after element is not present) * @param {DOMElement} after the sibling element which will append the element * after itself - * @param {Function=} done callback function that will be called after the element has been - * inserted into the DOM + * @param {object=} options an optional collection of styles that will be applied to the element. + * @return {Promise} the animation callback promise */ - enter : function(element, parent, after, done) { - if (after) { - after.after(element); - } else { - if (!parent || !parent[0]) { - parent = after.parent(); - } - parent.append(element); - } - async(done); + enter: function(element, parent, after, options) { + applyStyles(element, options); + after ? after.after(element) + : parent.prepend(element); + return asyncPromise(); }, /** @@ -4242,15 +4712,16 @@ var $AnimateProvider = ['$provide', function($provide) { * @ngdoc method * @name $animate#leave * @kind function - * @description Removes the element from the DOM. Once complete, the done() callback will be - * fired (if provided). + * @description Removes the element from the DOM. When the function is called a promise + * is returned that will be resolved at a later time. * @param {DOMElement} element the element which will be removed from the DOM - * @param {Function=} done callback function that will be called after the element has been - * removed from the DOM + * @param {object=} options an optional collection of options that will be applied to the element. + * @return {Promise} the animation callback promise */ - leave : function(element, done) { + leave: function(element, options) { + applyStyles(element, options); element.remove(); - async(done); + return asyncPromise(); }, /** @@ -4259,8 +4730,8 @@ var $AnimateProvider = ['$provide', function($provide) { * @name $animate#move * @kind function * @description Moves the position of the provided element within the DOM to be placed - * either after the `after` element or inside of the `parent` element. Once complete, the - * done() callback will be fired (if provided). + * either after the `after` element or inside of the `parent` element. When the function + * is called a promise is returned that will be resolved at a later time. * * @param {DOMElement} element the element which will be moved around within the * DOM @@ -4268,13 +4739,13 @@ var $AnimateProvider = ['$provide', function($provide) { * inserted into (if the after element is not present) * @param {DOMElement} after the sibling element where the element will be * positioned next to - * @param {Function=} done the callback function (if provided) that will be fired after the - * element has been moved to its new position + * @param {object=} options an optional collection of options that will be applied to the element. + * @return {Promise} the animation callback promise */ - move : function(element, parent, after, done) { + move: function(element, parent, after, options) { // Do not remove element before insert. Removing will cause data associated with the // element to be dropped. Insert will implicitly do the remove. - this.enter(element, parent, after, done); + return this.enter(element, parent, after, options); }, /** @@ -4282,22 +4753,28 @@ var $AnimateProvider = ['$provide', function($provide) { * @ngdoc method * @name $animate#addClass * @kind function - * @description Adds the provided className CSS class value to the provided element. Once - * complete, the done() callback will be fired (if provided). + * @description Adds the provided className CSS class value to the provided element. + * When the function is called a promise is returned that will be resolved at a later time. * @param {DOMElement} element the element which will have the className value * added to it * @param {string} className the CSS class which will be added to the element - * @param {Function=} done the callback function (if provided) that will be fired after the - * className value has been added to the element + * @param {object=} options an optional collection of options that will be applied to the element. + * @return {Promise} the animation callback promise */ - addClass : function(element, className, done) { - className = isString(className) ? - className : - isArray(className) ? className.join(' ') : ''; - forEach(element, function (element) { + addClass: function(element, className, options) { + return this.setClass(element, className, [], options); + }, + + $$addClassImmediately: function(element, className, options) { + element = jqLite(element); + className = !isString(className) + ? (isArray(className) ? className.join(' ') : '') + : className; + forEach(element, function(element) { jqLiteAddClass(element, className); }); - async(done); + applyStyles(element, options); + return asyncPromise(); }, /** @@ -4306,21 +4783,27 @@ var $AnimateProvider = ['$provide', function($provide) { * @name $animate#removeClass * @kind function * @description Removes the provided className CSS class value from the provided element. - * Once complete, the done() callback will be fired (if provided). + * When the function is called a promise is returned that will be resolved at a later time. * @param {DOMElement} element the element which will have the className value * removed from it * @param {string} className the CSS class which will be removed from the element - * @param {Function=} done the callback function (if provided) that will be fired after the - * className value has been removed from the element + * @param {object=} options an optional collection of options that will be applied to the element. + * @return {Promise} the animation callback promise */ - removeClass : function(element, className, done) { - className = isString(className) ? - className : - isArray(className) ? className.join(' ') : ''; - forEach(element, function (element) { + removeClass: function(element, className, options) { + return this.setClass(element, [], className, options); + }, + + $$removeClassImmediately: function(element, className, options) { + element = jqLite(element); + className = !isString(className) + ? (isArray(className) ? className.join(' ') : '') + : className; + forEach(element, function(element) { jqLiteRemoveClass(element, className); }); - async(done); + applyStyles(element, options); + return asyncPromise(); }, /** @@ -4329,28 +4812,75 @@ var $AnimateProvider = ['$provide', function($provide) { * @name $animate#setClass * @kind function * @description Adds and/or removes the given CSS classes to and from the element. - * Once complete, the done() callback will be fired (if provided). + * When the function is called a promise is returned that will be resolved at a later time. * @param {DOMElement} element the element which will have its CSS classes changed * removed from it * @param {string} add the CSS classes which will be added to the element * @param {string} remove the CSS class which will be removed from the element - * @param {Function=} done the callback function (if provided) that will be fired after the - * CSS classes have been set on the element + * @param {object=} options an optional collection of options that will be applied to the element. + * @return {Promise} the animation callback promise */ - setClass : function(element, add, remove, done) { - forEach(element, function (element) { - jqLiteAddClass(element, add); - jqLiteRemoveClass(element, remove); - }); - async(done); + setClass: function(element, add, remove, options) { + var self = this; + var STORAGE_KEY = '$$animateClasses'; + var createdCache = false; + element = jqLite(element); + + var cache = element.data(STORAGE_KEY); + if (!cache) { + cache = { + classes: {}, + options: options + }; + createdCache = true; + } else if (options && cache.options) { + cache.options = angular.extend(cache.options || {}, options); + } + + var classes = cache.classes; + + add = isArray(add) ? add : add.split(' '); + remove = isArray(remove) ? remove : remove.split(' '); + cachedClassManipulation(classes, add, true); + cachedClassManipulation(classes, remove, false); + + if (createdCache) { + cache.promise = runAnimationPostDigest(function(done) { + var cache = element.data(STORAGE_KEY); + element.removeData(STORAGE_KEY); + + // in the event that the element is removed before postDigest + // is run then the cache will be undefined and there will be + // no need anymore to add or remove and of the element classes + if (cache) { + var classes = resolveElementClasses(element, cache.classes); + if (classes) { + self.$$setClassImmediately(element, classes[0], classes[1], cache.options); + } + } + + done(); + }); + element.data(STORAGE_KEY, cache); + } + + return cache.promise; }, - enabled : noop + $$setClassImmediately: function(element, add, remove, options) { + add && this.$$addClassImmediately(element, add); + remove && this.$$removeClassImmediately(element, remove); + applyStyles(element, options); + return asyncPromise(); + }, + + enabled: noop, + cancel: noop }; }]; }]; -function $$AsyncCallbackProvider(){ +function $$AsyncCallbackProvider() { this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { return $$rAF.supported ? function(fn) { return $$rAF(fn); } @@ -4380,8 +4910,7 @@ function $$AsyncCallbackProvider(){ /** * @param {object} window The global window object. * @param {object} document jQuery wrapped document. - * @param {function()} XHR XMLHttpRequest constructor. - * @param {object} $log console.log or an object with the same interface. + * @param {object} $log window.console or an object with the same interface. * @param {object} $sniffer $sniffer service */ function Browser(window, document, $log, $sniffer) { @@ -4412,7 +4941,7 @@ function Browser(window, document, $log, $sniffer) { } finally { outstandingRequestCount--; if (outstandingRequestCount === 0) { - while(outstandingRequestCallbacks.length) { + while (outstandingRequestCallbacks.length) { try { outstandingRequestCallbacks.pop()(); } catch (e) { @@ -4423,6 +4952,11 @@ function Browser(window, document, $log, $sniffer) { } } + function getHash(url) { + var index = url.indexOf('#'); + return index === -1 ? '' : url.substr(index); + } + /** * @private * Note: this method is used only by scenario runner @@ -4433,7 +4967,7 @@ function Browser(window, document, $log, $sniffer) { // force browser to execute all pollFns - this is needed so that cookies and other pollers fire // at some deterministic time in respect to the test runner's actions. Leaving things up to the // regular poller would result in flaky tests. - forEach(pollFns, function(pollFn){ pollFn(); }); + forEach(pollFns, function(pollFn) { pollFn(); }); if (outstandingRequestCount === 0) { callback(); @@ -4475,7 +5009,7 @@ function Browser(window, document, $log, $sniffer) { */ function startPoller(interval, setTimeout) { (function check() { - forEach(pollFns, function(pollFn){ pollFn(); }); + forEach(pollFns, function(pollFn) { pollFn(); }); pollTimeout = setTimeout(check, interval); })(); } @@ -4484,10 +5018,14 @@ function Browser(window, document, $log, $sniffer) { // URL API ////////////////////////////////////////////////////////////// - var lastBrowserUrl = location.href, + var cachedState, lastHistoryState, + lastBrowserUrl = location.href, baseElement = document.find('base'), reloadLocation = null; + cacheState(); + lastHistoryState = cachedState; + /** * @name $browser#url * @@ -4505,37 +5043,53 @@ function Browser(window, document, $log, $sniffer) { * {@link ng.$location $location service} to change url. * * @param {string} url New url (when used as setter) - * @param {boolean=} replace Should new url replace current history record ? + * @param {boolean=} replace Should new url replace current history record? + * @param {object=} state object to use with pushState/replaceState */ - self.url = function(url, replace) { + self.url = function(url, replace, state) { + // In modern browsers `history.state` is `null` by default; treating it separately + // from `undefined` would cause `$browser.url('/foo')` to change `history.state` + // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. + if (isUndefined(state)) { + state = null; + } + // Android Browser BFCache causes location, history reference to become stale. if (location !== window.location) location = window.location; if (history !== window.history) history = window.history; // setter if (url) { - if (lastBrowserUrl == url) return; + var sameState = lastHistoryState === state; + + // Don't change anything if previous and current URLs and states match. This also prevents + // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. + // See https://github.com/angular/angular.js/commit/ffb2701 + if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { + return self; + } var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); lastBrowserUrl = url; + lastHistoryState = state; // Don't use history API if only the hash changed // due to a bug in IE10/IE11 which leads // to not firing a `hashchange` nor `popstate` event // in some cases (see #9143). - if (!sameBase && $sniffer.history) { - if (replace) history.replaceState(null, '', url); - else { - history.pushState(null, '', url); - // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 - baseElement.attr('href', baseElement.attr('href')); - } + if ($sniffer.history && (!sameBase || !sameState)) { + history[replace ? 'replaceState' : 'pushState'](state, '', url); + cacheState(); + // Do the assignment again so that those two variables are referentially identical. + lastHistoryState = cachedState; } else { - if (!sameBase) { + if (!sameBase || reloadLocation) { reloadLocation = url; } if (replace) { location.replace(url); - } else { + } else if (!sameBase) { location.href = url; + } else { + location.hash = getHash(url); } } return self; @@ -4548,15 +5102,59 @@ function Browser(window, document, $log, $sniffer) { } }; + /** + * @name $browser#state + * + * @description + * This method is a getter. + * + * Return history.state or null if history.state is undefined. + * + * @returns {object} state + */ + self.state = function() { + return cachedState; + }; + var urlChangeListeners = [], urlChangeInit = false; + function cacheStateAndFireUrlChange() { + cacheState(); + fireUrlChange(); + } + + function getCurrentState() { + try { + return history.state; + } catch (e) { + // MSIE can reportedly throw when there is no state (UNCONFIRMED). + } + } + + // This variable should be used *only* inside the cacheState function. + var lastCachedState = null; + function cacheState() { + // This should be the only place in $browser where `history.state` is read. + cachedState = getCurrentState(); + cachedState = isUndefined(cachedState) ? null : cachedState; + + // Prevent callbacks fo fire twice if both hashchange & popstate were fired. + if (equals(cachedState, lastCachedState)) { + cachedState = lastCachedState; + } + lastCachedState = cachedState; + } + function fireUrlChange() { - if (lastBrowserUrl == self.url()) return; + if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { + return; + } lastBrowserUrl = self.url(); + lastHistoryState = cachedState; forEach(urlChangeListeners, function(listener) { - listener(self.url()); + listener(self.url(), cachedState); }); } @@ -4589,11 +5187,9 @@ function Browser(window, document, $log, $sniffer) { // changed by push/replaceState // html5 history api - popstate event - if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); + if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); // hashchange event - if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); - // polling - else self.addPollFn(fireUrlChange); + jqLite(window).on('hashchange', cacheStateAndFireUrlChange); urlChangeInit = true; } @@ -4634,6 +5230,14 @@ function Browser(window, document, $log, $sniffer) { var lastCookieString = ''; var cookiePath = self.baseHref(); + function safeDecodeURIComponent(str) { + try { + return decodeURIComponent(str); + } catch (e) { + return str; + } + } + /** * @name $browser#cookies * @@ -4655,16 +5259,15 @@ function Browser(window, document, $log, $sniffer) { * @returns {Object} Hash of all cookies (if called without any parameter) */ self.cookies = function(name, value) { - /* global escape: false, unescape: false */ var cookieLength, cookieArray, cookie, i, index; if (name) { if (value === undefined) { - rawDocument.cookie = escape(name) + "=;path=" + cookiePath + + rawDocument.cookie = encodeURIComponent(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; } else { if (isString(value)) { - cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + + cookieLength = (rawDocument.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + ';path=' + cookiePath).length + 1; // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: @@ -4672,8 +5275,8 @@ function Browser(window, document, $log, $sniffer) { // - 20 cookies per unique domain // - 4096 bytes per cookie if (cookieLength > 4096) { - $log.warn("Cookie '"+ name + - "' possibly not set or overflowed because it was too large ("+ + $log.warn("Cookie '" + name + + "' possibly not set or overflowed because it was too large (" + cookieLength + " > 4096 bytes)!"); } } @@ -4688,12 +5291,12 @@ function Browser(window, document, $log, $sniffer) { cookie = cookieArray[i]; index = cookie.indexOf('='); if (index > 0) { //ignore nameless cookies - name = unescape(cookie.substring(0, index)); + name = safeDecodeURIComponent(cookie.substring(0, index)); // the first value that is seen for a cookie is the most // specific one. values for the same cookie name that // follow are for less specific paths. if (lastCookies[name] === undefined) { - lastCookies[name] = unescape(cookie.substring(index + 1)); + lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); } } } @@ -4751,9 +5354,9 @@ function Browser(window, document, $log, $sniffer) { } -function $BrowserProvider(){ +function $BrowserProvider() { this.$get = ['$window', '$log', '$sniffer', '$document', - function( $window, $log, $sniffer, $document){ + function($window, $log, $sniffer, $document) { return new Browser($window, $document, $log, $sniffer); }]; } @@ -5130,7 +5733,7 @@ function $CacheFactoryProvider() { * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, * element with ng-app attribute), otherwise the template will be ignored. * - * Adding via the $templateCache service: + * Adding via the `$templateCache` service: * * ```js * var myApp = angular.module('myApp', []); @@ -5158,6 +5761,17 @@ function $TemplateCacheProvider() { }]; } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! * * DOM-related variables: @@ -5219,9 +5833,11 @@ function $TemplateCacheProvider() { * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, * transclude: false, * restrict: 'A', + * templateNamespace: 'html', * scope: false, * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, - * controllerAs: 'stringAlias', + * controllerAs: 'stringIdentifier', + * bindToController: false, * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], * compile: function compile(tElement, tAttrs, transclude) { * return { @@ -5269,6 +5885,13 @@ function $TemplateCacheProvider() { * The directive definition object provides instructions to the {@link ng.$compile * compiler}. The attributes are: * + * #### `multiElement` + * When this property is set to true, the HTML compiler will collect DOM nodes between + * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them + * together as the directive elements. It is recommended that this feature be used on directives + * which are not strictly behavioural (such as {@link ngClick}), and which + * do not manipulate or replace child nodes (such as {@link ngInclude}). + * * #### `priority` * When there are multiple directives defined on a single DOM element, sometimes it * is necessary to specify the order in which the directives are applied. The `priority` is used @@ -5280,7 +5903,8 @@ function $TemplateCacheProvider() { * #### `terminal` * If set to true then the current `priority` will be the last set of directives * which will execute (any directives at the current priority will still execute - * as the order of execution on same `priority` is undefined). + * as the order of execution on same `priority` is undefined). Note that expressions + * and other directives used in the directive's template will also be excluded from execution. * * #### `scope` * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the @@ -5313,7 +5937,9 @@ function $TemplateCacheProvider() { * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You - * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. + * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If + * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use + * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). * * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. * If no `attr` name is specified then the attribute name is assumed to be the same as the @@ -5326,6 +5952,10 @@ function $TemplateCacheProvider() { * by calling the `localFn` as `localFn({amount: 22})`. * * + * #### `bindToController` + * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will + * allow a component to have its properties bound to the controller, rather than to scope. When the controller + * is instantiated, the initial values of the isolate scope bindings are already available. * * #### `controller` * Controller constructor function. The controller is instantiated before the @@ -5336,22 +5966,35 @@ function $TemplateCacheProvider() { * * `$scope` - Current scope associated with the element * * `$element` - Current element * * `$attrs` - Current attributes object for the element - * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. - * The scope can be overridden by an optional first argument. - * `function([scope], cloneLinkingFn)`. + * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: + * `function([scope], cloneLinkingFn, futureParentElement)`. + * * `scope`: optional argument to override the scope. + * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content. + * * `futureParentElement`: + * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. + * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. + * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) + * and when the `cloneLinkinFn` is passed, + * as those elements need to created and cloned in a special way when they are defined outside their + * usual containers (e.g. like ``). + * * See also the `directive.templateNamespace` property. * * * #### `require` * Require another directive and inject its controller as the fourth argument to the linking function. The * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the * injected argument will be an array in corresponding order. If no such directive can be - * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: + * found, or if the directive does not have a controller, then an error is raised (unless no link function + * is specified, in which case error checking is skipped). The name can be prefixed with: * * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. + * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass * `null` to the `link` fn if not found. + * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass + * `null` to the `link` fn if not found. * * * #### `controllerAs` @@ -5362,14 +6005,26 @@ function $TemplateCacheProvider() { * * #### `restrict` * String of subset of `EACM` which restricts the directive to a specific directive - * declaration style. If omitted, the default (attributes only) is used. + * declaration style. If omitted, the defaults (elements and attributes) are used. * - * * `E` - Element name: `` + * * `E` - Element name (default): `` * * `A` - Attribute (default): `
    ` * * `C` - Class: `
    ` * * `M` - Comment: `` * * + * #### `templateNamespace` + * String representing the document type used by the markup in the template. + * AngularJS needs this information as those elements need to be created and cloned + * in a special way when they are defined outside their usual containers like `` and ``. + * + * * `html` - All root nodes in the template are HTML. Root nodes may also be + * top-level elements such as `` or ``. + * * `svg` - The root nodes in the template are SVG elements (excluding ``). + * * `math` - The root nodes in the template are MathML elements (excluding ``). + * + * If no `templateNamespace` is specified, then the namespace is considered to be `html`. + * * #### `template` * HTML markup that may: * * Replace the contents of the directive's element (default). @@ -5384,34 +6039,42 @@ function $TemplateCacheProvider() { * * * #### `templateUrl` - * Same as `template` but the template is loaded from the specified URL. Because - * the template loading is asynchronous the compilation/linking is suspended until the template - * is loaded. + * This is similar to `template` but the template is loaded from the specified URL, asynchronously. + * + * Because template loading is asynchronous the compiler will suspend compilation of directives on that element + * for later when the template has been resolved. In the meantime it will continue to compile and link + * sibling and parent elements as though this element had not contained any directives. + * + * The compiler does not suspend the entire compilation to wait for templates to be loaded because this + * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the + * case when only one deeply nested directive has `templateUrl`. + * + * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} * * You can specify `templateUrl` as a string representing the URL or as a function which takes two * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns * a string value representing the url. In either case, the template URL is passed through {@link - * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. + * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. * * - * #### `replace` ([*DEPRECATED*!], will be removed in next major release) + * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) * specify what the template should replace. Defaults to `false`. * * * `true` - the template will replace the directive's element. * * `false` - the template will replace the contents of the directive's element. * * The replacement process migrates all of the attributes / classes from the old element to the new - * one. See the {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive + * one. See the {@link guide/directive#template-expanding-directive * Directives Guide} for an example. * + * There are very few scenarios where element replacement is required for the application function, + * the main one being reusable custom components that are used within SVG contexts + * (because SVG doesn't work with custom elements in the DOM tree). + * * #### `transclude` - * compile the content of the element and make it available to the directive. - * Typically used with {@link ng.directive:ngTransclude - * ngTransclude}. The advantage of transclusion is that the linking function receives a - * transclusion function which is pre-bound to the correct scope. In a typical setup the widget - * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` - * scope. This makes it possible for the widget to have private state, and the transclusion to - * be bound to the parent (pre-`isolate`) scope. + * Extract the contents of the element where the directive appears and make it available to the directive. + * The contents are compiled and provided to the directive as a **transclusion function**. See the + * {@link $compile#transclusion Transclusion} section below. * * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the * directive's element or the entire element: @@ -5421,11 +6084,6 @@ function $TemplateCacheProvider() { * element that defined at a lower priority than this directive. When used, the `template` * property is ignored. * - *
    - * **Note:** When testing an element transclude directive you must not place the directive at the root of the - * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives - * Testing Transclusion Directives}. - *
    * * #### `compile` * @@ -5498,15 +6156,20 @@ function $TemplateCacheProvider() { * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared * between all directive linking functions. * - * * `controller` - a controller instance - A controller instance if at least one directive on the - * element defines a controller. The controller is shared among all the directives, which allows - * the directives to use the controllers as a communication channel. + * * `controller` - the directive's required controller instance(s) - Instances are shared + * among all directives, which allows the directives to use the controllers as a communication + * channel. The exact value depends on the directive's `require` property: + * * `string`: the controller instance + * * `array`: array of controller instances + * * no controller(s) required: `undefined` + * + * If a required controller cannot be found, and it is optional, the instance is `null`, + * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. * * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. - * The scope can be overridden by an optional first argument. This is the same as the `$transclude` - * parameter of directive controllers. - * `function([scope], cloneLinkingFn)`. - * + * This is the same as the `$transclude` + * parameter of directive controllers, see there for details. + * `function([scope], cloneLinkingFn, futureParentElement)`. * * #### Pre-linking function * @@ -5515,9 +6178,130 @@ function $TemplateCacheProvider() { * * #### Post-linking function * - * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. + * Executed after the child elements are linked. + * + * Note that child elements that contain `templateUrl` directives will not have been compiled + * and linked since they are waiting for their template to load asynchronously and their own + * compilation and linking has been suspended until that occurs. + * + * It is safe to do DOM transformation in the post-linking function on elements that are not waiting + * for their async templates to be resolved. + * + * + * ### Transclusion + * + * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and + * copying them to another part of the DOM, while maintaining their connection to the original AngularJS + * scope from where they were taken. + * + * Transclusion is used (often with {@link ngTransclude}) to insert the + * original contents of a directive's element into a specified place in the template of the directive. + * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded + * content has access to the properties on the scope from which it was taken, even if the directive + * has isolated scope. + * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. + * + * This makes it possible for the widget to have private state for its template, while the transcluded + * content has access to its originating scope. + * + *
    + * **Note:** When testing an element transclude directive you must not place the directive at the root of the + * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives + * Testing Transclusion Directives}. + *
    + * + * #### Transclusion Functions + * + * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion + * function** to the directive's `link` function and `controller`. This transclusion function is a special + * **linking function** that will return the compiled contents linked to a new transclusion scope. + * + *
    + * If you are just using {@link ngTransclude} then you don't need to worry about this function, since + * ngTransclude will deal with it for us. + *
    + * + * If you want to manually control the insertion and removal of the transcluded content in your directive + * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery + * object that contains the compiled DOM, which is linked to the correct transclusion scope. + * + * When you call a transclusion function you can pass in a **clone attach function**. This function accepts + * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded + * content and the `scope` is the newly created transclusion scope, to which the clone is bound. + * + *
    + * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function + * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. + *
    + * + * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone + * attach function**: + * + * ```js + * var transcludedContent, transclusionScope; + * + * $transclude(function(clone, scope) { + * element.append(clone); + * transcludedContent = clone; + * transclusionScope = scope; + * }); + * ``` + * + * Later, if you want to remove the transcluded content from your DOM then you should also destroy the + * associated transclusion scope: + * + * ```js + * transcludedContent.remove(); + * transclusionScope.$destroy(); + * ``` + * + *
    + * **Best Practice**: if you intend to add and remove transcluded content manually in your directive + * (by calling the transclude function to get the DOM and and calling `element.remove()` to remove it), + * then you are also responsible for calling `$destroy` on the transclusion scope. + *
    + * + * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} + * automatically destroy their transluded clones as necessary so you do not need to worry about this if + * you are simply using {@link ngTransclude} to inject the transclusion into your directive. + * + * + * #### Transclusion Scopes + * + * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion + * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed + * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it + * was taken. + * + * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look + * like this: + * + * ```html + *
    + *
    + *
    + *
    + *
    + *
    + * ``` + * + * The `$parent` scope hierarchy will look like this: + * + * ``` + * - $rootScope + * - isolate + * - transclusion + * ``` + * + * but the scopes will inherit prototypically from different scopes to their `$parent`. + * + * ``` + * - $rootScope + * - transclusion + * - isolate + * ``` + * * - * * ### Attributes * * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the @@ -5618,10 +6402,17 @@ function $TemplateCacheProvider() { * * * @param {string|DOMElement} element Element or HTML string to compile into a template function. - * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. + * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. + * + *
    + * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it + * e.g. will not use the right outer scope. Please pass the transclude function as a + * `parentBoundTranscludeFn` to the link function instead. + *
    + * * @param {number} maxPriority only apply directives lower than given priority (Only effects the * root element(s), not their children) - * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template + * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template * (a DOM element/tree) to a scope. Where: * * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. @@ -5633,6 +6424,19 @@ function $TemplateCacheProvider() { * * `clonedElement` - is a clone of the original `element` passed into the compiler. * * `scope` - is the current scope with which the linking function is working with. * + * * `options` - An optional object hash with linking options. If `options` is provided, then the following + * keys may be used to control linking behavior: + * + * * `parentBoundTranscludeFn` - the transclude function made available to + * directives; if given, it will be passed through to the link functions of + * directives found in `element` during compilation. + * * `transcludeControllers` - an object hash with keys that map controller names + * to controller instances; if given, it will make the controllers + * available to directives. + * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add + * the cloned elements; only needed for transcludes that are allowed to contain non html + * elements (e.g. SVG elements). See also the directive.controller property. + * * Calling the linking function returns the element of the template. It is either the original * element passed in, or the clone of the element if the `cloneAttachFn` is provided. * @@ -5671,7 +6475,6 @@ var $compileMinErr = minErr('$compile'); /** * @ngdoc provider * @name $compileProvider - * @kind function * * @description */ @@ -5679,14 +6482,42 @@ $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; function $CompileProvider($provide, $$sanitizeUriProvider) { var hasDirectives = {}, Suffix = 'Directive', - COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, - CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/; + COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, + CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, + ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), + REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes // The assumption is that future DOM event attribute names will begin with // 'on' and be composed of only English letters. var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; + function parseIsolateBindings(scope, directiveName) { + var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; + + var bindings = {}; + + forEach(scope, function(definition, scopeName) { + var match = definition.match(LOCAL_REGEXP); + + if (!match) { + throw $compileMinErr('iscp', + "Invalid isolate scope definition for directive '{0}'." + + " Definition: {... {1}: '{2}' ...}", + directiveName, scopeName, definition); + } + + bindings[scopeName] = { + mode: match[1][0], + collection: match[2] === '*', + optional: match[3] === '?', + attrName: match[4] || scopeName + }; + }); + + return bindings; + } + /** * @ngdoc method * @name $compileProvider#directive @@ -5723,7 +6554,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { directive.index = index; directive.name = directive.name || name; directive.require = directive.require || (directive.controller && directive.name); - directive.restrict = directive.restrict || 'A'; + directive.restrict = directive.restrict || 'EA'; + if (isObject(directive.scope)) { + directive.$$isolateBindings = parseIsolateBindings(directive.scope, directive.name); + } directives.push(directive); } catch (e) { $exceptionHandler(e); @@ -5749,7 +6583,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during a[href] sanitization. * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * The sanitization is a security measure aimed at preventing XSS attacks via html links. * * Any url about to be assigned to a[href] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` @@ -5799,15 +6633,57 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } }; + /** + * @ngdoc method + * @name $compileProvider#debugInfoEnabled + * + * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the + * current debugInfoEnabled state + * @returns {*} current value if used as getter or itself (chaining) if used as setter + * + * @kind function + * + * @description + * Call this method to enable/disable various debug runtime information in the compiler such as adding + * binding information and a reference to the current scope on to DOM elements. + * If enabled, the compiler will add the following to DOM elements that have been bound to the scope + * * `ng-binding` CSS class + * * `$binding` data property containing an array of the binding expressions + * + * You may want to disable this in production for a significant performance boost. See + * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. + * + * The default value is true. + */ + var debugInfoEnabled = true; + this.debugInfoEnabled = function(enabled) { + if (isDefined(enabled)) { + debugInfoEnabled = enabled; + return this; + } + return debugInfoEnabled; + }; + this.$get = [ - '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', + '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', - function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, + function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { - var Attributes = function(element, attr) { + var Attributes = function(element, attributesToCopy) { + if (attributesToCopy) { + var keys = Object.keys(attributesToCopy); + var i, l, key; + + for (i = 0, l = keys.length; i < l; i++) { + key = keys[i]; + this[key] = attributesToCopy[key]; + } + } else { + this.$attr = {}; + } + this.$$element = element; - this.$attr = attr || {}; }; Attributes.prototype = { @@ -5840,8 +6716,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * * @param {string} classVal The className value that will be added to the element */ - $addClass : function(classVal) { - if(classVal && classVal.length > 0) { + $addClass: function(classVal) { + if (classVal && classVal.length > 0) { $animate.addClass(this.$$element, classVal); } }, @@ -5857,8 +6733,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * * @param {string} classVal The className value that will be removed from the element */ - $removeClass : function(classVal) { - if(classVal && classVal.length > 0) { + $removeClass: function(classVal) { + if (classVal && classVal.length > 0) { $animate.removeClass(this.$$element, classVal); } }, @@ -5875,16 +6751,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * @param {string} newClasses The current CSS className value * @param {string} oldClasses The former CSS className value */ - $updateClass : function(newClasses, oldClasses) { + $updateClass: function(newClasses, oldClasses) { var toAdd = tokenDifference(newClasses, oldClasses); - var toRemove = tokenDifference(oldClasses, newClasses); - - if(toAdd.length === 0) { - $animate.removeClass(this.$$element, toRemove); - } else if(toRemove.length === 0) { + if (toAdd && toAdd.length) { $animate.addClass(this.$$element, toAdd); - } else { - $animate.setClass(this.$$element, toAdd, toRemove); + } + + var toRemove = tokenDifference(oldClasses, newClasses); + if (toRemove && toRemove.length) { + $animate.removeClass(this.$$element, toRemove); } }, @@ -5902,13 +6777,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { //is set through this function since it may cause $updateClass to //become unstable. - var booleanKey = getBooleanAttrName(this.$$element[0], key), - normalizedVal, + var node = this.$$element[0], + booleanKey = getBooleanAttrName(node, key), + aliasedKey = getAliasedAttrName(node, key), + observer = key, nodeName; if (booleanKey) { this.$$element.prop(key, value); attrName = booleanKey; + } else if (aliasedKey) { + this[aliasedKey] = value; + observer = aliasedKey; } this[key] = value; @@ -5925,10 +6805,44 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { nodeName = nodeName_(this.$$element); - // sanitize a[href] and img[src] values - if ((nodeName === 'A' && key === 'href') || - (nodeName === 'IMG' && key === 'src')) { + if ((nodeName === 'a' && key === 'href') || + (nodeName === 'img' && key === 'src')) { + // sanitize a[href] and img[src] values this[key] = value = $$sanitizeUri(value, key === 'src'); + } else if (nodeName === 'img' && key === 'srcset') { + // sanitize img[srcset] values + var result = ""; + + // first check if there are spaces because it's not the same pattern + var trimmedSrcset = trim(value); + // ( 999x ,| 999w ,| ,|, ) + var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; + var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; + + // split srcset into tuple of uri and descriptor except for the last item + var rawUris = trimmedSrcset.split(pattern); + + // for each tuples + var nbrUrisWith2parts = Math.floor(rawUris.length / 2); + for (var i = 0; i < nbrUrisWith2parts; i++) { + var innerIdx = i * 2; + // sanitize the uri + result += $$sanitizeUri(trim(rawUris[innerIdx]), true); + // add the descriptor + result += (" " + trim(rawUris[innerIdx + 1])); + } + + // split the last item into uri and descriptor + var lastTuple = trim(rawUris[i * 2]).split(/\s/); + + // sanitize the last uri + result += $$sanitizeUri(trim(lastTuple[0]), true); + + // and add the last descriptor if any + if (lastTuple.length === 2) { + result += (" " + trim(lastTuple[1])); + } + this[key] = value = result; } if (writeAttr !== false) { @@ -5941,7 +6855,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // fire observers var $$observers = this.$$observers; - $$observers && forEach($$observers[key], function(fn) { + $$observers && forEach($$observers[observer], function(fn) { try { fn(value); } catch (e) { @@ -5966,25 +6880,39 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * @param {string} key Normalized key. (ie ngAttribute) . * @param {function(interpolatedValue)} fn Function that will be called whenever the interpolated value of the attribute changes. - * See the {@link guide/directive#Attributes Directives} guide for more info. - * @returns {function()} the `fn` parameter. + * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info. + * @returns {function()} Returns a deregistration function for this observer. */ $observe: function(key, fn) { var attrs = this, - $$observers = (attrs.$$observers || (attrs.$$observers = {})), + $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), listeners = ($$observers[key] || ($$observers[key] = [])); listeners.push(fn); $rootScope.$evalAsync(function() { - if (!listeners.$$inter) { + if (!listeners.$$inter && attrs.hasOwnProperty(key)) { // no one registered attribute interpolation function, so lets call it manually fn(attrs[key]); } }); - return fn; + + return function() { + arrayRemove(listeners, fn); + }; } }; + + function safeAddClass($element, className) { + try { + $element.addClass(className); + } catch (e) { + // ignore, since it means that we are trying to set class on + // SVG element, where class name is read-only. + } + } + + var startSymbol = $interpolate.startSymbol(), endSymbol = $interpolate.endSymbol(), denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') @@ -5994,6 +6922,30 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }, NG_ATTR_BINDING = /^ngAttr[A-Z]/; + compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { + var bindings = $element.data('$binding') || []; + + if (isArray(binding)) { + bindings = bindings.concat(binding); + } else { + bindings.push(binding); + } + + $element.data('$binding', bindings); + } : noop; + + compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { + safeAddClass($element, 'ng-binding'); + } : noop; + + compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { + var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; + $element.data(dataName, scope); + } : noop; + + compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { + safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); + } : noop; return compile; @@ -6008,48 +6960,74 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } // We can not compile top level text elements since text nodes can be merged and we will // not be able to attach scope data to them, so we will wrap them in - forEach($compileNodes, function(node, index){ - if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { - $compileNodes[index] = node = jqLite(node).wrap('').parent()[0]; + forEach($compileNodes, function(node, index) { + if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { + $compileNodes[index] = jqLite(node).wrap('').parent()[0]; } }); var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext); - safeAddClass($compileNodes, 'ng-scope'); - return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){ + compile.$$addScopeClass($compileNodes); + var namespace = null; + return function publicLinkFn(scope, cloneConnectFn, options) { assertArg(scope, 'scope'); - // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart - // and sometimes changes the structure of the DOM. - var $linkNode = cloneConnectFn - ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! - : $compileNodes; - forEach(transcludeControllers, function(instance, name) { - $linkNode.data('$' + name + 'Controller', instance); - }); + options = options || {}; + var parentBoundTranscludeFn = options.parentBoundTranscludeFn, + transcludeControllers = options.transcludeControllers, + futureParentElement = options.futureParentElement; - // Attach scope only to non-text nodes. - for(var i = 0, ii = $linkNode.length; i').append($compileNodes).html()) + ); + } else if (cloneConnectFn) { + // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart + // and sometimes changes the structure of the DOM. + $linkNode = JQLitePrototype.clone.call($compileNodes); + } else { + $linkNode = $compileNodes; + } + + if (transcludeControllers) { + for (var controllerName in transcludeControllers) { + $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); } } + compile.$$addScopeInfo($linkNode, scope); + if (cloneConnectFn) cloneConnectFn($linkNode, scope); if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); return $linkNode; }; } - function safeAddClass($element, className) { - try { - $element.addClass(className); - } catch(e) { - // ignore, since it means that we are trying to set class on - // SVG element, where class name is read-only. + function detectNamespaceForChildElements(parentElement) { + // TODO: Make this detect MathML as well... + var node = parentElement && parentElement[0]; + if (!node) { + return 'html'; + } else { + return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; } } @@ -6071,7 +7049,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) { var linkFns = [], - attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound; + attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; for (var i = 0; i < nodeList.length; i++) { attrs = new Attributes(); @@ -6086,7 +7064,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { : null; if (nodeLinkFn && nodeLinkFn.scope) { - safeAddClass(attrs.$$element, 'ng-scope'); + compile.$$addScopeClass(attrs.$$element); } childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || @@ -6098,8 +7076,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) && nodeLinkFn.transclude) : transcludeFn); - linkFns.push(nodeLinkFn, childLinkFn); - linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; + if (nodeLinkFn || childLinkFn) { + linkFns.push(i, nodeLinkFn, childLinkFn); + linkFnFound = true; + nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; + } + //use the previous context only for the first element in the virtual group previousCompileContext = null; } @@ -6108,30 +7090,42 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return linkFnFound ? compositeLinkFn : null; function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { - var nodeLinkFn, childLinkFn, node, childScope, i, ii, n, childBoundTranscludeFn; + var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; + var stableNodeList; - // copy nodeList so that linking doesn't break due to live list updates. - var nodeListLength = nodeList.length, - stableNodeList = new Array(nodeListLength); - for (i = 0; i < nodeListLength; i++) { - stableNodeList[i] = nodeList[i]; + + if (nodeLinkFnFound) { + // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our + // offsets don't get screwed up + var nodeListLength = nodeList.length; + stableNodeList = new Array(nodeListLength); + + // create a sparse array by only copying the elements which have a linkFn + for (i = 0; i < linkFns.length; i+=3) { + idx = linkFns[i]; + stableNodeList[idx] = nodeList[idx]; + } + } else { + stableNodeList = nodeList; } - for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) { - node = stableNodeList[n]; + for (i = 0, ii = linkFns.length; i < ii;) { + node = stableNodeList[linkFns[i++]]; nodeLinkFn = linkFns[i++]; childLinkFn = linkFns[i++]; if (nodeLinkFn) { if (nodeLinkFn.scope) { childScope = scope.$new(); - jqLite.data(node, '$scope', childScope); + compile.$$addScopeInfo(jqLite(node), childScope); } else { childScope = scope; } - if ( nodeLinkFn.transcludeOnThisElement ) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn); + if (nodeLinkFn.transcludeOnThisElement) { + childBoundTranscludeFn = createBoundTranscludeFn( + scope, nodeLinkFn.transclude, parentBoundTranscludeFn, + nodeLinkFn.elementTranscludeOnThisElement); } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { childBoundTranscludeFn = parentBoundTranscludeFn; @@ -6152,22 +7146,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } - function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { + function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) { - var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) { - var scopeCreated = false; + var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { if (!transcludedScope) { - transcludedScope = scope.$new(); + transcludedScope = scope.$new(false, containingScope); transcludedScope.$$transcluded = true; - scopeCreated = true; } - var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn); - if (scopeCreated) { - clone.on('$destroy', function() { transcludedScope.$destroy(); }); - } - return clone; + return transcludeFn(transcludedScope, cloneFn, { + parentBoundTranscludeFn: previousBoundTranscludeFn, + transcludeControllers: controllers, + futureParentElement: futureParentElement + }); }; return boundTranscludeFn; @@ -6189,11 +7181,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { match, className; - switch(nodeType) { - case 1: /* Element */ + switch (nodeType) { + case NODE_TYPE_ELEMENT: /* Element */ // use the node name: addDirective(directives, - directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); + directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); // iterate over the attributes for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, @@ -6202,39 +7194,46 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var attrEndName = false; attr = nAttrs[j]; - if (!msie || msie >= 8 || attr.specified) { - name = attr.name; - value = trim(attr.value); + name = attr.name; + value = trim(attr.value); - // support ngAttr attribute binding - ngAttrName = directiveNormalize(name); - if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { - name = snake_case(ngAttrName.substr(6), '-'); - } + // support ngAttr attribute binding + ngAttrName = directiveNormalize(name); + if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { + name = name.replace(PREFIX_REGEXP, '') + .substr(8).replace(/_(.)/g, function(match, letter) { + return letter.toUpperCase(); + }); + } - var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); + var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); + if (directiveIsMultiElement(directiveNName)) { if (ngAttrName === directiveNName + 'Start') { attrStartName = name; attrEndName = name.substr(0, name.length - 5) + 'end'; name = name.substr(0, name.length - 6); } - - nName = directiveNormalize(name.toLowerCase()); - attrsMap[nName] = name; - if (isNgAttr || !attrs.hasOwnProperty(nName)) { - attrs[nName] = value; - if (getBooleanAttrName(node, nName)) { - attrs[nName] = true; // presence means true - } - } - addAttrInterpolateDirective(node, directives, value, nName); - addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, - attrEndName); } + + nName = directiveNormalize(name.toLowerCase()); + attrsMap[nName] = name; + if (isNgAttr || !attrs.hasOwnProperty(nName)) { + attrs[nName] = value; + if (getBooleanAttrName(node, nName)) { + attrs[nName] = true; // presence means true + } + } + addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); + addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, + attrEndName); } // use class as directive className = node.className; + if (isObject(className)) { + // Maybe SVGAnimatedString + className = className.animVal; + } if (isString(className) && className !== '') { while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { nName = directiveNormalize(match[2]); @@ -6245,10 +7244,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } break; - case 3: /* Text Node */ + case NODE_TYPE_TEXT: /* Text Node */ addTextInterpolateDirective(directives, node.nodeValue); break; - case 8: /* Comment */ + case NODE_TYPE_COMMENT: /* Comment */ try { match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); if (match) { @@ -6281,14 +7280,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var nodes = []; var depth = 0; if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { - var startNode = node; do { if (!node) { throw $compileMinErr('uterdir', "Unterminated attribute, found '{0}' but no matching '{1}' found.", attrStart, attrEnd); } - if (node.nodeType == 1 /** Element **/) { + if (node.nodeType == NODE_TYPE_ELEMENT) { if (node.hasAttribute(attrStart)) depth++; if (node.hasAttribute(attrEnd)) depth--; } @@ -6348,6 +7346,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var terminalPriority = -Number.MAX_VALUE, newScopeDirective, controllerDirectives = previousCompileContext.controllerDirectives, + controllers, newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, templateDirective = previousCompileContext.templateDirective, nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, @@ -6364,7 +7363,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { directiveValue; // executes all directives on the current element - for(var i = 0, ii = directives.length; i < ii; i++) { + for (var i = 0, ii = directives.length; i < ii; i++) { directive = directives[i]; var attrStart = directive.$$start; var attrEnd = directive.$$end; @@ -6380,17 +7379,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } if (directiveValue = directive.scope) { - newScopeDirective = newScopeDirective || directive; // skip the check for directives with async templates, we'll check the derived sync // directive when the template arrives if (!directive.templateUrl) { - assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, - $compileNode); if (isObject(directiveValue)) { + // This directive is trying to add an isolated scope. + // Check that there is no scope of any kind already + assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, + directive, $compileNode); newIsolateScopeDirective = directive; + } else { + // This directive is trying to add a child scope. + // Check that there is no isolated scope already + assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, + $compileNode); } } + + newScopeDirective = newScopeDirective || directive; } directiveName = directive.name; @@ -6458,11 +7465,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (jqLiteIsTextNode(directiveValue)) { $template = []; } else { - $template = jqLite(trim(directiveValue)); + $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); } compileNode = $template[0]; - if ($template.length != 1 || compileNode.nodeType !== 1) { + if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}", directiveName, ''); @@ -6531,6 +7538,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; + nodeLinkFn.elementTranscludeOnThisElement = hasElementTranscludeDirective; nodeLinkFn.templateOnThisElement = hasTemplate; nodeLinkFn.transclude = childTranscludeFn; @@ -6565,27 +7573,41 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function getControllers(directiveName, require, $element, elementControllers) { var value, retrievalMethod = 'data', optional = false; + var $searchElement = $element; + var match; if (isString(require)) { - while((value = require.charAt(0)) == '^' || value == '?') { - require = require.substr(1); - if (value == '^') { - retrievalMethod = 'inheritedData'; - } - optional = optional || value == '?'; + match = require.match(REQUIRE_PREFIX_REGEXP); + require = require.substring(match[0].length); + + if (match[3]) { + if (match[1]) match[3] = null; + else match[1] = match[3]; } + if (match[1] === '^') { + retrievalMethod = 'inheritedData'; + } else if (match[1] === '^^') { + retrievalMethod = 'inheritedData'; + $searchElement = $element.parent(); + } + if (match[2] === '?') { + optional = true; + } + value = null; if (elementControllers && retrievalMethod === 'data') { - value = elementControllers[require]; + if (value = elementControllers[require]) { + value = value.instance; + } } - value = value || $element[retrievalMethod]('$' + require + 'Controller'); + value = value || $searchElement[retrievalMethod]('$' + require + 'Controller'); if (!value && !optional) { throw $compileMinErr('ctreq', "Controller '{0}', required by directive '{1}', can't be found!", require, directiveName); } - return value; + return value || null; } else if (isArray(require)) { value = []; forEach(require, function(require) { @@ -6597,104 +7619,32 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { - var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; + var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element, + attrs; - attrs = (compileNode === linkNode) - ? templateAttrs - : shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); - $element = attrs.$$element; + if (compileNode === linkNode) { + attrs = templateAttrs; + $element = templateAttrs.$$element; + } else { + $element = jqLite(linkNode); + attrs = new Attributes($element, templateAttrs); + } if (newIsolateScopeDirective) { - var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; - isolateScope = scope.$new(true); - - if (templateDirective && (templateDirective === newIsolateScopeDirective || - templateDirective === newIsolateScopeDirective.$$originalDirective)) { - $element.data('$isolateScope', isolateScope); - } else { - $element.data('$isolateScopeNoTemplate', isolateScope); - } - - - - safeAddClass($element, 'ng-isolate-scope'); - - forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { - var match = definition.match(LOCAL_REGEXP) || [], - attrName = match[3] || scopeName, - optional = (match[2] == '?'), - mode = match[1], // @, =, or & - lastValue, - parentGet, parentSet, compare; - - isolateScope.$$isolateBindings[scopeName] = mode + attrName; - - switch (mode) { - - case '@': - attrs.$observe(attrName, function(value) { - isolateScope[scopeName] = value; - }); - attrs.$$observers[attrName].$$scope = scope; - if( attrs[attrName] ) { - // If the attribute has been provided then we trigger an interpolation to ensure - // the value is there for use in the link fn - isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); - } - break; - - case '=': - if (optional && !attrs[attrName]) { - return; - } - parentGet = $parse(attrs[attrName]); - if (parentGet.literal) { - compare = equals; - } else { - compare = function(a,b) { return a === b || (a !== a && b !== b); }; - } - parentSet = parentGet.assign || function() { - // reset the change, or we will throw this exception on every $digest - lastValue = isolateScope[scopeName] = parentGet(scope); - throw $compileMinErr('nonassign', - "Expression '{0}' used with directive '{1}' is non-assignable!", - attrs[attrName], newIsolateScopeDirective.name); - }; - lastValue = isolateScope[scopeName] = parentGet(scope); - isolateScope.$watch(function parentValueWatch() { - var parentValue = parentGet(scope); - if (!compare(parentValue, isolateScope[scopeName])) { - // we are out of sync and need to copy - if (!compare(parentValue, lastValue)) { - // parent changed and it has precedence - isolateScope[scopeName] = parentValue; - } else { - // if the parent can be assigned then do so - parentSet(scope, parentValue = isolateScope[scopeName]); - } - } - return lastValue = parentValue; - }, null, parentGet.literal); - break; - - case '&': - parentGet = $parse(attrs[attrName]); - isolateScope[scopeName] = function(locals) { - return parentGet(scope, locals); - }; - break; - - default: - throw $compileMinErr('iscp', - "Invalid isolate scope definition for directive '{0}'." + - " Definition: {... {1}: '{2}' ...}", - newIsolateScopeDirective.name, scopeName, definition); - } - }); } - transcludeFn = boundTranscludeFn && controllersBoundTransclude; + + if (boundTranscludeFn) { + // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` + // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` + transcludeFn = controllersBoundTransclude; + transcludeFn.$$boundTransclude = boundTranscludeFn; + } + if (controllerDirectives) { + // TODO: merge `controllers` and `elementControllers` into single object. + controllers = {}; + elementControllers = {}; forEach(controllerDirectives, function(directive) { var locals = { $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, @@ -6708,7 +7658,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { controller = attrs[directive.name]; } - controllerInstance = $controller(controller, locals); + controllerInstance = $controller(controller, locals, true, directive.controllerAs); + // For directives with element transclusion the element is a comment, // but jQuery .data doesn't support attaching data to comment nodes as it's hard to // clean up (http://bugs.jquery.com/ticket/8335). @@ -6716,24 +7667,113 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // later, once we have the actual element. elementControllers[directive.name] = controllerInstance; if (!hasElementTranscludeDirective) { - $element.data('$' + directive.name + 'Controller', controllerInstance); + $element.data('$' + directive.name + 'Controller', controllerInstance.instance); } - if (directive.controllerAs) { - locals.$scope[directive.controllerAs] = controllerInstance; - } + controllers[directive.name] = controllerInstance; }); } - // PRELINKING - for(i = 0, ii = preLinkFns.length; i < ii; i++) { - try { - linkFn = preLinkFns[i]; - linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, - linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); - } catch (e) { - $exceptionHandler(e, startingTag($element)); + if (newIsolateScopeDirective) { + compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || + templateDirective === newIsolateScopeDirective.$$originalDirective))); + compile.$$addScopeClass($element, true); + + var isolateScopeController = controllers && controllers[newIsolateScopeDirective.name]; + var isolateBindingContext = isolateScope; + if (isolateScopeController && isolateScopeController.identifier && + newIsolateScopeDirective.bindToController === true) { + isolateBindingContext = isolateScopeController.instance; } + + forEach(isolateScope.$$isolateBindings = newIsolateScopeDirective.$$isolateBindings, function(definition, scopeName) { + var attrName = definition.attrName, + optional = definition.optional, + mode = definition.mode, // @, =, or & + lastValue, + parentGet, parentSet, compare; + + switch (mode) { + + case '@': + attrs.$observe(attrName, function(value) { + isolateBindingContext[scopeName] = value; + }); + attrs.$$observers[attrName].$$scope = scope; + if (attrs[attrName]) { + // If the attribute has been provided then we trigger an interpolation to ensure + // the value is there for use in the link fn + isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope); + } + break; + + case '=': + if (optional && !attrs[attrName]) { + return; + } + parentGet = $parse(attrs[attrName]); + if (parentGet.literal) { + compare = equals; + } else { + compare = function(a, b) { return a === b || (a !== a && b !== b); }; + } + parentSet = parentGet.assign || function() { + // reset the change, or we will throw this exception on every $digest + lastValue = isolateBindingContext[scopeName] = parentGet(scope); + throw $compileMinErr('nonassign', + "Expression '{0}' used with directive '{1}' is non-assignable!", + attrs[attrName], newIsolateScopeDirective.name); + }; + lastValue = isolateBindingContext[scopeName] = parentGet(scope); + var parentValueWatch = function parentValueWatch(parentValue) { + if (!compare(parentValue, isolateBindingContext[scopeName])) { + // we are out of sync and need to copy + if (!compare(parentValue, lastValue)) { + // parent changed and it has precedence + isolateBindingContext[scopeName] = parentValue; + } else { + // if the parent can be assigned then do so + parentSet(scope, parentValue = isolateBindingContext[scopeName]); + } + } + return lastValue = parentValue; + }; + parentValueWatch.$stateful = true; + var unwatch; + if (definition.collection) { + unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch); + } else { + unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); + } + isolateScope.$on('$destroy', unwatch); + break; + + case '&': + parentGet = $parse(attrs[attrName]); + isolateBindingContext[scopeName] = function(locals) { + return parentGet(scope, locals); + }; + break; + } + }); + } + if (controllers) { + forEach(controllers, function(controller) { + controller(); + }); + controllers = null; + } + + // PRELINKING + for (i = 0, ii = preLinkFns.length; i < ii; i++) { + linkFn = preLinkFns[i]; + invokeLinkFn(linkFn, + linkFn.isolateScope ? isolateScope : scope, + $element, + attrs, + linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), + transcludeFn + ); } // RECURSION @@ -6746,22 +7786,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); // POSTLINKING - for(i = postLinkFns.length - 1; i >= 0; i--) { - try { - linkFn = postLinkFns[i]; - linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, - linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); - } catch (e) { - $exceptionHandler(e, startingTag($element)); - } + for (i = postLinkFns.length - 1; i >= 0; i--) { + linkFn = postLinkFns[i]; + invokeLinkFn(linkFn, + linkFn.isolateScope ? isolateScope : scope, + $element, + attrs, + linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), + transcludeFn + ); } // This is the function that is injected as `$transclude`. - function controllersBoundTransclude(scope, cloneAttachFn) { + // Note: all arguments are optional! + function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { var transcludeControllers; - // no scope passed - if (arguments.length < 2) { + // No scope passed in: + if (!isScope(scope)) { + futureParentElement = cloneAttachFn; cloneAttachFn = scope; scope = undefined; } @@ -6769,8 +7812,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (hasElementTranscludeDirective) { transcludeControllers = elementControllers; } - - return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); + if (!futureParentElement) { + futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; + } + return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); } } } @@ -6801,11 +7846,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (name === ignoreDirective) return null; var match = null; if (hasDirectives.hasOwnProperty(name)) { - for(var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i directive.priority) && + if ((maxPriority === undefined || maxPriority > directive.priority) && directive.restrict.indexOf(location) != -1) { if (startAttrName) { directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); @@ -6813,13 +7858,34 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { tDirectives.push(directive); match = directive; } - } catch(e) { $exceptionHandler(e); } + } catch (e) { $exceptionHandler(e); } } } return match; } + /** + * looks up the directive and returns true if it is a multi-element directive, + * and therefore requires DOM nodes between -start and -end markers to be grouped + * together. + * + * @param {string} name name of the directive to look up. + * @returns true if directive was registered as multi-element. + */ + function directiveIsMultiElement(name) { + if (hasDirectives.hasOwnProperty(name)) { + for (var directive, directives = $injector.get(name + Suffix), + i = 0, ii = directives.length; i < ii; i++) { + directive = directives[i]; + if (directive.multiElement) { + return true; + } + } + } + return false; + } + /** * When the element is replaced with HTML template then the new attributes * on the template need to be merged with the existing attributes in the DOM. @@ -6869,18 +7935,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { afterTemplateChildLinkFn, beforeTemplateCompileNode = $compileNode[0], origAsyncDirective = directives.shift(), - // The fact that we have to copy and patch the directive seems wrong! - derivedSyncDirective = extend({}, origAsyncDirective, { + derivedSyncDirective = inherit(origAsyncDirective, { templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective }), templateUrl = (isFunction(origAsyncDirective.templateUrl)) ? origAsyncDirective.templateUrl($compileNode, tAttrs) - : origAsyncDirective.templateUrl; + : origAsyncDirective.templateUrl, + templateNamespace = origAsyncDirective.templateNamespace; $compileNode.empty(); - $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). - success(function(content) { + $templateRequest(templateUrl) + .then(function(content) { var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; content = denormalizeTemplate(content); @@ -6889,11 +7955,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (jqLiteIsTextNode(content)) { $template = []; } else { - $template = jqLite(trim(content)); + $template = removeComments(wrapTemplate(templateNamespace, trim(content))); } compileNode = $template[0]; - if ($template.length != 1 || compileNode.nodeType !== 1) { + if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}", origAsyncDirective.name, templateUrl); @@ -6925,13 +7991,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }); afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); - while(linkQueue.length) { + while (linkQueue.length) { var scope = linkQueue.shift(), beforeTemplateLinkNode = linkQueue.shift(), linkRootElement = linkQueue.shift(), boundTranscludeFn = linkQueue.shift(), linkNode = $compileNode[0]; + if (scope.$$destroyed) continue; + if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { var oldClasses = beforeTemplateLinkNode.className; @@ -6940,7 +8008,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // it was cloned therefore we have to clone as well. linkNode = jqLiteClone(compileNode); } - replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); // Copy in CSS classes from original node @@ -6955,18 +8022,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { childBoundTranscludeFn); } linkQueue = null; - }). - error(function(response, code, headers, config) { - throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); }); return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { var childBoundTranscludeFn = boundTranscludeFn; + if (scope.$$destroyed) return; if (linkQueue) { - linkQueue.push(scope); - linkQueue.push(node); - linkQueue.push(rootElement); - linkQueue.push(childBoundTranscludeFn); + linkQueue.push(scope, + node, + rootElement, + childBoundTranscludeFn); } else { if (afterTemplateNodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); @@ -6996,31 +8061,45 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } - function addTextInterpolateDirective(directives, text) { - var interpolateFn = $interpolate(text, true); - if (interpolateFn) { - directives.push({ - priority: 0, - compile: function textInterpolateCompileFn(templateNode) { - // when transcluding a template that has bindings in the root - // then we don't have a parent and should do this in the linkFn - var parent = templateNode.parent(), hasCompileParent = parent.length; - if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding'); + function addTextInterpolateDirective(directives, text) { + var interpolateFn = $interpolate(text, true); + if (interpolateFn) { + directives.push({ + priority: 0, + compile: function textInterpolateCompileFn(templateNode) { + var templateNodeParent = templateNode.parent(), + hasCompileParent = !!templateNodeParent.length; - return function textInterpolateLinkFn(scope, node) { - var parent = node.parent(), - bindings = parent.data('$binding') || []; - bindings.push(interpolateFn); - parent.data('$binding', bindings); - if (!hasCompileParent) safeAddClass(parent, 'ng-binding'); - scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { - node[0].nodeValue = value; - }); - }; - } - }); - } + // When transcluding a template that has bindings in the root + // we don't have a parent and thus need to add the class during linking fn. + if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); + + return function textInterpolateLinkFn(scope, node) { + var parent = node.parent(); + if (!hasCompileParent) compile.$$addBindingClass(parent); + compile.$$addBindingInfo(parent, interpolateFn.expressions); + scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { + node[0].nodeValue = value; + }); + }; + } + }); } + } + + + function wrapTemplate(type, template) { + type = lowercase(type || 'html'); + switch (type) { + case 'svg': + case 'math': + var wrapper = document.createElement('div'); + wrapper.innerHTML = '<' + type + '>' + template + ''; + return wrapper.childNodes[0].childNodes; + default: + return template; + } + } function getTrustedContext(node, attrNormalizedName) { @@ -7030,22 +8109,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var tag = nodeName_(node); // maction[xlink:href] can source SVG. It's not limited to . if (attrNormalizedName == "xlinkHref" || - (tag == "FORM" && attrNormalizedName == "action") || - (tag != "IMG" && (attrNormalizedName == "src" || + (tag == "form" && attrNormalizedName == "action") || + (tag != "img" && (attrNormalizedName == "src" || attrNormalizedName == "ngSrc"))) { return $sce.RESOURCE_URL; } } - function addAttrInterpolateDirective(node, directives, value, name) { - var interpolateFn = $interpolate(value, true); + function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { + var trustedContext = getTrustedContext(node, name); + allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; + + var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); // no interpolation found -> ignore if (!interpolateFn) return; - if (name === "multiple" && nodeName_(node) === "SELECT") { + if (name === "multiple" && nodeName_(node) === "select") { throw $compileMinErr("selmulti", "Binding to the 'multiple' attribute is not supported. Element: {0}", startingTag(node)); @@ -7064,17 +8146,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { "ng- versions (such as ng-click instead of onclick) instead."); } - // we need to interpolate again, in case the attribute value has been updated - // (e.g. by another directive's compile function) - interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); + // If the attribute has changed since last $interpolate()ed + var newValue = attr[name]; + if (newValue !== value) { + // we need to interpolate again since the attribute value has been updated + // (e.g. by another directive's compile function) + // ensure unset/empty values make interpolateFn falsy + interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); + value = newValue; + } // if attribute was updated so that there is no interpolation going on we don't want to // register any observers if (!interpolateFn) return; - // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the - // actual attr value + // initialize attr object so that it's ready in case we need the value for isolate + // scope initialization, otherwise the value would not be available from isolate + // directive's linking fn during linking phase attr[name] = interpolateFn(scope); + ($$observers[name] || ($$observers[name] = [])).$$inter = true; (attr.$$observers && attr.$$observers[name].$$scope || scope). $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { @@ -7084,7 +8174,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { //skip animations when the first digest occurs (when //both the new and the old values are the same) since //the CSS classes are the non-interpolated values - if(name === 'class' && newValue != oldValue) { + if (name === 'class' && newValue != oldValue) { attr.$updateClass(newValue, oldValue); } else { attr.$set(name, newValue); @@ -7114,7 +8204,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { i, ii; if ($rootElement) { - for(i = 0, ii = $rootElement.length; i < ii; i++) { + for (i = 0, ii = $rootElement.length; i < ii; i++) { if ($rootElement[i] == firstElementToRemove) { $rootElement[i++] = newNode; for (var j = i, j2 = j + removeCount - 1, @@ -7127,6 +8217,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } $rootElement.length -= removeCount - 1; + + // If the replaced element is also the jQuery .context then replace it + // .context is a deprecated jQuery api, so we should set it only when jQuery set it + // http://api.jquery.com/context/ + if ($rootElement.context === firstElementToRemove) { + $rootElement.context = newNode; + } break; } } @@ -7135,9 +8232,33 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (parent) { parent.replaceChild(newNode, firstElementToRemove); } + + // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? var fragment = document.createDocumentFragment(); fragment.appendChild(firstElementToRemove); - newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; + + // Copy over user data (that includes Angular's $scope etc.). Don't copy private + // data here because there's no public interface in jQuery to do that and copying over + // event listeners (which is the main use of private data) wouldn't work anyway. + jqLite(newNode).data(jqLite(firstElementToRemove).data()); + + // Remove data of the replaced element. We cannot just call .remove() + // on the element it since that would deallocate scope that is needed + // for the new node. Instead, remove the data "manually". + if (!jQuery) { + delete jqLite.cache[firstElementToRemove[jqLite.expando]]; + } else { + // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after + // the replaced element. The cleanData version monkey-patched by Angular would cause + // the scope to be trashed and we do need the very same scope to work with the new + // element. However, we cannot just cache the non-patched version and use it here as + // that would break if another library patches the method after Angular does (one + // example is jQuery UI). Instead, set a flag indicating scope destroying should be + // skipped this one time. + skipDestroyOnNextJQueryCleanData = true; + jQuery.cleanData([firstElementToRemove]); + } + for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { var element = elementsToRemove[k]; jqLite(element).remove(); // must do this way to clean up expando @@ -7153,10 +8274,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function cloneAndAnnotateFn(fn, annotation) { return extend(function() { return fn.apply(null, arguments); }, fn, annotation); } + + + function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { + try { + linkFn(scope, $element, attrs, controllers, transcludeFn); + } catch (e) { + $exceptionHandler(e, startingTag($element)); + } + } }]; } -var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; +var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; /** * Converts all accepted directives format into proper directive name. * @param name Name to normalize @@ -7215,7 +8345,7 @@ function nodesetLinkingFn( /* NodeList */ nodeList, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn -){} +) {} function directiveLinkingFn( /* nodesetLinkingFn */ nodesetLinkingFn, @@ -7223,7 +8353,7 @@ function directiveLinkingFn( /* Node */ node, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn -){} +) {} function tokenDifference(str1, str2) { var values = '', @@ -7231,16 +8361,35 @@ function tokenDifference(str1, str2) { tokens2 = str2.split(/\s+/); outer: - for(var i = 0; i < tokens1.length; i++) { + for (var i = 0; i < tokens1.length; i++) { var token = tokens1[i]; - for(var j = 0; j < tokens2.length; j++) { - if(token == tokens2[j]) continue outer; + for (var j = 0; j < tokens2.length; j++) { + if (token == tokens2[j]) continue outer; } values += (values.length > 0 ? ' ' : '') + token; } return values; } +function removeComments(jqNodes) { + jqNodes = jqLite(jqNodes); + var i = jqNodes.length; + + if (i <= 1) { + return jqNodes; + } + + while (i--) { + var node = jqNodes[i]; + if (node.nodeType === NODE_TYPE_COMMENT) { + splice.call(jqNodes, i, 1); + } + } + return jqNodes; +} + +var $controllerMinErr = minErr('$controller'); + /** * @ngdoc provider * @name $controllerProvider @@ -7253,6 +8402,7 @@ function tokenDifference(str1, str2) { */ function $ControllerProvider() { var controllers = {}, + globals = false, CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; @@ -7273,6 +8423,15 @@ function $ControllerProvider() { } }; + /** + * @ngdoc method + * @name $controllerProvider#allowGlobals + * @description If called, allows `$controller` to find controller constructors on `window` + */ + this.allowGlobals = function() { + globals = true; + }; + this.$get = ['$injector', '$window', function($injector, $window) { @@ -7287,7 +8446,12 @@ function $ControllerProvider() { * * * check if a controller with given name is registered via `$controllerProvider` * * check if evaluating the string on the current scope returns a constructor - * * check `window[constructor]` on the global `window` object + * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global + * `window` object (not recommended) + * + * The string can use the `controller as property` syntax, where the controller instance is published + * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this + * to work correctly. * * @param {Object} locals Injection locals for Controller. * @return {Object} Instance of given controller. @@ -7298,34 +8462,83 @@ function $ControllerProvider() { * It's just a simple call to {@link auto.$injector $injector}, but extracted into * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). */ - return function(expression, locals) { + return function(expression, locals, later, ident) { + // PRIVATE API: + // param `later` --- indicates that the controller's constructor is invoked at a later time. + // If true, $controller will allocate the object with the correct + // prototype chain, but will not invoke the controller until a returned + // callback is invoked. + // param `ident` --- An optional label which overrides the label parsed from the controller + // expression, if any. var instance, match, constructor, identifier; + later = later === true; + if (ident && isString(ident)) { + identifier = ident; + } - if(isString(expression)) { - match = expression.match(CNTRL_REG), + if (isString(expression)) { + match = expression.match(CNTRL_REG); + if (!match) { + throw $controllerMinErr('ctrlfmt', + "Badly formed controller string '{0}'. " + + "Must match `__name__ as __id__` or `__name__`.", expression); + } constructor = match[1], - identifier = match[3]; + identifier = identifier || match[3]; expression = controllers.hasOwnProperty(constructor) ? controllers[constructor] - : getter(locals.$scope, constructor, true) || getter($window, constructor, true); + : getter(locals.$scope, constructor, true) || + (globals ? getter($window, constructor, true) : undefined); assertArgFn(expression, constructor, true); } - instance = $injector.instantiate(expression, locals); + if (later) { + // Instantiate controller later: + // This machinery is used to create an instance of the object before calling the + // controller's constructor itself. + // + // This allows properties to be added to the controller before the constructor is + // invoked. Primarily, this is used for isolate scope bindings in $compile. + // + // This feature is not intended for use by applications, and is thus not documented + // publicly. + // Object creation: http://jsperf.com/create-constructor/2 + var controllerPrototype = (isArray(expression) ? + expression[expression.length - 1] : expression).prototype; + instance = Object.create(controllerPrototype || null); - if (identifier) { - if (!(locals && typeof locals.$scope === 'object')) { - throw minErr('$controller')('noscp', - "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", - constructor || expression.name, identifier); + if (identifier) { + addIdentifier(locals, identifier, instance, constructor || expression.name); } - locals.$scope[identifier] = instance; + return extend(function() { + $injector.invoke(expression, instance, locals, constructor); + return instance; + }, { + instance: instance, + identifier: identifier + }); + } + + instance = $injector.instantiate(expression, locals, constructor); + + if (identifier) { + addIdentifier(locals, identifier, instance, constructor || expression.name); } return instance; }; + + function addIdentifier(locals, identifier, instance, name) { + if (!(locals && isObject(locals.$scope))) { + throw minErr('$controller')('noscp', + "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", + name, identifier); + } + + locals.$scope[identifier] = instance; + } }]; } @@ -7354,8 +8567,8 @@ function $ControllerProvider() {
    */ -function $DocumentProvider(){ - this.$get = ['$window', function(window){ +function $DocumentProvider() { + this.$get = ['$window', function(window) { return jqLite(window.document); }]; } @@ -7376,8 +8589,8 @@ function $DocumentProvider(){ * ## Example: * * ```js - * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { - * return function (exception, cause) { + * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { + * return function(exception, cause) { * exception.message += ' (caused by "' + cause + '")'; * throw exception; * }; @@ -7387,6 +8600,14 @@ function $DocumentProvider(){ * This example will override the normal action of `$exceptionHandler`, to make angular * exceptions fail hard when they happen, instead of just logging to the console. * + *
    + * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` + * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} + * (unless executed during a digest). + * + * If you wish, you can manually delegate exceptions, e.g. + * `try { ... } catch(e) { $exceptionHandler(e); }` + * * @param {Error} exception Exception associated with the error. * @param {string=} cause optional information about the context in which * the error was thrown. @@ -7400,6 +8621,36 @@ function $ExceptionHandlerProvider() { }]; } +var APPLICATION_JSON = 'application/json'; +var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; +var JSON_START = /^\[|^\{(?!\{)/; +var JSON_ENDS = { + '[': /]$/, + '{': /}$/ +}; +var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; + +function defaultHttpResponseTransform(data, headers) { + if (isString(data)) { + // Strip json vulnerability protection prefix and trim whitespace + var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); + + if (tempData) { + var contentType = headers('Content-Type'); + if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { + data = fromJson(tempData); + } + } + } + + return data; +} + +function isJsonLike(str) { + var jsonStart = str.match(JSON_START); + return jsonStart && JSON_ENDS[jsonStart[0]].test(str); +} + /** * Parse headers into key value object * @@ -7407,7 +8658,7 @@ function $ExceptionHandlerProvider() { * @returns {Object} Parsed headers as key value object */ function parseHeaders(headers) { - var parsed = {}, key, val, i; + var parsed = createMap(), key, val, i; if (!headers) return parsed; @@ -7444,7 +8695,11 @@ function headersGetter(headers) { if (!headersObj) headersObj = parseHeaders(headers); if (name) { - return headersObj[lowercase(name)] || null; + var value = headersObj[lowercase(name)]; + if (value === void 0) { + value = null; + } + return value; } return headersObj; @@ -7458,16 +8713,17 @@ function headersGetter(headers) { * This function is used for both request and response transforming * * @param {*} data Data to transform. - * @param {function(string=)} headers Http headers getter fn. + * @param {function(string=)} headers HTTP headers getter fn. + * @param {number} status HTTP status code of the response. * @param {(Function|Array.)} fns Function or an array of functions. * @returns {*} Transformed data. */ -function transformData(data, headers, fns) { +function transformData(data, headers, status, fns) { if (isFunction(fns)) - return fns(data, headers); + return fns(data, headers, status); forEach(fns, function(fn) { - data = fn(data, headers); + data = fn(data, headers, status); }); return data; @@ -7486,11 +8742,6 @@ function isSuccess(status) { * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. * */ function $HttpProvider() { - var JSON_START = /^\s*(\[|\{[^\{])/, - JSON_END = /[\}\]]\s*$/, - PROTECTION_PREFIX = /^\)\]\}',?\n/, - CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; - /** * @ngdoc property * @name $httpProvider#defaults @@ -7498,6 +8749,11 @@ function $HttpProvider() { * * Object containing default values for all {@link ng.$http $http} requests. * + * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} + * that will provide the cache for all requests who set their `cache` property to `true`. + * If you set the `default.cache = false` then only requests that specify their own custom + * cache object will be cached. See {@link $http#caching $http Caching} for more information. + * * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. * Defaults value is `'XSRF-TOKEN'`. * @@ -7511,22 +8767,15 @@ function $HttpProvider() { * - **`defaults.headers.post`** * - **`defaults.headers.put`** * - **`defaults.headers.patch`** + * **/ var defaults = this.defaults = { // transform incoming response data - transformResponse: [function(data) { - if (isString(data)) { - // strip json vulnerability protection prefix - data = data.replace(PROTECTION_PREFIX, ''); - if (JSON_START.test(data) && JSON_END.test(data)) - data = fromJson(data); - } - return data; - }], + transformResponse: [defaultHttpResponseTransform], // transform outgoing request data transformRequest: [function(d) { - return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; + return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; }], // default headers @@ -7543,6 +8792,34 @@ function $HttpProvider() { xsrfHeaderName: 'X-XSRF-TOKEN' }; + var useApplyAsync = false; + /** + * @ngdoc method + * @name $httpProvider#useApplyAsync + * @description + * + * Configure $http service to combine processing of multiple http responses received at around + * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in + * significant performance improvement for bigger applications that make many HTTP requests + * concurrently (common during application bootstrap). + * + * Defaults to false. If no value is specifed, returns the current configured value. + * + * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred + * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window + * to load and share the same digest cycle. + * + * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. + * otherwise, returns the current configured value. + **/ + this.useApplyAsync = function(value) { + if (isDefined(value)) { + useApplyAsync = !!value; + return this; + } + return useApplyAsync; + }; + /** * @ngdoc property * @name $httpProvider#interceptors @@ -7558,12 +8835,6 @@ function $HttpProvider() { **/ var interceptorFactories = this.interceptors = []; - /** - * For historical reasons, response interceptors are ordered by the order in which - * they are applied to the response. (This is the opposite of interceptorFactories) - */ - var responseInterceptorFactories = this.responseInterceptors = []; - this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { @@ -7581,27 +8852,6 @@ function $HttpProvider() { ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); }); - forEach(responseInterceptorFactories, function(interceptorFactory, index) { - var responseFn = isString(interceptorFactory) - ? $injector.get(interceptorFactory) - : $injector.invoke(interceptorFactory); - - /** - * Response interceptors go before "around" interceptors (no real reason, just - * had to pick one.) But they are already reversed, so we can't use unshift, hence - * the splice. - */ - reversedInterceptors.splice(index, 0, { - response: function(response) { - return responseFn($q.when(response)); - }, - responseError: function(response) { - return responseFn($q.reject(response)); - } - }); - }); - - /** * @ngdoc service * @kind function @@ -7628,13 +8878,14 @@ function $HttpProvider() { * it is important to familiarize yourself with these APIs and the guarantees they provide. * * - * # General usage + * ## General usage * The `$http` service is a function which takes a single argument — a configuration object — * that is used to generate an HTTP request and returns a {@link ng.$q promise} * with two $http specific methods: `success` and `error`. * * ```js - * $http({method: 'GET', url: '/someUrl'}). + * // Simple GET request example : + * $http.get('/someUrl'). * success(function(data, status, headers, config) { * // this callback will be called asynchronously * // when the response is available @@ -7645,6 +8896,20 @@ function $HttpProvider() { * }); * ``` * + * ```js + * // Simple POST request example (passing data) : + * $http.post('/someUrl', {msg:'hello word!'}). + * success(function(data, status, headers, config) { + * // this callback will be called asynchronously + * // when the response is available + * }). + * error(function(data, status, headers, config) { + * // called asynchronously if an error occurs + * // or server returns response with an error status. + * }); + * ``` + * + * * Since the returned value of calling the $http function is a `promise`, you can also use * the `then` method to register callbacks, and these callbacks will receive a single argument – * an object representing the response. See the API signature and type info below for more @@ -7655,7 +8920,7 @@ function $HttpProvider() { * XMLHttpRequest will transparently follow it, meaning that the error callback will not be * called for such responses. * - * # Writing Unit Tests that use $http + * ## Writing Unit Tests that use $http * When unit testing (using {@link ngMock ngMock}), it is necessary to call * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending * request using trained responses. @@ -7666,7 +8931,7 @@ function $HttpProvider() { * $httpBackend.flush(); * ``` * - * # Shortcut methods + * ## Shortcut methods * * Shortcut methods are also available. All shortcut methods require passing in the URL, and * request data must be passed in for POST/PUT requests. @@ -7687,7 +8952,7 @@ function $HttpProvider() { * - {@link ng.$http#patch $http.patch} * * - * # Setting HTTP Headers + * ## Setting HTTP Headers * * The $http service will automatically add certain HTTP headers to all requests. These defaults * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration @@ -7727,42 +8992,75 @@ function $HttpProvider() { * headers: { * 'Content-Type': undefined * }, - * data: { test: 'test' }, + * data: { test: 'test' } * } * * $http(req).success(function(){...}).error(function(){...}); * ``` * - * # Transforming Requests and Responses + * ## Transforming Requests and Responses * - * Both requests and responses can be transformed using transform functions. By default, Angular - * applies these transformations: + * Both requests and responses can be transformed using transformation functions: `transformRequest` + * and `transformResponse`. These properties can be a single function that returns + * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, + * which allows you to `push` or `unshift` a new transformation function into the transformation chain. * - * Request transformations: + * ### Default Transformations + * + * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and + * `defaults.transformResponse` properties. If a request does not provide its own transformations + * then these will be applied. + * + * You can augment or replace the default transformations by modifying these properties by adding to or + * replacing the array. + * + * Angular provides the following default transformations: + * + * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): * * - If the `data` property of the request configuration object contains an object, serialize it * into JSON format. * - * Response transformations: + * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): * * - If XSRF prefix is detected, strip it (see Security Considerations section below). * - If JSON response is detected, deserialize it using a JSON parser. * - * To globally augment or override the default transforms, modify the - * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` - * properties. These properties are by default an array of transform functions, which allows you - * to `push` or `unshift` a new transformation function into the transformation chain. You can - * also decide to completely override any default transformations by assigning your - * transformation functions to these properties directly without the array wrapper. These defaults - * are again available on the $http factory at run-time, which may be useful if you have run-time - * services you wish to be involved in your transformations. * - * Similarly, to locally override the request/response transforms, augment the - * `transformRequest` and/or `transformResponse` properties of the configuration object passed + * ### Overriding the Default Transformations Per Request + * + * If you wish override the request/response transformations only for a single request then provide + * `transformRequest` and/or `transformResponse` properties on the configuration object passed * into `$http`. * + * Note that if you provide these properties on the config object the default transformations will be + * overwritten. If you wish to augment the default transformations then you must include them in your + * local transformation array. * - * # Caching + * The following code demonstrates adding a new response transformation to be run after the default response + * transformations have been run. + * + * ```js + * function appendTransform(defaults, transform) { + * + * // We can't guarantee that the default transformation is an array + * defaults = angular.isArray(defaults) ? defaults : [defaults]; + * + * // Append the new transformation to the defaults + * return defaults.concat(transform); + * } + * + * $http({ + * url: '...', + * method: 'GET', + * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { + * return doTransform(value); + * }) + * }); + * ``` + * + * + * ## Caching * * To enable caching, set the request configuration `cache` property to `true` (to use default * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). @@ -7779,13 +9077,13 @@ function $HttpProvider() { * * You can change the default cache to a new object (built with * {@link ng.$cacheFactory `$cacheFactory`}) by updating the - * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set + * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set * their `cache` property to `true` will now use this cache object. * * If you set the default cache to `false` then only requests that specify their own custom * cache object will be cached. * - * # Interceptors + * ## Interceptors * * Before you start creating interceptors, be sure to understand the * {@link ng.$q $q and deferred/promise APIs}. @@ -7870,52 +9168,7 @@ function $HttpProvider() { * }); * ``` * - * # Response interceptors (DEPRECATED) - * - * Before you start creating interceptors, be sure to understand the - * {@link ng.$q $q and deferred/promise APIs}. - * - * For purposes of global error handling, authentication or any kind of synchronous or - * asynchronous preprocessing of received responses, it is desirable to be able to intercept - * responses for http requests before they are handed over to the application code that - * initiated these requests. The response interceptors leverage the {@link ng.$q - * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. - * - * The interceptors are service factories that are registered with the $httpProvider by - * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and - * injected with dependencies (if specified) and returns the interceptor — a function that - * takes a {@link ng.$q promise} and returns the original or a new promise. - * - * ```js - * // register the interceptor as a service - * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { - * return function(promise) { - * return promise.then(function(response) { - * // do something on success - * return response; - * }, function(response) { - * // do something on error - * if (canRecover(response)) { - * return responseOrNewPromise - * } - * return $q.reject(response); - * }); - * } - * }); - * - * $httpProvider.responseInterceptors.push('myHttpInterceptor'); - * - * - * // register the interceptor via an anonymous factory - * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) { - * return function(promise) { - * // same as above - * } - * }); - * ``` - * - * - * # Security Considerations + * ## Security Considerations * * When designing web applications, consider security threats from: * @@ -7926,7 +9179,7 @@ function $HttpProvider() { * pre-configured with strategies that address these issues, but for this to work backend server * cooperation is required. * - * ## JSON Vulnerability Protection + * ### JSON Vulnerability Protection * * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) * allows third party website to turn your JSON resource URL into @@ -7948,7 +9201,7 @@ function $HttpProvider() { * Angular will strip the prefix, before processing the JSON. * * - * ## Cross Site Request Forgery (XSRF) Protection + * ### Cross Site Request Forgery (XSRF) Protection * * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which * an unauthorized site can gain your user's private data. Angular provides a mechanism @@ -7990,10 +9243,14 @@ function $HttpProvider() { * `{function(data, headersGetter)|Array.}` – * transform function or an array of such functions. The transform function takes the http * request body and headers and returns its transformed (typically serialized) version. + * See {@link ng.$http#overriding-the-default-transformations-per-request + * Overriding the Default Transformations} * - **transformResponse** – - * `{function(data, headersGetter)|Array.}` – + * `{function(data, headersGetter, status)|Array.}` – * transform function or an array of such functions. The transform function takes the http - * response body and headers and returns its transformed (typically deserialized) version. + * response body, headers and status and returns its transformed (typically deserialized) version. + * See {@link ng.$http#overriding-the-default-transformations-per-request + * Overriding the Default Transformations} * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the * GET request, otherwise if a cache instance built with * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for @@ -8114,20 +9371,23 @@ function $HttpProvider() { */ function $http(requestConfig) { - var config = { + + if (!angular.isObject(requestConfig)) { + throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); + } + + var config = extend({ method: 'get', transformRequest: defaults.transformRequest, transformResponse: defaults.transformResponse - }; - var headers = mergeHeaders(requestConfig); + }, requestConfig); - extend(config, requestConfig); - config.headers = headers; + config.headers = mergeHeaders(requestConfig); config.method = uppercase(config.method); var serverRequest = function(config) { - headers = config.headers; - var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); + var headers = config.headers; + var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); // strip content-type if data is undefined if (isUndefined(reqData)) { @@ -8143,7 +9403,7 @@ function $HttpProvider() { } // send request - return sendReq(config, reqData, headers).then(transformResponse, transformResponse); + return sendReq(config, reqData).then(transformResponse, transformResponse); }; var chain = [serverRequest, undefined]; @@ -8159,7 +9419,7 @@ function $HttpProvider() { } }); - while(chain.length) { + while (chain.length) { var thenFn = chain.shift(); var rejectFn = chain.shift(); @@ -8167,6 +9427,8 @@ function $HttpProvider() { } promise.success = function(fn) { + assertArgFn(fn, 'fn'); + promise.then(function(response) { fn(response.data, response.status, response.headers, config); }); @@ -8174,6 +9436,8 @@ function $HttpProvider() { }; promise.error = function(fn) { + assertArgFn(fn, 'fn'); + promise.then(null, function(response) { fn(response.data, response.status, response.headers, config); }); @@ -8184,14 +9448,34 @@ function $HttpProvider() { function transformResponse(response) { // make a copy since the response must be cacheable - var resp = extend({}, response, { - data: transformData(response.data, response.headers, config.transformResponse) - }); + var resp = extend({}, response); + if (!response.data) { + resp.data = response.data; + } else { + resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); + } return (isSuccess(response.status)) ? resp : $q.reject(resp); } + function executeHeaderFns(headers) { + var headerContent, processedHeaders = {}; + + forEach(headers, function(headerFn, header) { + if (isFunction(headerFn)) { + headerContent = headerFn(); + if (headerContent != null) { + processedHeaders[header] = headerContent; + } + } else { + processedHeaders[header] = headerFn; + } + }); + + return processedHeaders; + } + function mergeHeaders(config) { var defHeaders = defaults.headers, reqHeaders = extend({}, config.headers), @@ -8214,23 +9498,7 @@ function $HttpProvider() { } // execute if header value is a function for merged headers - execHeaders(reqHeaders); - return reqHeaders; - - function execHeaders(headers) { - var headerContent; - - forEach(headers, function(headerFn, header) { - if (isFunction(headerFn)) { - headerContent = headerFn(); - if (headerContent != null) { - headers[header] = headerContent; - } else { - delete headers[header]; - } - } - }); - } + return executeHeaderFns(reqHeaders); } } @@ -8312,30 +9580,30 @@ function $HttpProvider() { * @returns {HttpPromise} Future object */ - /** - * @ngdoc method - * @name $http#patch - * - * @description - * Shortcut method to perform `PATCH` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ + /** + * @ngdoc method + * @name $http#patch + * + * @description + * Shortcut method to perform `PATCH` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ createShortMethodsWithData('post', 'put', 'patch'); - /** - * @ngdoc property - * @name $http#defaults - * - * @description - * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of - * default headers, withCredentials as well as request and response transformations. - * - * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. - */ + /** + * @ngdoc property + * @name $http#defaults + * + * @description + * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of + * default headers, withCredentials as well as request and response transformations. + * + * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. + */ $http.defaults = defaults; @@ -8373,11 +9641,12 @@ function $HttpProvider() { * !!! ACCESSES CLOSURE VARS: * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests */ - function sendReq(config, reqData, reqHeaders) { + function sendReq(config, reqData) { var deferred = $q.defer(), promise = deferred.promise, cache, cachedResp, + reqHeaders = config.headers, url = buildUrl(config.url, config.params); $http.pendingRequests.push(config); @@ -8396,8 +9665,7 @@ function $HttpProvider() { if (isDefined(cachedResp)) { if (isPromiseLike(cachedResp)) { // cached request has already been sent, but there is no response yet - cachedResp.then(removePendingReq, removePendingReq); - return cachedResp; + cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); } else { // serving from cache if (isArray(cachedResp)) { @@ -8446,8 +9714,16 @@ function $HttpProvider() { } } - resolvePromise(response, status, headersString, statusText); - if (!$rootScope.$$phase) $rootScope.$apply(); + function resolveHttpPromise() { + resolvePromise(response, status, headersString, statusText); + } + + if (useApplyAsync) { + $rootScope.$applyAsync(resolveHttpPromise); + } else { + resolveHttpPromise(); + if (!$rootScope.$$phase) $rootScope.$apply(); + } } @@ -8455,21 +9731,24 @@ function $HttpProvider() { * Resolves the raw $http promise. */ function resolvePromise(response, status, headers, statusText) { - // normalize internal statuses to 0 - status = Math.max(status, 0); + //status: HTTP response status code, 0, -1 (aborted by timeout / promise) + status = status >= -1 ? status : 0; (isSuccess(status) ? deferred.resolve : deferred.reject)({ data: response, status: status, headers: headersGetter(headers), config: config, - statusText : statusText + statusText: statusText }); } + function resolvePromiseWithResult(result) { + resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); + } function removePendingReq() { - var idx = indexOf($http.pendingRequests, config); + var idx = $http.pendingRequests.indexOf(config); if (idx !== -1) $http.pendingRequests.splice(idx, 1); } } @@ -8484,7 +9763,7 @@ function $HttpProvider() { forEach(value, function(v) { if (isObject(v)) { - if (isDate(v)){ + if (isDate(v)) { v = v.toISOString(); } else { v = toJson(v); @@ -8494,7 +9773,7 @@ function $HttpProvider() { encodeUriQuery(v)); }); }); - if(parts.length > 0) { + if (parts.length > 0) { url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); } return url; @@ -8502,18 +9781,8 @@ function $HttpProvider() { }]; } -function createXhr(method) { - //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest - //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest - //if it is available - if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || - !window.XMLHttpRequest)) { - return new window.ActiveXObject("Microsoft.XMLHTTP"); - } else if (window.XMLHttpRequest) { - return new window.XMLHttpRequest(); - } - - throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); +function createXhr() { + return new window.XMLHttpRequest(); } /** @@ -8539,11 +9808,8 @@ function $HttpBackendProvider() { } function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { - var ABORTED = -1; - // TODO(vojta): fix the signature return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { - var status; $browser.$$incOutstandingRequestCount(); url = url || $browser.url(); @@ -8561,7 +9827,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc }); } else { - var xhr = createXhr(method); + var xhr = createXhr(); xhr.open(method, url, true); forEach(headers, function(value, key) { @@ -8570,44 +9836,39 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc } }); - // In IE6 and 7, this might be called synchronously when xhr.send below is called and the - // response is in the cache. the promise api will ensure that to the app code the api is - // always async - xhr.onreadystatechange = function() { - // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by - // xhrs that are resolved while the app is in the background (see #5426). - // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before - // continuing - // - // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and - // Safari respectively. - if (xhr && xhr.readyState == 4) { - var responseHeaders = null, - response = null, - statusText = ''; + xhr.onload = function requestLoaded() { + var statusText = xhr.statusText || ''; - if(status !== ABORTED) { - responseHeaders = xhr.getAllResponseHeaders(); + // responseText is the old-school way of retrieving response (supported by IE9) + // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) + var response = ('response' in xhr) ? xhr.response : xhr.responseText; - // responseText is the old-school way of retrieving response (supported by IE8 & 9) - // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) - response = ('response' in xhr) ? xhr.response : xhr.responseText; - } + // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) + var status = xhr.status === 1223 ? 204 : xhr.status; - // Accessing statusText on an aborted xhr object will - // throw an 'c00c023f error' in IE9 and lower, don't touch it. - if (!(status === ABORTED && msie < 10)) { - statusText = xhr.statusText; - } - - completeRequest(callback, - status || xhr.status, - response, - responseHeaders, - statusText); + // fix status code when it is 0 (0 status is undocumented). + // Occurs when accessing file resources or on Android 4.1 stock browser + // while retrieving files from application cache. + if (status === 0) { + status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; } + + completeRequest(callback, + status, + response, + xhr.getAllResponseHeaders(), + statusText); }; + var requestError = function() { + // The response is always empty + // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error + completeRequest(callback, -1, null, null, ''); + }; + + xhr.onerror = requestError; + xhr.onabort = requestError; + if (withCredentials) { xhr.withCredentials = true; } @@ -8640,26 +9901,16 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc function timeoutRequest() { - status = ABORTED; jsonpDone && jsonpDone(); xhr && xhr.abort(); } function completeRequest(callback, status, response, headersString, statusText) { // cancel timeout and subsequent timeout promise resolution - timeoutId && $browserDefer.cancel(timeoutId); - jsonpDone = xhr = null; - - // fix status code when it is 0 (0 status is undocumented). - // Occurs when accessing file resources or on Android 4.1 stock browser - // while retrieving files from application cache. - if (status === 0) { - status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; + if (timeoutId !== undefined) { + $browserDefer.cancel(timeoutId); } - - // normalize IE bug (http://bugs.jquery.com/ticket/1450) - status = status === 1223 ? 204 : status; - statusText = statusText || ''; + jsonpDone = xhr = null; callback(status, response, headersString, statusText); $browser.$$completeOutstandingRequest(noop); @@ -8667,7 +9918,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc }; function jsonpReq(url, callbackId, done) { - // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: + // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: // - fetches local scripts via XHR and evals them // - adds and immediately removes script elements from the document var script = rawDocument.createElement('script'), callback = null; @@ -8698,18 +9949,6 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc addEventListenerFn(script, "load", callback); addEventListenerFn(script, "error", callback); - - if (msie <= 8) { - script.onreadystatechange = function() { - if (isString(script.readyState) && /loaded|complete/.test(script.readyState)) { - script.onreadystatechange = null; - callback({ - type: 'load' - }); - } - }; - } - rawDocument.body.appendChild(script); return callback; } @@ -8720,7 +9959,6 @@ var $interpolateMinErr = minErr('$interpolate'); /** * @ngdoc provider * @name $interpolateProvider - * @kind function * * @description * @@ -8766,7 +10004,7 @@ function $InterpolateProvider() { * @param {string=} value new value to set the starting symbol to. * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ - this.startSymbol = function(value){ + this.startSymbol = function(value) { if (value) { startSymbol = value; return this; @@ -8784,7 +10022,7 @@ function $InterpolateProvider() { * @param {string=} value new value to set the ending symbol to. * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ - this.endSymbol = function(value){ + this.endSymbol = function(value) { if (value) { endSymbol = value; return this; @@ -8796,7 +10034,13 @@ function $InterpolateProvider() { this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { var startSymbolLength = startSymbol.length, - endSymbolLength = endSymbol.length; + endSymbolLength = endSymbol.length, + escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), + escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); + + function escape(ch) { + return '\\\\\\' + ch; + } /** * @ngdoc service @@ -8820,6 +10064,62 @@ function $InterpolateProvider() { * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); * ``` * + * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is + * `true`, the interpolation function will return `undefined` unless all embedded expressions + * evaluate to a value other than `undefined`. + * + * ```js + * var $interpolate = ...; // injected + * var context = {greeting: 'Hello', name: undefined }; + * + * // default "forgiving" mode + * var exp = $interpolate('{{greeting}} {{name}}!'); + * expect(exp(context)).toEqual('Hello !'); + * + * // "allOrNothing" mode + * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); + * expect(exp(context)).toBeUndefined(); + * context.name = 'Angular'; + * expect(exp(context)).toEqual('Hello Angular!'); + * ``` + * + * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. + * + * ####Escaped Interpolation + * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers + * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). + * It will be rendered as a regular start/end marker, and will not be interpreted as an expression + * or binding. + * + * This enables web-servers to prevent script injection attacks and defacing attacks, to some + * degree, while also enabling code examples to work without relying on the + * {@link ng.directive:ngNonBindable ngNonBindable} directive. + * + * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, + * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all + * interpolation start/end markers with their escaped counterparts.** + * + * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered + * output when the $interpolate service processes the text. So, for HTML elements interpolated + * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter + * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, + * this is typically useful only when user-data is used in rendering a template from the server, or + * when otherwise untrusted data is used by a directive. + * + * + * + *
    + *

    {{apptitle}}: \{\{ username = "defaced value"; \}\} + *

    + *

    {{username}} attempts to inject code which will deface the + * application, but fails to accomplish their task, because the server has correctly + * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) + * characters.

    + *

    Instead, the result of the attempted script injection is visible, and can be removed + * from the database by an administrator.

    + *
    + *
    + *
    * * @param {string} text The text with markup to interpolate. * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have @@ -8829,103 +10129,139 @@ function $InterpolateProvider() { * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that * provides Strict Contextual Escaping for details. + * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined + * unless all embedded expressions evaluate to a value other than `undefined`. * @returns {function(context)} an interpolation function which is used to compute the * interpolated string. The function has these parameters: * - * * `context`: an object against which any expressions embedded in the strings are evaluated - * against. - * + * - `context`: evaluation context for all expressions embedded in the interpolated text */ - function $interpolate(text, mustHaveExpression, trustedContext) { + function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { + allOrNothing = !!allOrNothing; var startIndex, endIndex, index = 0, - parts = [], - length = text.length, - hasInterpolation = false, - fn, + expressions = [], + parseFns = [], + textLength = text.length, exp, - concat = []; + concat = [], + expressionPositions = []; - while(index < length) { - if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && - ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { - (index != startIndex) && parts.push(text.substring(index, startIndex)); - parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); - fn.exp = exp; + while (index < textLength) { + if (((startIndex = text.indexOf(startSymbol, index)) != -1) && + ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { + if (index !== startIndex) { + concat.push(unescapeText(text.substring(index, startIndex))); + } + exp = text.substring(startIndex + startSymbolLength, endIndex); + expressions.push(exp); + parseFns.push($parse(exp, parseStringifyInterceptor)); index = endIndex + endSymbolLength; - hasInterpolation = true; + expressionPositions.push(concat.length); + concat.push(''); } else { - // we did not find anything, so we have to add the remainder to the parts array - (index != length) && parts.push(text.substring(index)); - index = length; + // we did not find an interpolation, so we have to add the remainder to the separators array + if (index !== textLength) { + concat.push(unescapeText(text.substring(index))); + } + break; } } - if (!(length = parts.length)) { - // we added, nothing, must have been an empty string. - parts.push(''); - length = 1; - } - // Concatenating expressions makes it hard to reason about whether some combination of // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a // single expression be used for iframe[src], object[src], etc., we ensure that the value // that's used is assigned or constructed by some JS code somewhere that is more testable or // make it obvious that you bound the value to some user controlled value. This helps reduce // the load when auditing for XSS issues. - if (trustedContext && parts.length > 1) { + if (trustedContext && concat.length > 1) { throw $interpolateMinErr('noconcat', "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + "interpolations that concatenate multiple expressions when a trusted value is " + "required. See http://docs.angularjs.org/api/ng.$sce", text); } - if (!mustHaveExpression || hasInterpolation) { - concat.length = length; - fn = function(context) { - try { - for(var i = 0, ii = length, part; i "http://example.com/#/some/path?foo=bar&baz=xoxo" + * ``` + * * @return {string} full url */ absUrl: locationGetter('$$absUrl'), @@ -9553,6 +10935,13 @@ LocationHashbangInHtml5Url.prototype = * * Change path, search and hash, when called with parameter and return `$location`. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var url = $location.url(); + * // => "/some/path?foo=bar&baz=xoxo" + * ``` + * * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) * @return {string} url */ @@ -9561,8 +10950,8 @@ LocationHashbangInHtml5Url.prototype = return this.$$url; var match = PATH_MATCH.exec(url); - if (match[1]) this.path(decodeURIComponent(match[1])); - if (match[2] || match[1]) this.search(match[3] || ''); + if (match[1] || url === '') this.path(decodeURIComponent(match[1])); + if (match[2] || match[1] || url === '') this.search(match[3] || ''); this.hash(match[5] || ''); return this; @@ -9577,6 +10966,13 @@ LocationHashbangInHtml5Url.prototype = * * Return protocol of current url. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var protocol = $location.protocol(); + * // => "http" + * ``` + * * @return {string} protocol of current url */ protocol: locationGetter('$$protocol'), @@ -9590,6 +10986,21 @@ LocationHashbangInHtml5Url.prototype = * * Return host of current url. * + * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. + * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var host = $location.host(); + * // => "example.com" + * + * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo + * host = $location.host(); + * // => "example.com" + * host = location.host; + * // => "example.com:8080" + * ``` + * * @return {string} host of current url. */ host: locationGetter('$$host'), @@ -9603,6 +11014,13 @@ LocationHashbangInHtml5Url.prototype = * * Return port of current url. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var port = $location.port(); + * // => 80 + * ``` + * * @return {Number} port */ port: locationGetter('$$port'), @@ -9621,6 +11039,13 @@ LocationHashbangInHtml5Url.prototype = * Note: Path should always begin with forward slash (/), this method will add the forward slash * if it is missing. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var path = $location.path(); + * // => "/some/path" + * ``` + * * @param {(string|number)=} path New path * @return {string} path */ @@ -9646,10 +11071,9 @@ LocationHashbangInHtml5Url.prototype = * var searchObject = $location.search(); * // => {foo: 'bar', baz: 'xoxo'} * - * * // set foo to 'yipee' * $location.search('foo', 'yipee'); - * // => $location + * // $location.search() => {foo: 'yipee', baz: 'xoxo'} * ``` * * @param {string|Object.|Object.>} search New search params - string or @@ -9684,6 +11108,7 @@ LocationHashbangInHtml5Url.prototype = search = search.toString(); this.$$search = parseKeyValue(search); } else if (isObject(search)) { + search = copy(search, {}); // remove object undefined or null properties forEach(search, function(value, key) { if (value == null) delete search[key]; @@ -9718,6 +11143,13 @@ LocationHashbangInHtml5Url.prototype = * * Change hash fragment when called with parameter and return `$location`. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue + * var hash = $location.hash(); + * // => "hashValue" + * ``` + * * @param {(string|number)=} hash New hash fragment * @return {string} hash */ @@ -9739,6 +11171,46 @@ LocationHashbangInHtml5Url.prototype = } }; +forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { + Location.prototype = Object.create(locationPrototype); + + /** + * @ngdoc method + * @name $location#state + * + * @description + * This method is getter / setter. + * + * Return the history state object when called without any parameter. + * + * Change the history state object when called with one parameter and return `$location`. + * The state object is later passed to `pushState` or `replaceState`. + * + * NOTE: This method is supported only in HTML5 mode and only in browsers supporting + * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support + * older browsers (like IE9 or Android < 4.0), don't use this method. + * + * @param {object=} state State object for pushState or replaceState + * @return {object} state + */ + Location.prototype.state = function(state) { + if (!arguments.length) + return this.$$state; + + if (Location !== LocationHtml5Url || !this.$$html5) { + throw $locationMinErr('nostate', 'History API state support is available only ' + + 'in HTML5 mode and only in browsers supporting HTML5 History API'); + } + // The user might modify `stateObject` after invoking `$location.state(stateObject)` + // but we're changing the $$state reference to $browser.state() during the $digest + // so the modification window is narrow. + this.$$state = isUndefined(state) ? null : state; + + return this; + }; +}); + + function locationGetter(property) { return function() { return this[property]; @@ -9791,9 +11263,13 @@ function locationGetterSetter(property, preprocess) { * @description * Use the `$locationProvider` to configure how the application deep linking paths are stored. */ -function $LocationProvider(){ +function $LocationProvider() { var hashPrefix = '', - html5Mode = false; + html5Mode = { + enabled: false, + requireBase: true, + rewriteLinks: true + }; /** * @ngdoc method @@ -9815,12 +11291,39 @@ function $LocationProvider(){ * @ngdoc method * @name $locationProvider#html5Mode * @description - * @param {boolean=} mode Use HTML5 strategy if available. - * @returns {*} current value if used as getter or itself (chaining) if used as setter + * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. + * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported + * properties: + * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to + * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not + * support `pushState`. + * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies + * whether or not a tag is required to be present. If `enabled` and `requireBase` are + * true, and a base tag is not present, an error will be thrown when `$location` is injected. + * See the {@link guide/$location $location guide for more information} + * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, + * enables/disables url rewriting for relative links. + * + * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter */ this.html5Mode = function(mode) { - if (isDefined(mode)) { - html5Mode = mode; + if (isBoolean(mode)) { + html5Mode.enabled = mode; + return this; + } else if (isObject(mode)) { + + if (isBoolean(mode.enabled)) { + html5Mode.enabled = mode.enabled; + } + + if (isBoolean(mode.requireBase)) { + html5Mode.requireBase = mode.requireBase; + } + + if (isBoolean(mode.rewriteLinks)) { + html5Mode.rewriteLinks = mode.rewriteLinks; + } + return this; } else { return html5Mode; @@ -9832,14 +11335,21 @@ function $LocationProvider(){ * @name $location#$locationChangeStart * @eventType broadcast on root scope * @description - * Broadcasted before a URL will change. This change can be prevented by calling + * Broadcasted before a URL will change. + * + * This change can be prevented by calling * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more * details about event object. Upon successful change - * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. + * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. + * + * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when + * the browser supports the HTML5 History API. * * @param {Object} angularEvent Synthetic event object. * @param {string} newUrl New URL * @param {string=} oldUrl URL that was before it was changed. + * @param {string=} newState New history state object + * @param {string=} oldState History state object that was before it was changed. */ /** @@ -9849,41 +11359,73 @@ function $LocationProvider(){ * @description * Broadcasted after a URL was changed. * + * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when + * the browser supports the HTML5 History API. + * * @param {Object} angularEvent Synthetic event object. * @param {string} newUrl New URL * @param {string=} oldUrl URL that was before it was changed. + * @param {string=} newState New history state object + * @param {string=} oldState History state object that was before it was changed. */ - this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', - function( $rootScope, $browser, $sniffer, $rootElement) { + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', + function($rootScope, $browser, $sniffer, $rootElement, $window) { var $location, LocationMode, baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' initialUrl = $browser.url(), appBase; - if (html5Mode) { + if (html5Mode.enabled) { + if (!baseHref && html5Mode.requireBase) { + throw $locationMinErr('nobase', + "$location in HTML5 mode requires a tag to be present!"); + } appBase = serverBase(initialUrl) + (baseHref || '/'); LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; } else { appBase = stripHash(initialUrl); LocationMode = LocationHashbangUrl; } - $location = new LocationMode(appBase, '#' + hashPrefix); + var appBaseNoFile = stripFile(appBase); + + $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); $location.$$parseLinkUrl(initialUrl, initialUrl); + $location.$$state = $browser.state(); + var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; + function setBrowserUrlWithFallback(url, replace, state) { + var oldUrl = $location.url(); + var oldState = $location.$$state; + try { + $browser.url(url, replace, state); + + // Make sure $location.state() returns referentially identical (not just deeply equal) + // state object; this makes possible quick checking if the state changed in the digest + // loop. Checking deep equality would be too expensive. + $location.$$state = $browser.state(); + } catch (e) { + // Restore old values if pushState fails + $location.url(oldUrl); + $location.$$state = oldState; + + throw e; + } + } + $rootElement.on('click', function(event) { // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) // currently we open nice url link and redirect then - if (event.ctrlKey || event.metaKey || event.which == 2) return; + if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; var elm = jqLite(event.target); // traverse the DOM up to find first A tag - while (lowercase(elm[0].nodeName) !== 'a') { + while (nodeName_(elm[0]) !== 'a') { // ignore rewriting if no A tag (reached root element, or no parent - removed from document) if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; } @@ -9912,7 +11454,7 @@ function $LocationProvider(){ if ($location.absUrl() != $browser.url()) { $rootScope.$apply(); // hack to work around FF6 bug 684208 when scenario runner clicks on links - window.angular['ff-684208-preventDefault'] = true; + $window.angular['ff-684208-preventDefault'] = true; } } } @@ -9920,56 +11462,93 @@ function $LocationProvider(){ // rewrite hashbang url <> html5 url - if ($location.absUrl() != initialUrl) { + if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { $browser.url($location.absUrl(), true); } - // update $location when $browser url changes - $browser.onUrlChange(function(newUrl) { - if ($location.absUrl() != newUrl) { - $rootScope.$evalAsync(function() { - var oldUrl = $location.absUrl(); + var initializing = true; - $location.$$parse(newUrl); - if ($rootScope.$broadcast('$locationChangeStart', newUrl, - oldUrl).defaultPrevented) { - $location.$$parse(oldUrl); - $browser.url(oldUrl); - } else { - afterLocationChange(oldUrl); - } - }); - if (!$rootScope.$$phase) $rootScope.$digest(); + // update $location when $browser url changes + $browser.onUrlChange(function(newUrl, newState) { + + if (isUndefined(beginsWith(appBaseNoFile, newUrl))) { + // If we are navigating outside of the app then force a reload + $window.location.href = newUrl; + return; } + + $rootScope.$evalAsync(function() { + var oldUrl = $location.absUrl(); + var oldState = $location.$$state; + var defaultPrevented; + + $location.$$parse(newUrl); + $location.$$state = newState; + + defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + newState, oldState).defaultPrevented; + + // if the location was changed by a `$locationChangeStart` handler then stop + // processing this location change + if ($location.absUrl() !== newUrl) return; + + if (defaultPrevented) { + $location.$$parse(oldUrl); + $location.$$state = oldState; + setBrowserUrlWithFallback(oldUrl, false, oldState); + } else { + initializing = false; + afterLocationChange(oldUrl, oldState); + } + }); + if (!$rootScope.$$phase) $rootScope.$digest(); }); // update browser - var changeCounter = 0; $rootScope.$watch(function $locationWatch() { - var oldUrl = $browser.url(); + var oldUrl = trimEmptyHash($browser.url()); + var newUrl = trimEmptyHash($location.absUrl()); + var oldState = $browser.state(); var currentReplace = $location.$$replace; + var urlOrStateChanged = oldUrl !== newUrl || + ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); + + if (initializing || urlOrStateChanged) { + initializing = false; - if (!changeCounter || oldUrl != $location.absUrl()) { - changeCounter++; $rootScope.$evalAsync(function() { - if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). - defaultPrevented) { + var newUrl = $location.absUrl(); + var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + $location.$$state, oldState).defaultPrevented; + + // if the location was changed by a `$locationChangeStart` handler then stop + // processing this location change + if ($location.absUrl() !== newUrl) return; + + if (defaultPrevented) { $location.$$parse(oldUrl); + $location.$$state = oldState; } else { - $browser.url($location.absUrl(), currentReplace); - afterLocationChange(oldUrl); + if (urlOrStateChanged) { + setBrowserUrlWithFallback(newUrl, currentReplace, + oldState === $location.$$state ? null : $location.$$state); + } + afterLocationChange(oldUrl, oldState); } }); } + $location.$$replace = false; - return changeCounter; + // we don't need to return anything because $evalAsync will make the digest loop dirty when + // there is a change }); return $location; - function afterLocationChange(oldUrl) { - $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); + function afterLocationChange(oldUrl, oldState) { + $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, + $location.$$state, oldState); } }]; } @@ -10006,6 +11585,7 @@ function $LocationProvider(){ +
    @@ -10017,7 +11597,7 @@ function $LocationProvider(){ * @description * Use the `$logProvider` to configure how the application logs messages */ -function $LogProvider(){ +function $LogProvider() { var debug = true, self = this; @@ -10037,7 +11617,7 @@ function $LogProvider(){ } }; - this.$get = ['$window', function($window){ + this.$get = ['$window', function($window) { return { /** * @ngdoc method @@ -10082,7 +11662,7 @@ function $LogProvider(){ * @description * Write a debug message */ - debug: (function () { + debug: (function() { var fn = consoleLog('debug'); return function() { @@ -10136,9 +11716,18 @@ function $LogProvider(){ }]; } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + var $parseMinErr = minErr('$parse'); -var promiseWarningCache = {}; -var promiseWarning; // Sandboxing Angular Expressions // ------------------------------ @@ -10170,7 +11759,26 @@ function ensureSafeMemberName(name, fullExpression) { || name === "__proto__") { throw $parseMinErr('isecfld', 'Attempting to access a disallowed field in Angular expressions! ' - +'Expression: {0}', fullExpression); + + 'Expression: {0}', fullExpression); + } + return name; +} + +function getStringValue(name, fullExpression) { + // From the JavaScript docs: + // Property names must be strings. This means that non-string objects cannot be used + // as keys in an object. Any non-string object, including a number, is typecasted + // into a string via the toString method. + // + // So, to ensure that we are checking the same `name` that JavaScript would use, + // we cast it to a string, if possible. + // Doing `name + ''` can cause a repl error if the result to `toString` is not a string, + // this is, this will handle objects that misbehave. + name = name + ''; + if (!isString(name)) { + throw $parseMinErr('iseccst', + 'Cannot convert object to primitive value! ' + + 'Expression: {0}', fullExpression); } return name; } @@ -10183,7 +11791,7 @@ function ensureSafeObject(obj, fullExpression) { 'Referencing Function in Angular expressions is disallowed! Expression: {0}', fullExpression); } else if (// isWindow(obj) - obj.document && obj.location && obj.alert && obj.setInterval) { + obj.window === obj) { throw $parseMinErr('isecwindow', 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', fullExpression); @@ -10212,7 +11820,7 @@ function ensureSafeFunction(obj, fullExpression) { throw $parseMinErr('isecfn', 'Referencing Function in Angular expressions is disallowed! Expression: {0}', fullExpression); - } else if (obj === CALL || obj === APPLY || (BIND && obj === BIND)) { + } else if (obj === CALL || obj === APPLY || obj === BIND) { throw $parseMinErr('isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', fullExpression); @@ -10220,13 +11828,26 @@ function ensureSafeFunction(obj, fullExpression) { } } -var OPERATORS = { - /* jshint bitwise : false */ - 'null':function(){return null;}, - 'true':function(){return true;}, - 'false':function(){return false;}, - undefined:noop, - '+':function(self, locals, a,b){ +//Keyword constants +var CONSTANTS = createMap(); +forEach({ + 'null': function() { return null; }, + 'true': function() { return true; }, + 'false': function() { return false; }, + 'undefined': function() {} +}, function(constantGetter, name) { + constantGetter.constant = constantGetter.literal = constantGetter.sharedGetter = true; + CONSTANTS[name] = constantGetter; +}); + +//Not quite a constant, but can be lex/parsed the same +CONSTANTS['this'] = function(self) { return self; }; +CONSTANTS['this'].sharedGetter = true; + + +//Operators - will be wrapped by binaryFn/unaryFn/assignment/filter +var OPERATORS = extend(createMap(), { + '+':function(self, locals, a, b) { a=a(self, locals); b=b(self, locals); if (isDefined(a)) { if (isDefined(b)) { @@ -10234,32 +11855,30 @@ var OPERATORS = { } return a; } - return isDefined(b)?b:undefined;}, - '-':function(self, locals, a,b){ + return isDefined(b) ? b : undefined;}, + '-':function(self, locals, a, b) { a=a(self, locals); b=b(self, locals); - return (isDefined(a)?a:0)-(isDefined(b)?b:0); + return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0); }, - '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, - '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, - '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, - '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, - '=':noop, - '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, - '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, - '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, - '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, - '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, - '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, - '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, - '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, - '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, - '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, -// '|':function(self, locals, a,b){return a|b;}, - '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, - '!':function(self, locals, a){return !a(self, locals);} -}; -/* jshint bitwise: true */ + '*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);}, + '/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);}, + '%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);}, + '===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);}, + '!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);}, + '==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);}, + '!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);}, + '<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);}, + '>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);}, + '<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);}, + '>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);}, + '&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);}, + '||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);}, + '!':function(self, locals, a) {return !a(self, locals);}, + + //Tokenized as operators but parsed as assignment/filters + '=':true, + '|':true +}); var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; @@ -10269,73 +11888,51 @@ var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"' /** * @constructor */ -var Lexer = function (options) { +var Lexer = function(options) { this.options = options; }; Lexer.prototype = { constructor: Lexer, - lex: function (text) { + lex: function(text) { this.text = text; - this.index = 0; - this.ch = undefined; - this.lastCh = ':'; // can start regexp - this.tokens = []; while (this.index < this.text.length) { - this.ch = this.text.charAt(this.index); - if (this.is('"\'')) { - this.readString(this.ch); - } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { + var ch = this.text.charAt(this.index); + if (ch === '"' || ch === "'") { + this.readString(ch); + } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { this.readNumber(); - } else if (this.isIdent(this.ch)) { + } else if (this.isIdent(ch)) { this.readIdent(); - } else if (this.is('(){}[].,;:?')) { - this.tokens.push({ - index: this.index, - text: this.ch - }); + } else if (this.is(ch, '(){}[].,;:?')) { + this.tokens.push({index: this.index, text: ch}); this.index++; - } else if (this.isWhitespace(this.ch)) { + } else if (this.isWhitespace(ch)) { this.index++; - continue; } else { - var ch2 = this.ch + this.peek(); + var ch2 = ch + this.peek(); var ch3 = ch2 + this.peek(2); - var fn = OPERATORS[this.ch]; - var fn2 = OPERATORS[ch2]; - var fn3 = OPERATORS[ch3]; - if (fn3) { - this.tokens.push({index: this.index, text: ch3, fn: fn3}); - this.index += 3; - } else if (fn2) { - this.tokens.push({index: this.index, text: ch2, fn: fn2}); - this.index += 2; - } else if (fn) { - this.tokens.push({ - index: this.index, - text: this.ch, - fn: fn - }); - this.index += 1; + var op1 = OPERATORS[ch]; + var op2 = OPERATORS[ch2]; + var op3 = OPERATORS[ch3]; + if (op1 || op2 || op3) { + var token = op3 ? ch3 : (op2 ? ch2 : ch); + this.tokens.push({index: this.index, text: token, operator: true}); + this.index += token.length; } else { this.throwError('Unexpected next character ', this.index, this.index + 1); } } - this.lastCh = this.ch; } return this.tokens; }, - is: function(chars) { - return chars.indexOf(this.ch) !== -1; - }, - - was: function(chars) { - return chars.indexOf(this.lastCh) !== -1; + is: function(ch, chars) { + return chars.indexOf(ch) !== -1; }, peek: function(i) { @@ -10344,7 +11941,7 @@ Lexer.prototype = { }, isNumber: function(ch) { - return ('0' <= ch && ch <= '9'); + return ('0' <= ch && ch <= '9') && typeof ch === "string"; }, isWhitespace: function(ch) { @@ -10397,88 +11994,28 @@ Lexer.prototype = { } this.index++; } - number = 1 * number; this.tokens.push({ index: start, text: number, - literal: true, constant: true, - fn: function() { return number; } + value: Number(number) }); }, readIdent: function() { - var parser = this; - - var ident = ''; var start = this.index; - - var lastDot, peekIndex, methodName, ch; - while (this.index < this.text.length) { - ch = this.text.charAt(this.index); - if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { - if (ch === '.') lastDot = this.index; - ident += ch; - } else { + var ch = this.text.charAt(this.index); + if (!(this.isIdent(ch) || this.isNumber(ch))) { break; } this.index++; } - - //check if this is not a method invocation and if it is back out to last dot - if (lastDot) { - peekIndex = this.index; - while (peekIndex < this.text.length) { - ch = this.text.charAt(peekIndex); - if (ch === '(') { - methodName = ident.substr(lastDot - start + 1); - ident = ident.substr(0, lastDot - start); - this.index = peekIndex; - break; - } - if (this.isWhitespace(ch)) { - peekIndex++; - } else { - break; - } - } - } - - - var token = { + this.tokens.push({ index: start, - text: ident - }; - - // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn - if (OPERATORS.hasOwnProperty(ident)) { - token.fn = OPERATORS[ident]; - token.literal = true; - token.constant = true; - } else { - var getter = getterFn(ident, this.options, this.text); - token.fn = extend(function(self, locals) { - return (getter(self, locals)); - }, { - assign: function(self, value) { - return setter(self, ident, value, parser.text, parser.options); - } - }); - } - - this.tokens.push(token); - - if (methodName) { - this.tokens.push({ - index:lastDot, - text: '.' - }); - this.tokens.push({ - index: lastDot + 1, - text: methodName - }); - } + text: this.text.slice(start, this.index), + identifier: true + }); }, readString: function(quote) { @@ -10509,10 +12046,8 @@ Lexer.prototype = { this.tokens.push({ index: start, text: rawString, - string: string, - literal: true, constant: true, - fn: function() { return string; } + value: string }); return; } else { @@ -10525,27 +12060,31 @@ Lexer.prototype = { }; +function isConstant(exp) { + return exp.constant; +} + /** * @constructor */ -var Parser = function (lexer, $filter, options) { +var Parser = function(lexer, $filter, options) { this.lexer = lexer; this.$filter = $filter; this.options = options; }; -Parser.ZERO = extend(function () { +Parser.ZERO = extend(function() { return 0; }, { + sharedGetter: true, constant: true }); Parser.prototype = { constructor: Parser, - parse: function (text) { + parse: function(text) { this.text = text; - this.tokens = this.lexer.lex(text); var value = this.statements(); @@ -10560,7 +12099,7 @@ Parser.prototype = { return value; }, - primary: function () { + primary: function() { var primary; if (this.expect('(')) { primary = this.filterChain(); @@ -10569,14 +12108,14 @@ Parser.prototype = { primary = this.arrayDeclaration(); } else if (this.expect('{')) { primary = this.object(); + } else if (this.peek().identifier && this.peek().text in CONSTANTS) { + primary = CONSTANTS[this.consume().text]; + } else if (this.peek().identifier) { + primary = this.identifier(); + } else if (this.peek().constant) { + primary = this.constant(); } else { - var token = this.expect(); - primary = token.fn; - if (!primary) { - this.throwError('not a primary expression', token); - } - primary.literal = !!token.literal; - primary.constant = !!token.constant; + this.throwError('not a primary expression', this.peek()); } var next, context; @@ -10610,8 +12149,11 @@ Parser.prototype = { }, peek: function(e1, e2, e3, e4) { - if (this.tokens.length > 0) { - var token = this.tokens[0]; + return this.peekAhead(0, e1, e2, e3, e4); + }, + peekAhead: function(i, e1, e2, e3, e4) { + if (this.tokens.length > i) { + var token = this.tokens[i]; var t = token.text; if (t === e1 || t === e2 || t === e3 || t === e4 || (!e1 && !e2 && !e3 && !e4)) { @@ -10621,7 +12163,7 @@ Parser.prototype = { return false; }, - expect: function(e1, e2, e3, e4){ + expect: function(e1, e2, e3, e4) { var token = this.peek(e1, e2, e3, e4); if (token) { this.tokens.shift(); @@ -10630,33 +12172,57 @@ Parser.prototype = { return false; }, - consume: function(e1){ - if (!this.expect(e1)) { + consume: function(e1) { + if (this.tokens.length === 0) { + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); + } + + var token = this.expect(e1); + if (!token) { this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); } + return token; }, - unaryFn: function(fn, right) { - return extend(function(self, locals) { + unaryFn: function(op, right) { + var fn = OPERATORS[op]; + return extend(function $parseUnaryFn(self, locals) { return fn(self, locals, right); }, { - constant:right.constant + constant:right.constant, + inputs: [right] }); }, - ternaryFn: function(left, middle, right){ - return extend(function(self, locals){ - return left(self, locals) ? middle(self, locals) : right(self, locals); - }, { - constant: left.constant && middle.constant && right.constant - }); - }, - - binaryFn: function(left, fn, right) { - return extend(function(self, locals) { + binaryFn: function(left, op, right, isBranching) { + var fn = OPERATORS[op]; + return extend(function $parseBinaryFn(self, locals) { return fn(self, locals, left, right); }, { - constant:left.constant && right.constant + constant: left.constant && right.constant, + inputs: !isBranching && [left, right] + }); + }, + + identifier: function() { + var id = this.consume().text; + + //Continue reading each `.identifier` unless it is a method invocation + while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) { + id += this.consume().text + this.consume().text; + } + + return getterFn(id, this.options, this.text); + }, + + constant: function() { + var value = this.consume().value; + + return extend(function $parseConstant() { + return value; + }, { + constant: true, + literal: true }); }, @@ -10670,13 +12236,10 @@ Parser.prototype = { // TODO(size): maybe we should not support multiple statements? return (statements.length === 1) ? statements[0] - : function(self, locals) { + : function $parseStatements(self, locals) { var value; - for (var i = 0; i < statements.length; i++) { - var statement = statements[i]; - if (statement) { - value = statement(self, locals); - } + for (var i = 0, ii = statements.length; i < ii; i++) { + value = statements[i](self, locals); } return value; }; @@ -10687,35 +12250,45 @@ Parser.prototype = { filterChain: function() { var left = this.expression(); var token; - while (true) { - if ((token = this.expect('|'))) { - left = this.binaryFn(left, token.fn, this.filter()); - } else { - return left; - } + while ((token = this.expect('|'))) { + left = this.filter(left); } + return left; }, - filter: function() { - var token = this.expect(); - var fn = this.$filter(token.text); - var argsFn = []; - while (true) { - if ((token = this.expect(':'))) { + filter: function(inputFn) { + var fn = this.$filter(this.consume().text); + var argsFn; + var args; + + if (this.peek(':')) { + argsFn = []; + args = []; // we can safely reuse the array + while (this.expect(':')) { argsFn.push(this.expression()); - } else { - var fnInvoke = function(self, locals, input) { - var args = [input]; - for (var i = 0; i < argsFn.length; i++) { - args.push(argsFn[i](self, locals)); - } - return fn.apply(self, args); - }; - return function() { - return fnInvoke; - }; } } + + var inputs = [inputFn].concat(argsFn || []); + + return extend(function $parseFilter(self, locals) { + var input = inputFn(self, locals); + if (args) { + args[0] = input; + + var i = argsFn.length; + while (i--) { + args[i + 1] = argsFn[i](self, locals); + } + + return fn.apply(undefined, args); + } + + return fn(input); + }, { + constant: !fn.$stateful && inputs.every(isConstant), + inputs: !fn.$stateful && inputs + }); }, expression: function() { @@ -10732,9 +12305,11 @@ Parser.prototype = { this.text.substring(0, token.index) + '] can not be assigned to', token); } right = this.ternary(); - return function(scope, locals) { + return extend(function $parseAssignment(scope, locals) { return left.assign(scope, right(scope, locals), locals); - }; + }, { + inputs: [left, right] + }); } return left; }, @@ -10745,33 +12320,34 @@ Parser.prototype = { var token; if ((token = this.expect('?'))) { middle = this.assignment(); - if ((token = this.expect(':'))) { - return this.ternaryFn(left, middle, this.assignment()); - } else { - this.throwError('expected :', token); + if (this.consume(':')) { + var right = this.assignment(); + + return extend(function $parseTernary(self, locals) { + return left(self, locals) ? middle(self, locals) : right(self, locals); + }, { + constant: left.constant && middle.constant && right.constant + }); } - } else { - return left; } + + return left; }, logicalOR: function() { var left = this.logicalAND(); var token; - while (true) { - if ((token = this.expect('||'))) { - left = this.binaryFn(left, token.fn, this.logicalAND()); - } else { - return left; - } + while ((token = this.expect('||'))) { + left = this.binaryFn(left, token.text, this.logicalAND(), true); } + return left; }, logicalAND: function() { var left = this.equality(); var token; - if ((token = this.expect('&&'))) { - left = this.binaryFn(left, token.fn, this.logicalAND()); + while ((token = this.expect('&&'))) { + left = this.binaryFn(left, token.text, this.equality(), true); } return left; }, @@ -10779,8 +12355,8 @@ Parser.prototype = { equality: function() { var left = this.relational(); var token; - if ((token = this.expect('==','!=','===','!=='))) { - left = this.binaryFn(left, token.fn, this.equality()); + while ((token = this.expect('==','!=','===','!=='))) { + left = this.binaryFn(left, token.text, this.relational()); } return left; }, @@ -10788,8 +12364,8 @@ Parser.prototype = { relational: function() { var left = this.additive(); var token; - if ((token = this.expect('<', '>', '<=', '>='))) { - left = this.binaryFn(left, token.fn, this.relational()); + while ((token = this.expect('<', '>', '<=', '>='))) { + left = this.binaryFn(left, token.text, this.additive()); } return left; }, @@ -10798,7 +12374,7 @@ Parser.prototype = { var left = this.multiplicative(); var token; while ((token = this.expect('+','-'))) { - left = this.binaryFn(left, token.fn, this.multiplicative()); + left = this.binaryFn(left, token.text, this.multiplicative()); } return left; }, @@ -10807,7 +12383,7 @@ Parser.prototype = { var left = this.unary(); var token; while ((token = this.expect('*','/','%'))) { - left = this.binaryFn(left, token.fn, this.unary()); + left = this.binaryFn(left, token.text, this.unary()); } return left; }, @@ -10817,65 +12393,56 @@ Parser.prototype = { if (this.expect('+')) { return this.primary(); } else if ((token = this.expect('-'))) { - return this.binaryFn(Parser.ZERO, token.fn, this.unary()); + return this.binaryFn(Parser.ZERO, token.text, this.unary()); } else if ((token = this.expect('!'))) { - return this.unaryFn(token.fn, this.unary()); + return this.unaryFn(token.text, this.unary()); } else { return this.primary(); } }, fieldAccess: function(object) { - var parser = this; - var field = this.expect().text; - var getter = getterFn(field, this.options, this.text); + var getter = this.identifier(); - return extend(function(scope, locals, self) { - return getter(self || object(scope, locals)); + return extend(function $parseFieldAccess(scope, locals, self) { + var o = self || object(scope, locals); + return (o == null) ? undefined : getter(o); }, { assign: function(scope, value, locals) { var o = object(scope, locals); - if (!o) object.assign(scope, o = {}); - return setter(o, field, value, parser.text, parser.options); + if (!o) object.assign(scope, o = {}, locals); + return getter.assign(o, value); } }); }, objectIndex: function(obj) { - var parser = this; + var expression = this.text; var indexFn = this.expression(); this.consume(']'); - return extend(function(self, locals) { + return extend(function $parseObjectIndex(self, locals) { var o = obj(self, locals), - i = indexFn(self, locals), - v, p; + i = getStringValue(indexFn(self, locals), expression), + v; - ensureSafeMemberName(i, parser.text); + ensureSafeMemberName(i, expression); if (!o) return undefined; - v = ensureSafeObject(o[i], parser.text); - if (v && v.then && parser.options.unwrapPromises) { - p = v; - if (!('$$v' in v)) { - p.$$v = undefined; - p.then(function(val) { p.$$v = val; }); - } - v = v.$$v; - } + v = ensureSafeObject(o[i], expression); return v; }, { assign: function(self, value, locals) { - var key = ensureSafeMemberName(indexFn(self, locals), parser.text); + var key = ensureSafeMemberName(getStringValue(indexFn(self, locals), expression), expression); // prevent overwriting of Function.constructor which would break ensureSafeObject check - var o = ensureSafeObject(obj(self, locals), parser.text); - if (!o) obj.assign(self, o = {}); + var o = ensureSafeObject(obj(self, locals), expression); + if (!o) obj.assign(self, o = {}, locals); return o[key] = value; } }); }, - functionCall: function(fn, contextGetter) { + functionCall: function(fnGetter, contextGetter) { var argsFn = []; if (this.peekToken().text !== ')') { do { @@ -10884,91 +12451,97 @@ Parser.prototype = { } this.consume(')'); - var parser = this; + var expressionText = this.text; + // we can safely reuse the array across invocations + var args = argsFn.length ? [] : null; - return function(scope, locals) { - var args = []; - var context = contextGetter ? contextGetter(scope, locals) : scope; + return function $parseFunctionCall(scope, locals) { + var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope; + var fn = fnGetter(scope, locals, context) || noop; - for (var i = 0; i < argsFn.length; i++) { - args.push(ensureSafeObject(argsFn[i](scope, locals), parser.text)); + if (args) { + var i = argsFn.length; + while (i--) { + args[i] = ensureSafeObject(argsFn[i](scope, locals), expressionText); + } } - var fnPtr = fn(scope, locals, context) || noop; - ensureSafeObject(context, parser.text); - ensureSafeFunction(fnPtr, parser.text); + ensureSafeObject(context, expressionText); + ensureSafeFunction(fn, expressionText); // IE doesn't have apply for some native functions - var v = fnPtr.apply - ? fnPtr.apply(context, args) - : fnPtr(args[0], args[1], args[2], args[3], args[4]); + var v = fn.apply + ? fn.apply(context, args) + : fn(args[0], args[1], args[2], args[3], args[4]); - return ensureSafeObject(v, parser.text); - }; + if (args) { + // Free-up the memory (arguments of the last function call). + args.length = 0; + } + + return ensureSafeObject(v, expressionText); + }; }, // This is used with json array declaration - arrayDeclaration: function () { + arrayDeclaration: function() { var elementFns = []; - var allConstant = true; if (this.peekToken().text !== ']') { do { if (this.peek(']')) { // Support trailing commas per ES5.1. break; } - var elementFn = this.expression(); - elementFns.push(elementFn); - if (!elementFn.constant) { - allConstant = false; - } + elementFns.push(this.expression()); } while (this.expect(',')); } this.consume(']'); - return extend(function(self, locals) { + return extend(function $parseArrayLiteral(self, locals) { var array = []; - for (var i = 0; i < elementFns.length; i++) { + for (var i = 0, ii = elementFns.length; i < ii; i++) { array.push(elementFns[i](self, locals)); } return array; }, { literal: true, - constant: allConstant + constant: elementFns.every(isConstant), + inputs: elementFns }); }, - object: function () { - var keyValues = []; - var allConstant = true; + object: function() { + var keys = [], valueFns = []; if (this.peekToken().text !== '}') { do { if (this.peek('}')) { // Support trailing commas per ES5.1. break; } - var token = this.expect(), - key = token.string || token.text; - this.consume(':'); - var value = this.expression(); - keyValues.push({key: key, value: value}); - if (!value.constant) { - allConstant = false; + var token = this.consume(); + if (token.constant) { + keys.push(token.value); + } else if (token.identifier) { + keys.push(token.text); + } else { + this.throwError("invalid key", token); } + this.consume(':'); + valueFns.push(this.expression()); } while (this.expect(',')); } this.consume('}'); - return extend(function(self, locals) { + return extend(function $parseObjectLiteral(self, locals) { var object = {}; - for (var i = 0; i < keyValues.length; i++) { - var keyValue = keyValues[i]; - object[keyValue.key] = keyValue.value(self, locals); + for (var i = 0, ii = valueFns.length; i < ii; i++) { + object[keys[i]] = valueFns[i](self, locals); } return object; }, { literal: true, - constant: allConstant + constant: valueFns.every(isConstant), + inputs: valueFns }); } }; @@ -10978,33 +12551,19 @@ Parser.prototype = { // Parser helper functions ////////////////////////////////////////////////// -function setter(obj, path, setValue, fullExp, options) { +function setter(obj, locals, path, setValue, fullExp) { ensureSafeObject(obj, fullExp); - - //needed? - options = options || {}; + ensureSafeObject(locals, fullExp); var element = path.split('.'), key; for (var i = 0; element.length > 1; i++) { key = ensureSafeMemberName(element.shift(), fullExp); - var propertyObj = ensureSafeObject(obj[key], fullExp); + var propertyObj = (i === 0 && locals && locals[key]) || obj[key]; if (!propertyObj) { propertyObj = {}; obj[key] = propertyObj; } - obj = propertyObj; - if (obj.then && options.unwrapPromises) { - promiseWarning(fullExp); - if (!("$$v" in obj)) { - (function(promise) { - promise.then(function(val) { promise.$$v = val; }); } - )(obj); - } - if (obj.$$v === undefined) { - obj.$$v = {}; - } - obj = obj.$$v; - } + obj = ensureSafeObject(propertyObj, fullExp); } key = ensureSafeMemberName(element.shift(), fullExp); ensureSafeObject(obj[key], fullExp); @@ -11012,8 +12571,8 @@ function setter(obj, path, setValue, fullExp, options) { return setValue; } -var getterFnCacheDefault = {}; -var getterFnCacheExpensive = {}; +var getterFnCacheDefault = createMap(); +var getterFnCacheExpensive = createMap(); function isPossiblyDangerousMemberName(name) { return name == 'constructor'; @@ -11024,7 +12583,7 @@ function isPossiblyDangerousMemberName(name) { * - http://jsperf.com/angularjs-parse-getter/4 * - http://jsperf.com/path-evaluation-simplified/7 */ -function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { +function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, expensiveChecks) { ensureSafeMemberName(key0, fullExp); ensureSafeMemberName(key1, fullExp); ensureSafeMemberName(key2, fullExp); @@ -11033,141 +12592,64 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { var eso = function(o) { return ensureSafeObject(o, fullExp); }; - var expensiveChecks = options.expensiveChecks; var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity; var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity; var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity; var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity; var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity; - return !options.unwrapPromises - ? function cspSafeGetter(scope, locals) { - var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; + return function cspSafeGetter(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; - if (pathVal == null) return pathVal; - pathVal = eso0(pathVal[key0]); + if (pathVal == null) return pathVal; + pathVal = eso0(pathVal[key0]); - if (!key1) return pathVal; - if (pathVal == null) return undefined; - pathVal = eso1(pathVal[key1]); + if (!key1) return pathVal; + if (pathVal == null) return undefined; + pathVal = eso1(pathVal[key1]); - if (!key2) return pathVal; - if (pathVal == null) return undefined; - pathVal = eso2(pathVal[key2]); + if (!key2) return pathVal; + if (pathVal == null) return undefined; + pathVal = eso2(pathVal[key2]); - if (!key3) return pathVal; - if (pathVal == null) return undefined; - pathVal = eso3(pathVal[key3]); + if (!key3) return pathVal; + if (pathVal == null) return undefined; + pathVal = eso3(pathVal[key3]); - if (!key4) return pathVal; - if (pathVal == null) return undefined; - pathVal = eso4(pathVal[key4]); + if (!key4) return pathVal; + if (pathVal == null) return undefined; + pathVal = eso4(pathVal[key4]); - return pathVal; - } - : function cspSafePromiseEnabledGetter(scope, locals) { - var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, - promise; - - if (pathVal == null) return pathVal; - - pathVal = eso0(pathVal[key0]); - if (pathVal && pathVal.then) { - promiseWarning(fullExp); - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = eso0(val); }); - } - pathVal = eso0(pathVal.$$v); - } - - if (!key1) return pathVal; - if (pathVal == null) return undefined; - pathVal = eso1(pathVal[key1]); - if (pathVal && pathVal.then) { - promiseWarning(fullExp); - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = eso1(val); }); - } - pathVal = eso1(pathVal.$$v); - } - - if (!key2) return pathVal; - if (pathVal == null) return undefined; - pathVal = eso2(pathVal[key2]); - if (pathVal && pathVal.then) { - promiseWarning(fullExp); - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = eso2(val); }); - } - pathVal = eso2(pathVal.$$v); - } - - if (!key3) return pathVal; - if (pathVal == null) return undefined; - pathVal = eso3(pathVal[key3]); - if (pathVal && pathVal.then) { - promiseWarning(fullExp); - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = eso3(val); }); - } - pathVal = eso3(pathVal.$$v); - } - - if (!key4) return pathVal; - if (pathVal == null) return undefined; - pathVal = eso4(pathVal[key4]); - if (pathVal && pathVal.then) { - promiseWarning(fullExp); - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = eso4(val); }); - } - pathVal = eso4(pathVal.$$v); - } - return pathVal; - }; + return pathVal; + }; } -function getterFnWithExtraArgs(fn, fullExpression) { +function getterFnWithEnsureSafeObject(fn, fullExpression) { return function(s, l) { - return fn(s, l, promiseWarning, ensureSafeObject, fullExpression); + return fn(s, l, ensureSafeObject, fullExpression); }; } function getterFn(path, options, fullExp) { var expensiveChecks = options.expensiveChecks; var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault); - // Check whether the cache has this getter already. - // We can use hasOwnProperty directly on the cache because we ensure, - // see below, that the cache never stores a path called 'hasOwnProperty' - if (getterFnCache.hasOwnProperty(path)) { - return getterFnCache[path]; - } + var fn = getterFnCache[path]; + if (fn) return fn; + var pathKeys = path.split('.'), - pathKeysLength = pathKeys.length, - fn; + pathKeysLength = pathKeys.length; // http://jsperf.com/angularjs-parse-getter/6 if (options.csp) { if (pathKeysLength < 6) { - fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, - options); + fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, expensiveChecks); } else { - fn = function(scope, locals) { + fn = function cspSafeGetter(scope, locals) { var i = 0, val; do { val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], - pathKeys[i++], fullExp, options)(scope, locals); + pathKeys[i++], fullExp, expensiveChecks)(scope, locals); locals = undefined; // clear after first iteration scope = val; @@ -11176,7 +12658,7 @@ function getterFn(path, options, fullExp) { }; } } else { - var code = 'var p;\n'; + var code = ''; if (expensiveChecks) { code += 's = eso(s, fe);\nl = eso(l, fe);\n'; } @@ -11187,48 +12669,40 @@ function getterFn(path, options, fullExp) { // we simply dereference 's' on any .dot notation ? 's' // but if we are first then we check locals first, and if so read it first - : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '["' + key + '"]'; - var wrapWithEso = expensiveChecks || isPossiblyDangerousMemberName(key); - if (wrapWithEso) { + : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key; + if (expensiveChecks || isPossiblyDangerousMemberName(key)) { lookupJs = 'eso(' + lookupJs + ', fe)'; needsEnsureSafeObject = true; } code += 'if(s == null) return undefined;\n' + 's=' + lookupJs + ';\n'; - if (options.unwrapPromises) { - code += 'if (s && s.then) {\n' + - ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' + - ' if (!("$$v" in s)) {\n' + - ' p=s;\n' + - ' p.$$v = undefined;\n' + - ' p.then(function(v) {p.$$v=' + (wrapWithEso ? 'eso(v)' : 'v') + ';});\n' + - '}\n' + - ' s=' + (wrapWithEso ? 'eso(s.$$v)' : 's.$$v') + '\n' + - '}\n'; - - } }); code += 'return s;'; /* jshint -W054 */ - // s=scope, l=locals, pw=promiseWarning, eso=ensureSafeObject, fe=fullExpression - var evaledFnGetter = new Function('s', 'l', 'pw', 'eso', 'fe', code); + var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject /* jshint +W054 */ evaledFnGetter.toString = valueFn(code); - if (needsEnsureSafeObject || options.unwrapPromises) { - evaledFnGetter = getterFnWithExtraArgs(evaledFnGetter, fullExp); + if (needsEnsureSafeObject) { + evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp); } fn = evaledFnGetter; } - // Only cache the value if it's not going to mess up the cache object - // This is more performant that using Object.prototype.hasOwnProperty.call - if (path !== 'hasOwnProperty') { - getterFnCache[path] = fn; - } + fn.sharedGetter = true; + fn.assign = function(self, value, locals) { + return setter(self, locals, path, value, path); + }; + getterFnCache[path] = fn; return fn; } +var objectValueOf = Object.prototype.valueOf; + +function getValueOf(value) { + return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); +} + /////////////////////////////////// /** @@ -11275,152 +12749,257 @@ function getterFn(path, options, fullExp) { /** * @ngdoc provider * @name $parseProvider - * @kind function * * @description * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} * service. */ function $ParseProvider() { - var cacheDefault = {}; - var cacheExpensive = {}; - - var $parseOptions = { - csp: false, - unwrapPromises: false, - logPromiseWarnings: true, - expensiveChecks: false - }; + var cacheDefault = createMap(); + var cacheExpensive = createMap(); - /** - * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. - * - * @ngdoc method - * @name $parseProvider#unwrapPromises - * @description - * - * **This feature is deprecated, see deprecation notes below for more info** - * - * If set to true (default is false), $parse will unwrap promises automatically when a promise is - * found at any part of the expression. In other words, if set to true, the expression will always - * result in a non-promise value. - * - * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, - * the fulfillment value is used in place of the promise while evaluating the expression. - * - * **Deprecation notice** - * - * This is a feature that didn't prove to be wildly useful or popular, primarily because of the - * dichotomy between data access in templates (accessed as raw values) and controller code - * (accessed as promises). - * - * In most code we ended up resolving promises manually in controllers anyway and thus unifying - * the model access there. - * - * Other downsides of automatic promise unwrapping: - * - * - when building components it's often desirable to receive the raw promises - * - adds complexity and slows down expression evaluation - * - makes expression code pre-generation unattractive due to the amount of code that needs to be - * generated - * - makes IDE auto-completion and tool support hard - * - * **Warning Logs** - * - * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a - * promise (to reduce the noise, each expression is logged only once). To disable this logging use - * `$parseProvider.logPromiseWarnings(false)` api. - * - * - * @param {boolean=} value New value. - * @returns {boolean|self} Returns the current setting when used as getter and self if used as - * setter. - */ - this.unwrapPromises = function(value) { - if (isDefined(value)) { - $parseOptions.unwrapPromises = !!value; - return this; - } else { - return $parseOptions.unwrapPromises; + + this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { + var $parseOptions = { + csp: $sniffer.csp, + expensiveChecks: false + }, + $parseOptionsExpensive = { + csp: $sniffer.csp, + expensiveChecks: true + }; + + function wrapSharedExpression(exp) { + var wrapped = exp; + + if (exp.sharedGetter) { + wrapped = function $parseWrapper(self, locals) { + return exp(self, locals); + }; + wrapped.literal = exp.literal; + wrapped.constant = exp.constant; + wrapped.assign = exp.assign; + } + + return wrapped; } - }; - - /** - * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. - * - * @ngdoc method - * @name $parseProvider#logPromiseWarnings - * @description - * - * Controls whether Angular should log a warning on any encounter of a promise in an expression. - * - * The default is set to `true`. - * - * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. - * - * @param {boolean=} value New value. - * @returns {boolean|self} Returns the current setting when used as getter and self if used as - * setter. - */ - this.logPromiseWarnings = function(value) { - if (isDefined(value)) { - $parseOptions.logPromiseWarnings = value; - return this; - } else { - return $parseOptions.logPromiseWarnings; - } - }; - - - this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { - $parseOptions.csp = $sniffer.csp; - var $parseOptionsExpensive = { - csp: $parseOptions.csp, - unwrapPromises: $parseOptions.unwrapPromises, - logPromiseWarnings: $parseOptions.logPromiseWarnings, - expensiveChecks: true - }; - - promiseWarning = function promiseWarningFn(fullExp) { - if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; - promiseWarningCache[fullExp] = true; - $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + - 'Automatic unwrapping of promises in Angular expressions is deprecated.'); - }; - - return function(exp, expensiveChecks) { - var parsedExpression; + return function $parse(exp, interceptorFn, expensiveChecks) { + var parsedExpression, oneTime, cacheKey; switch (typeof exp) { case 'string': + cacheKey = exp = exp.trim(); var cache = (expensiveChecks ? cacheExpensive : cacheDefault); - if (cache.hasOwnProperty(exp)) { - return cache[exp]; + parsedExpression = cache[cacheKey]; + + if (!parsedExpression) { + if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { + oneTime = true; + exp = exp.substring(2); + } + + var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; + var lexer = new Lexer(parseOptions); + var parser = new Parser(lexer, $filter, parseOptions); + parsedExpression = parser.parse(exp); + + if (parsedExpression.constant) { + parsedExpression.$$watchDelegate = constantWatchDelegate; + } else if (oneTime) { + //oneTime is not part of the exp passed to the Parser so we may have to + //wrap the parsedExpression before adding a $$watchDelegate + parsedExpression = wrapSharedExpression(parsedExpression); + parsedExpression.$$watchDelegate = parsedExpression.literal ? + oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; + } else if (parsedExpression.inputs) { + parsedExpression.$$watchDelegate = inputsWatchDelegate; + } + + cache[cacheKey] = parsedExpression; } - - var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; - var lexer = new Lexer(parseOptions); - var parser = new Parser(lexer, $filter, parseOptions); - parsedExpression = parser.parse(exp); - - if (exp !== 'hasOwnProperty') { - // Only cache the value if it's not going to mess up the cache object - // This is more performant that using Object.prototype.hasOwnProperty.call - cache[exp] = parsedExpression; - } - - return parsedExpression; + return addInterceptor(parsedExpression, interceptorFn); case 'function': - return exp; + return addInterceptor(exp, interceptorFn); default: - return noop; + return addInterceptor(noop, interceptorFn); } }; + + function collectExpressionInputs(inputs, list) { + for (var i = 0, ii = inputs.length; i < ii; i++) { + var input = inputs[i]; + if (!input.constant) { + if (input.inputs) { + collectExpressionInputs(input.inputs, list); + } else if (list.indexOf(input) === -1) { // TODO(perf) can we do better? + list.push(input); + } + } + } + + return list; + } + + function expressionInputDirtyCheck(newValue, oldValueOfValue) { + + if (newValue == null || oldValueOfValue == null) { // null/undefined + return newValue === oldValueOfValue; + } + + if (typeof newValue === 'object') { + + // attempt to convert the value to a primitive type + // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can + // be cheaply dirty-checked + newValue = getValueOf(newValue); + + if (typeof newValue === 'object') { + // objects/arrays are not supported - deep-watching them would be too expensive + return false; + } + + // fall-through to the primitive equality check + } + + //Primitive or NaN + return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); + } + + function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression) { + var inputExpressions = parsedExpression.$$inputs || + (parsedExpression.$$inputs = collectExpressionInputs(parsedExpression.inputs, [])); + + var lastResult; + + if (inputExpressions.length === 1) { + var oldInputValue = expressionInputDirtyCheck; // init to something unique so that equals check fails + inputExpressions = inputExpressions[0]; + return scope.$watch(function expressionInputWatch(scope) { + var newInputValue = inputExpressions(scope); + if (!expressionInputDirtyCheck(newInputValue, oldInputValue)) { + lastResult = parsedExpression(scope); + oldInputValue = newInputValue && getValueOf(newInputValue); + } + return lastResult; + }, listener, objectEquality); + } + + var oldInputValueOfValues = []; + for (var i = 0, ii = inputExpressions.length; i < ii; i++) { + oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails + } + + return scope.$watch(function expressionInputsWatch(scope) { + var changed = false; + + for (var i = 0, ii = inputExpressions.length; i < ii; i++) { + var newInputValue = inputExpressions[i](scope); + if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { + oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); + } + } + + if (changed) { + lastResult = parsedExpression(scope); + } + + return lastResult; + }, listener, objectEquality); + } + + function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { + var unwatch, lastValue; + return unwatch = scope.$watch(function oneTimeWatch(scope) { + return parsedExpression(scope); + }, function oneTimeListener(value, old, scope) { + lastValue = value; + if (isFunction(listener)) { + listener.apply(this, arguments); + } + if (isDefined(value)) { + scope.$$postDigest(function() { + if (isDefined(lastValue)) { + unwatch(); + } + }); + } + }, objectEquality); + } + + function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { + var unwatch, lastValue; + return unwatch = scope.$watch(function oneTimeWatch(scope) { + return parsedExpression(scope); + }, function oneTimeListener(value, old, scope) { + lastValue = value; + if (isFunction(listener)) { + listener.call(this, value, old, scope); + } + if (isAllDefined(value)) { + scope.$$postDigest(function() { + if (isAllDefined(lastValue)) unwatch(); + }); + } + }, objectEquality); + + function isAllDefined(value) { + var allDefined = true; + forEach(value, function(val) { + if (!isDefined(val)) allDefined = false; + }); + return allDefined; + } + } + + function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { + var unwatch; + return unwatch = scope.$watch(function constantWatch(scope) { + return parsedExpression(scope); + }, function constantListener(value, old, scope) { + if (isFunction(listener)) { + listener.apply(this, arguments); + } + unwatch(); + }, objectEquality); + } + + function addInterceptor(parsedExpression, interceptorFn) { + if (!interceptorFn) return parsedExpression; + var watchDelegate = parsedExpression.$$watchDelegate; + + var regularWatch = + watchDelegate !== oneTimeLiteralWatchDelegate && + watchDelegate !== oneTimeWatchDelegate; + + var fn = regularWatch ? function regularInterceptedExpression(scope, locals) { + var value = parsedExpression(scope, locals); + return interceptorFn(value, scope, locals); + } : function oneTimeInterceptedExpression(scope, locals) { + var value = parsedExpression(scope, locals); + var result = interceptorFn(value, scope, locals); + // we only return the interceptor's result if the + // initial value is defined (for bind-once) + return isDefined(value) ? result : value; + }; + + // Propagate $$watchDelegates other then inputsWatchDelegate + if (parsedExpression.$$watchDelegate && + parsedExpression.$$watchDelegate !== inputsWatchDelegate) { + fn.$$watchDelegate = parsedExpression.$$watchDelegate; + } else if (!interceptorFn.$stateful) { + // If there is an interceptor, but no watchDelegate then treat the interceptor like + // we treat filters - it is assumed to be a pure function unless flagged with $stateful + fn.$$watchDelegate = inputsWatchDelegate; + fn.inputs = [parsedExpression]; + } + + return fn; + } }]; } @@ -11436,6 +13015,49 @@ function $ParseProvider() { * This is an implementation of promises/deferred objects inspired by * [Kris Kowal's Q](https://github.com/kriskowal/q). * + * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred + * implementations, and the other which resembles ES6 promises to some degree. + * + * # $q constructor + * + * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` + * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, + * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). + * + * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are + * available yet. + * + * It can be used like so: + * + * ```js + * // for the purpose of this example let's assume that variables `$q` and `okToGreet` + * // are available in the current lexical scope (they could have been injected or passed in). + * + * function asyncGreet(name) { + * // perform some asynchronous operation, resolve or reject the promise when appropriate. + * return $q(function(resolve, reject) { + * setTimeout(function() { + * if (okToGreet(name)) { + * resolve('Hello, ' + name + '!'); + * } else { + * reject('Greeting ' + name + ' is not allowed.'); + * } + * }, 1000); + * }); + * } + * + * var promise = asyncGreet('Robin Hood'); + * promise.then(function(greeting) { + * alert('Success: ' + greeting); + * }, function(reason) { + * alert('Failed: ' + reason); + * }); + * ``` + * + * Note: progress/notify callbacks are not currently supported via the ES6-style interface. + * + * However, the more traditional CommonJS-style usage is still available, and documented below. + * * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an * interface for interacting with an object that represents the result of an action that is * performed asynchronously, and may or may not be finished at any given point in time. @@ -11444,7 +13066,7 @@ function $ParseProvider() { * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. * * ```js - * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` + * // for the purpose of this example let's assume that variables `$q` and `okToGreet` * // are available in the current lexical scope (they could have been injected or passed in). * * function asyncGreet(name) { @@ -11482,7 +13104,6 @@ function $ParseProvider() { * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the * section on serial or parallel joining of promises. * - * * # The Deferred API * * A new instance of deferred is constructed by calling `$q.defer()`. @@ -11523,25 +13144,17 @@ function $ParseProvider() { * * This method *returns a new promise* which is resolved or rejected via the return value of the * `successCallback`, `errorCallback`. It also notifies via the return value of the - * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback + * `notifyCallback` method. The promise cannot be resolved or rejected from the notifyCallback * method. * * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` * - * Because `catch` is a reserved word in JavaScript and reserved keywords are not supported as - * property names by ES3, you'll need to invoke the method like `promise['catch'](callback)` or - * `promise.then(null, errorCallback)` to make your code IE8 and Android 2.x compatible. - * - * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, + * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, * but to do so without modifying the final value. This is useful to release resources or do some * clean-up that needs to be done whether the promise was rejected or resolved. See the [full * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for * more information. * - * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as - * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to - * make your code IE8 and Android 2.x compatible. - * * # Chaining promises * * Because calling the `then` method of a promise returns a new derived promise, it is easily @@ -11595,6 +13208,12 @@ function $ParseProvider() { * expect(resolvedValue).toEqual(123); * })); * ``` + * + * @param {function(function, function)} resolver Function which is responsible for resolving or + * rejecting the newly created promise. The first parameter is a function which resolves the + * promise, the second parameter is a function which rejects the promise. + * + * @returns {Promise} The newly created promise. */ function $QProvider() { @@ -11605,20 +13224,40 @@ function $QProvider() { }]; } +function $$QProvider() { + this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { + return qFactory(function(callback) { + $browser.defer(callback); + }, $exceptionHandler); + }]; +} /** * Constructs a promise manager. * - * @param {function(Function)} nextTick Function for executing functions in the next turn. + * @param {function(function)} nextTick Function for executing functions in the next turn. * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for * debugging purposes. * @returns {object} Promise manager. */ function qFactory(nextTick, exceptionHandler) { + var $qMinErr = minErr('$q', TypeError); + function callOnce(self, resolveFn, rejectFn) { + var called = false; + function wrap(fn) { + return function(value) { + if (called) return; + called = true; + fn.call(self, value); + }; + } + + return [wrap(resolveFn), wrap(rejectFn)]; + } /** * @ngdoc method - * @name $q#defer + * @name ng.$q#defer * @kind function * * @description @@ -11627,152 +13266,147 @@ function qFactory(nextTick, exceptionHandler) { * @returns {Deferred} Returns a new instance of deferred. */ var defer = function() { - var pending = [], - value, deferred; - - deferred = { - - resolve: function(val) { - if (pending) { - var callbacks = pending; - pending = undefined; - value = ref(val); - - if (callbacks.length) { - nextTick(function() { - var callback; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - callback = callbacks[i]; - value.then(callback[0], callback[1], callback[2]); - } - }); - } - } - }, - - - reject: function(reason) { - deferred.resolve(createInternalRejectedPromise(reason)); - }, - - - notify: function(progress) { - if (pending) { - var callbacks = pending; - - if (pending.length) { - nextTick(function() { - var callback; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - callback = callbacks[i]; - callback[2](progress); - } - }); - } - } - }, - - - promise: { - then: function(callback, errback, progressback) { - var result = defer(); - - var wrappedCallback = function(value) { - try { - result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); - } catch(e) { - result.reject(e); - exceptionHandler(e); - } - }; - - var wrappedErrback = function(reason) { - try { - result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); - } catch(e) { - result.reject(e); - exceptionHandler(e); - } - }; - - var wrappedProgressback = function(progress) { - try { - result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); - } catch(e) { - exceptionHandler(e); - } - }; - - if (pending) { - pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); - } else { - value.then(wrappedCallback, wrappedErrback, wrappedProgressback); - } - - return result.promise; - }, - - "catch": function(callback) { - return this.then(null, callback); - }, - - "finally": function(callback) { - - function makePromise(value, resolved) { - var result = defer(); - if (resolved) { - result.resolve(value); - } else { - result.reject(value); - } - return result.promise; - } - - function handleCallback(value, isResolved) { - var callbackOutput = null; - try { - callbackOutput = (callback ||defaultCallback)(); - } catch(e) { - return makePromise(e, false); - } - if (isPromiseLike(callbackOutput)) { - return callbackOutput.then(function() { - return makePromise(value, isResolved); - }, function(error) { - return makePromise(error, false); - }); - } else { - return makePromise(value, isResolved); - } - } - - return this.then(function(value) { - return handleCallback(value, true); - }, function(error) { - return handleCallback(error, false); - }); - } - } - }; - - return deferred; + return new Deferred(); }; + function Promise() { + this.$$state = { status: 0 }; + } - var ref = function(value) { - if (isPromiseLike(value)) return value; - return { - then: function(callback) { - var result = defer(); + Promise.prototype = { + then: function(onFulfilled, onRejected, progressBack) { + var result = new Deferred(); + + this.$$state.pending = this.$$state.pending || []; + this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); + if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); + + return result.promise; + }, + + "catch": function(callback) { + return this.then(null, callback); + }, + + "finally": function(callback, progressBack) { + return this.then(function(value) { + return handleCallback(value, true, callback); + }, function(error) { + return handleCallback(error, false, callback); + }, progressBack); + } + }; + + //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native + function simpleBind(context, fn) { + return function(value) { + fn.call(context, value); + }; + } + + function processQueue(state) { + var fn, promise, pending; + + pending = state.pending; + state.processScheduled = false; + state.pending = undefined; + for (var i = 0, ii = pending.length; i < ii; ++i) { + promise = pending[i][0]; + fn = pending[i][state.status]; + try { + if (isFunction(fn)) { + promise.resolve(fn(state.value)); + } else if (state.status === 1) { + promise.resolve(state.value); + } else { + promise.reject(state.value); + } + } catch (e) { + promise.reject(e); + exceptionHandler(e); + } + } + } + + function scheduleProcessQueue(state) { + if (state.processScheduled || !state.pending) return; + state.processScheduled = true; + nextTick(function() { processQueue(state); }); + } + + function Deferred() { + this.promise = new Promise(); + //Necessary to support unbound execution :/ + this.resolve = simpleBind(this, this.resolve); + this.reject = simpleBind(this, this.reject); + this.notify = simpleBind(this, this.notify); + } + + Deferred.prototype = { + resolve: function(val) { + if (this.promise.$$state.status) return; + if (val === this.promise) { + this.$$reject($qMinErr( + 'qcycle', + "Expected promise to be resolved with value other than itself '{0}'", + val)); + } else { + this.$$resolve(val); + } + + }, + + $$resolve: function(val) { + var then, fns; + + fns = callOnce(this, this.$$resolve, this.$$reject); + try { + if ((isObject(val) || isFunction(val))) then = val && val.then; + if (isFunction(then)) { + this.promise.$$state.status = -1; + then.call(val, fns[0], fns[1], this.notify); + } else { + this.promise.$$state.value = val; + this.promise.$$state.status = 1; + scheduleProcessQueue(this.promise.$$state); + } + } catch (e) { + fns[1](e); + exceptionHandler(e); + } + }, + + reject: function(reason) { + if (this.promise.$$state.status) return; + this.$$reject(reason); + }, + + $$reject: function(reason) { + this.promise.$$state.value = reason; + this.promise.$$state.status = 2; + scheduleProcessQueue(this.promise.$$state); + }, + + notify: function(progress) { + var callbacks = this.promise.$$state.pending; + + if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { nextTick(function() { - result.resolve(callback(value)); + var callback, result; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + result = callbacks[i][0]; + callback = callbacks[i][3]; + try { + result.notify(isFunction(callback) ? callback(progress) : progress); + } catch (e) { + exceptionHandler(e); + } + } }); - return result.promise; } - }; + } }; - /** * @ngdoc method * @name $q#reject @@ -11810,28 +13444,38 @@ function qFactory(nextTick, exceptionHandler) { * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. */ var reject = function(reason) { - var result = defer(); + var result = new Deferred(); result.reject(reason); return result.promise; }; - var createInternalRejectedPromise = function(reason) { - return { - then: function(callback, errback) { - var result = defer(); - nextTick(function() { - try { - result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); - } catch(e) { - result.reject(e); - exceptionHandler(e); - } - }); - return result.promise; - } - }; + var makePromise = function makePromise(value, resolved) { + var result = new Deferred(); + if (resolved) { + result.resolve(value); + } else { + result.reject(value); + } + return result.promise; }; + var handleCallback = function handleCallback(value, isResolved, callback) { + var callbackOutput = null; + try { + if (isFunction(callback)) callbackOutput = callback(); + } catch (e) { + return makePromise(e, false); + } + if (isPromiseLike(callbackOutput)) { + return callbackOutput.then(function() { + return makePromise(value, isResolved); + }, function(error) { + return makePromise(error, false); + }); + } else { + return makePromise(value, isResolved); + } + }; /** * @ngdoc method @@ -11846,65 +13490,14 @@ function qFactory(nextTick, exceptionHandler) { * @param {*} value Value or a promise * @returns {Promise} Returns a promise of the passed value or promise */ - var when = function(value, callback, errback, progressback) { - var result = defer(), - done; - var wrappedCallback = function(value) { - try { - return (isFunction(callback) ? callback : defaultCallback)(value); - } catch (e) { - exceptionHandler(e); - return reject(e); - } - }; - var wrappedErrback = function(reason) { - try { - return (isFunction(errback) ? errback : defaultErrback)(reason); - } catch (e) { - exceptionHandler(e); - return reject(e); - } - }; - - var wrappedProgressback = function(progress) { - try { - return (isFunction(progressback) ? progressback : defaultCallback)(progress); - } catch (e) { - exceptionHandler(e); - } - }; - - nextTick(function() { - ref(value).then(function(value) { - if (done) return; - done = true; - result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); - }, function(reason) { - if (done) return; - done = true; - result.resolve(wrappedErrback(reason)); - }, function(progress) { - if (done) return; - result.notify(wrappedProgressback(progress)); - }); - }); - - return result.promise; + var when = function(value, callback, errback, progressBack) { + var result = new Deferred(); + result.resolve(value); + return result.promise.then(callback, errback, progressBack); }; - - function defaultCallback(value) { - return value; - } - - - function defaultErrback(reason) { - return reject(reason); - } - - /** * @ngdoc method * @name $q#all @@ -11920,14 +13513,15 @@ function qFactory(nextTick, exceptionHandler) { * If any of the promises is resolved with a rejection, this resulting promise will be rejected * with the same rejection value. */ + function all(promises) { - var deferred = defer(), + var deferred = new Deferred(), counter = 0, results = isArray(promises) ? [] : {}; forEach(promises, function(promise, key) { counter++; - ref(promise).then(function(value) { + when(promise).then(function(value) { if (results.hasOwnProperty(key)) return; results[key] = value; if (!(--counter)) deferred.resolve(results); @@ -11944,27 +13538,50 @@ function qFactory(nextTick, exceptionHandler) { return deferred.promise; } - return { - defer: defer, - reject: reject, - when: when, - all: all + var $Q = function Q(resolver) { + if (!isFunction(resolver)) { + throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); + } + + if (!(this instanceof Q)) { + // More useful when $Q is the Promise itself. + return new Q(resolver); + } + + var deferred = new Deferred(); + + function resolveFn(value) { + deferred.resolve(value); + } + + function rejectFn(reason) { + deferred.reject(reason); + } + + resolver(resolveFn, rejectFn); + + return deferred.promise; }; + + $Q.defer = defer; + $Q.reject = reject; + $Q.when = when; + $Q.all = all; + + return $Q; } -function $$RAFProvider(){ //rAF +function $$RAFProvider() { //rAF this.$get = ['$window', '$timeout', function($window, $timeout) { var requestAnimationFrame = $window.requestAnimationFrame || - $window.webkitRequestAnimationFrame || - $window.mozRequestAnimationFrame; + $window.webkitRequestAnimationFrame; var cancelAnimationFrame = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || - $window.mozCancelAnimationFrame || $window.webkitCancelRequestAnimationFrame; var rafSupported = !!requestAnimationFrame; - var raf = rafSupported + var rafFn = rafSupported ? function(fn) { var id = requestAnimationFrame(fn); return function() { @@ -11978,9 +13595,47 @@ function $$RAFProvider(){ //rAF }; }; - raf.supported = rafSupported; + queueFn.supported = rafSupported; - return raf; + var cancelLastRAF; + var taskCount = 0; + var taskQueue = []; + return queueFn; + + function flush() { + for (var i = 0; i < taskQueue.length; i++) { + var task = taskQueue[i]; + if (task) { + taskQueue[i] = null; + task(); + } + } + taskCount = taskQueue.length = 0; + } + + function queueFn(asyncFn) { + var index = taskQueue.length; + + taskCount++; + taskQueue.push(asyncFn); + + if (index === 0) { + cancelLastRAF = rafFn(flush); + } + + return function cancelQueueFn() { + if (index >= 0) { + taskQueue[index] = null; + index = null; + + if (--taskCount === 0 && cancelLastRAF) { + cancelLastRAF(); + cancelLastRAF = null; + taskQueue.length = 0; + } + } + }; + } }]; } @@ -12051,10 +13706,11 @@ function $$RAFProvider(){ //rAF * They also provide an event emission/broadcast and subscription facility. See the * {@link guide/scope developer guide on scopes}. */ -function $RootScopeProvider(){ +function $RootScopeProvider() { var TTL = 10; var $rootScopeMinErr = minErr('$rootScope'); var lastDirtyWatch = null; + var applyAsyncId = null; this.digestTtl = function(value) { if (arguments.length) { @@ -12063,8 +13719,25 @@ function $RootScopeProvider(){ return TTL; }; + function createChildScopeClass(parent) { + function ChildScope() { + this.$$watchers = this.$$nextSibling = + this.$$childHead = this.$$childTail = null; + this.$$listeners = {}; + this.$$listenerCount = {}; + this.$id = nextUid(); + this.$$ChildScope = null; + } + ChildScope.prototype = parent; + return ChildScope; + } + this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', - function( $injector, $exceptionHandler, $parse, $browser) { + function($injector, $exceptionHandler, $parse, $browser) { + + function destroyChildScope($event) { + $event.currentScope.$$destroyed = true; + } /** * @ngdoc type @@ -12074,12 +13747,9 @@ function $RootScopeProvider(){ * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the * {@link auto.$injector $injector}. Child scopes are created using the * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when - * compiled HTML template is executed.) + * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for + * an in-depth introduction and usage examples. * - * Here is a simple scope snippet to show how you can interact with the scope. - * ```html - * - * ``` * * # Inheritance * A scope can inherit from a parent scope, as in this example: @@ -12088,7 +13758,6 @@ function $RootScopeProvider(){ var child = parent.$new(); parent.salutation = "Hello"; - child.name = "World"; expect(child.salutation).toEqual('Hello'); child.salutation = "Welcome"; @@ -12096,6 +13765,10 @@ function $RootScopeProvider(){ expect(parent.salutation).toEqual('Hello'); * ``` * + * When interacting with `Scope` in tests, additional helper methods are available on the + * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional + * details. + * * * @param {Object.=} providers Map of service factory which need to be * provided for the current scope. Defaults to {@link ng}. @@ -12111,13 +13784,11 @@ function $RootScopeProvider(){ this.$$phase = this.$parent = this.$$watchers = this.$$nextSibling = this.$$prevSibling = this.$$childHead = this.$$childTail = null; - this['this'] = this.$root = this; + this.$root = this; this.$$destroyed = false; - this.$$asyncQueue = []; - this.$$postDigestQueue = []; this.$$listeners = {}; this.$$listenerCount = {}; - this.$$isolateBindings = {}; + this.$$isolateBindings = null; } /** @@ -12166,44 +13837,47 @@ function $RootScopeProvider(){ * When creating widgets, it is useful for the widget to not accidentally read parent * state. * + * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` + * of the newly created scope. Defaults to `this` scope if not provided. + * This is used when creating a transclude scope to correctly place it + * in the scope hierarchy while maintaining the correct prototypical + * inheritance. + * * @returns {Object} The newly created child scope. * */ - $new: function(isolate) { - var ChildScope, - child; + $new: function(isolate, parent) { + var child; + + parent = parent || this; if (isolate) { child = new Scope(); child.$root = this.$root; - // ensure that there is just one async queue per $rootScope and its children - child.$$asyncQueue = this.$$asyncQueue; - child.$$postDigestQueue = this.$$postDigestQueue; } else { // Only create a child scope class if somebody asks for one, // but cache it to allow the VM to optimize lookups. - if (!this.$$childScopeClass) { - this.$$childScopeClass = function() { - this.$$watchers = this.$$nextSibling = - this.$$childHead = this.$$childTail = null; - this.$$listeners = {}; - this.$$listenerCount = {}; - this.$id = nextUid(); - this.$$childScopeClass = null; - }; - this.$$childScopeClass.prototype = this; + if (!this.$$ChildScope) { + this.$$ChildScope = createChildScopeClass(this); } - child = new this.$$childScopeClass(); + child = new this.$$ChildScope(); } - child['this'] = child; - child.$parent = this; - child.$$prevSibling = this.$$childTail; - if (this.$$childHead) { - this.$$childTail.$$nextSibling = child; - this.$$childTail = child; + child.$parent = parent; + child.$$prevSibling = parent.$$childTail; + if (parent.$$childHead) { + parent.$$childTail.$$nextSibling = child; + parent.$$childTail = child; } else { - this.$$childHead = this.$$childTail = child; + parent.$$childHead = parent.$$childTail = child; } + + // When the new scope is not isolated or we inherit from `this`, and + // the parent scope is destroyed, the property `$$destroyed` is inherited + // prototypically. In all other cases, this property needs to be set + // when the parent scope is destroyed. + // The listener needs to be added after the parent is set + if (isolate || parent != this) child.$on('$destroy', destroyChildScope); + return child; }, @@ -12236,9 +13910,9 @@ function $RootScopeProvider(){ * * * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, - * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` - * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a - * change is detected, be prepared for multiple calls to your listener.) + * you can register a `watchExpression` function with no `listener`. (Be prepared for + * multiple calls to your `watchExpression` because it will execute multiple times in a + * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) * * After a watcher is registered with the scope, the `listener` fn is called asynchronously * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the @@ -12247,7 +13921,6 @@ function $RootScopeProvider(){ * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the * listener was called due to initialization. * - * The example below contains an illustration of using a function as your $watch listener * * * # Example @@ -12277,14 +13950,14 @@ function $RootScopeProvider(){ - // Using a listener function + // Using a function as a watchExpression var food; scope.foodCounter = 0; expect(scope.foodCounter).toEqual(0); scope.$watch( - // This is the listener function + // This function returns the value being watched. It is called for each turn of the $digest loop function() { return food; }, - // This is the change handler + // This is the change listener, called when the value returned from the above function changes function(newValue, oldValue) { if ( newValue !== oldValue ) { // Only increment the counter if the value changed @@ -12314,20 +13987,23 @@ function $RootScopeProvider(){ * * - `string`: Evaluated as {@link guide/expression expression} * - `function(scope)`: called with current `scope` as a parameter. - * @param {(function()|string)=} listener Callback called whenever the return value of - * the `watchExpression` changes. - * - * - `string`: Evaluated as {@link guide/expression expression} - * - `function(newValue, oldValue, scope)`: called with current and previous values as - * parameters. + * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value + * of `watchExpression` changes. * + * - `newVal` contains the current value of the `watchExpression` + * - `oldVal` contains the previous value of the `watchExpression` + * - `scope` refers to the current scope * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of * comparing for reference equality. * @returns {function()} Returns a deregistration function for this listener. */ $watch: function(watchExp, listener, objectEquality) { + var get = $parse(watchExp); + + if (get.$$watchDelegate) { + return get.$$watchDelegate(this, listener, objectEquality, get); + } var scope = this, - get = compileToFn(watchExp, 'watch'), array = scope.$$watchers, watcher = { fn: listener, @@ -12339,18 +14015,8 @@ function $RootScopeProvider(){ lastDirtyWatch = null; - // in the case user pass string, we need to compile it, do we really need this ? if (!isFunction(listener)) { - var listenFn = compileToFn(listener || noop, 'listener'); - watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; - } - - if (typeof watchExp == 'string' && get.constant) { - var originalFn = watcher.fn; - watcher.fn = function(newVal, oldVal, scope) { - originalFn.call(this, newVal, oldVal, scope); - arrayRemove(array, watcher); - }; + watcher.fn = noop; } if (!array) { @@ -12366,6 +14032,89 @@ function $RootScopeProvider(){ }; }, + /** + * @ngdoc method + * @name $rootScope.Scope#$watchGroup + * @kind function + * + * @description + * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. + * If any one expression in the collection changes the `listener` is executed. + * + * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every + * call to $digest() to see if any items changes. + * - The `listener` is called whenever any expression in the `watchExpressions` array changes. + * + * @param {Array.} watchExpressions Array of expressions that will be individually + * watched using {@link ng.$rootScope.Scope#$watch $watch()} + * + * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any + * expression in `watchExpressions` changes + * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching + * those of `watchExpression` + * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching + * those of `watchExpression` + * The `scope` refers to the current scope. + * @returns {function()} Returns a de-registration function for all listeners. + */ + $watchGroup: function(watchExpressions, listener) { + var oldValues = new Array(watchExpressions.length); + var newValues = new Array(watchExpressions.length); + var deregisterFns = []; + var self = this; + var changeReactionScheduled = false; + var firstRun = true; + + if (!watchExpressions.length) { + // No expressions means we call the listener ASAP + var shouldCall = true; + self.$evalAsync(function() { + if (shouldCall) listener(newValues, newValues, self); + }); + return function deregisterWatchGroup() { + shouldCall = false; + }; + } + + if (watchExpressions.length === 1) { + // Special case size of one + return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { + newValues[0] = value; + oldValues[0] = oldValue; + listener(newValues, (value === oldValue) ? newValues : oldValues, scope); + }); + } + + forEach(watchExpressions, function(expr, i) { + var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { + newValues[i] = value; + oldValues[i] = oldValue; + if (!changeReactionScheduled) { + changeReactionScheduled = true; + self.$evalAsync(watchGroupAction); + } + }); + deregisterFns.push(unwatchFn); + }); + + function watchGroupAction() { + changeReactionScheduled = false; + + if (firstRun) { + firstRun = false; + listener(newValues, newValues, self); + } else { + listener(newValues, oldValues, self); + } + } + + return function deregisterWatchGroup() { + while (deregisterFns.length) { + deregisterFns.shift()(); + } + }; + }, + /** * @ngdoc method @@ -12423,6 +14172,8 @@ function $RootScopeProvider(){ * de-registration function is executed, the internal watch operation is terminated. */ $watchCollection: function(obj, listener) { + $watchCollectionInterceptor.$stateful = true; + var self = this; // the current value, updated on each dirty-check run var newValue; @@ -12434,15 +14185,18 @@ function $RootScopeProvider(){ // only track veryOldValue if the listener is asking for it var trackVeryOldValue = (listener.length > 1); var changeDetected = 0; - var objGetter = $parse(obj); + var changeDetector = $parse(obj, $watchCollectionInterceptor); var internalArray = []; var internalObject = {}; var initRun = true; var oldLength = 0; - function $watchCollectionWatch() { - newValue = objGetter(self); - var newLength, key, bothNaN; + function $watchCollectionInterceptor(_value) { + newValue = _value; + var newLength, key, bothNaN, newItem, oldItem; + + // If the new value is undefined, then return undefined as the watch may be a one-time watch + if (isUndefined(newValue)) return; if (!isObject(newValue)) { // if primitive if (oldValue !== newValue) { @@ -12466,11 +14220,13 @@ function $RootScopeProvider(){ } // copy the items to oldValue and look for changes. for (var i = 0; i < newLength; i++) { - bothNaN = (oldValue[i] !== oldValue[i]) && - (newValue[i] !== newValue[i]); - if (!bothNaN && (oldValue[i] !== newValue[i])) { + oldItem = oldValue[i]; + newItem = newValue[i]; + + bothNaN = (oldItem !== oldItem) && (newItem !== newItem); + if (!bothNaN && (oldItem !== newItem)) { changeDetected++; - oldValue[i] = newValue[i]; + oldValue[i] = newItem; } } } else { @@ -12485,16 +14241,18 @@ function $RootScopeProvider(){ for (key in newValue) { if (newValue.hasOwnProperty(key)) { newLength++; - if (oldValue.hasOwnProperty(key)) { - bothNaN = (oldValue[key] !== oldValue[key]) && - (newValue[key] !== newValue[key]); - if (!bothNaN && (oldValue[key] !== newValue[key])) { + newItem = newValue[key]; + oldItem = oldValue[key]; + + if (key in oldValue) { + bothNaN = (oldItem !== oldItem) && (newItem !== newItem); + if (!bothNaN && (oldItem !== newItem)) { changeDetected++; - oldValue[key] = newValue[key]; + oldValue[key] = newItem; } } else { oldLength++; - oldValue[key] = newValue[key]; + oldValue[key] = newItem; changeDetected++; } } @@ -12502,8 +14260,8 @@ function $RootScopeProvider(){ if (oldLength > newLength) { // we used to have more keys, need to find them and destroy them. changeDetected++; - for(key in oldValue) { - if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { + for (key in oldValue) { + if (!newValue.hasOwnProperty(key)) { oldLength--; delete oldValue[key]; } @@ -12542,7 +14300,7 @@ function $RootScopeProvider(){ } } - return this.$watch($watchCollectionWatch, $watchCollectionAction); + return this.$watch(changeDetector, $watchCollectionAction); }, /** @@ -12562,7 +14320,7 @@ function $RootScopeProvider(){ * {@link ng.directive:ngController controllers} or in * {@link ng.$compileProvider#directive directives}. * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within - * a {@link ng.$compileProvider#directive directives}), which will force a `$digest()`. + * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. * * If you want to be notified whenever `$digest()` is called, * you can register a `watchExpression` function with @@ -12599,8 +14357,6 @@ function $RootScopeProvider(){ $digest: function() { var watch, value, last, watchers, - asyncQueue = this.$$asyncQueue, - postDigestQueue = this.$$postDigestQueue, length, dirty, ttl = TTL, next, current, target = this, @@ -12611,18 +14367,24 @@ function $RootScopeProvider(){ // Check for changes to browser url that happened in sync before the call to $digest $browser.$$checkUrlChange(); + if (this === $rootScope && applyAsyncId !== null) { + // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then + // cancel the scheduled $apply and flush the queue of expressions to be evaluated. + $browser.defer.cancel(applyAsyncId); + flushApplyAsync(); + } + lastDirtyWatch = null; do { // "while dirty" loop dirty = false; current = target; - while(asyncQueue.length) { + while (asyncQueue.length) { try { asyncTask = asyncQueue.shift(); - asyncTask.scope.$eval(asyncTask.expression); + asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); } catch (e) { - clearPhase(); $exceptionHandler(e); } lastDirtyWatch = null; @@ -12651,11 +14413,11 @@ function $RootScopeProvider(){ if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; - logMsg = (isFunction(watch.exp)) - ? 'fn: ' + (watch.exp.name || watch.exp.toString()) - : watch.exp; - logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); - watchLog[logIdx].push(logMsg); + watchLog[logIdx].push({ + msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, + newVal: value, + oldVal: last + }); } } else if (watch === lastDirtyWatch) { // If the most recently dirty watcher is now clean, short circuit since the remaining watchers @@ -12665,7 +14427,6 @@ function $RootScopeProvider(){ } } } catch (e) { - clearPhase(); $exceptionHandler(e); } } @@ -12676,7 +14437,7 @@ function $RootScopeProvider(){ // this piece should be kept in sync with the traversal in $broadcast if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { - while(current !== target && !(next = current.$$nextSibling)) { + while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } @@ -12684,19 +14445,19 @@ function $RootScopeProvider(){ // `break traverseScopesLoop;` takes us to here - if((dirty || asyncQueue.length) && !(ttl--)) { + if ((dirty || asyncQueue.length) && !(ttl--)) { clearPhase(); throw $rootScopeMinErr('infdig', '{0} $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: {1}', - TTL, toJson(watchLog)); + TTL, watchLog); } } while (dirty || asyncQueue.length); clearPhase(); - while(postDigestQueue.length) { + while (postDigestQueue.length) { try { postDigestQueue.shift()(); } catch (e) { @@ -12749,7 +14510,9 @@ function $RootScopeProvider(){ this.$$destroyed = true; if (this === $rootScope) return; - forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); + for (var eventName in this.$$listenerCount) { + decrementListenerCount(this, this.$$listenerCount[eventName], eventName); + } // sever all the references to parent scopes (after this cleanup, the current scope should // not be retained by any of our references and should be eligible for garbage collection) @@ -12758,6 +14521,10 @@ function $RootScopeProvider(){ if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; + // Disable listeners, watchers and apply/digest methods + this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; + this.$on = this.$watch = this.$watchGroup = function() { return noop; }; + this.$$listeners = {}; // All of the code below is bogus code that works around V8's memory leak via optimized code // and inline caches. @@ -12768,15 +14535,7 @@ function $RootScopeProvider(){ // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = - this.$$childTail = this.$root = null; - - // don't reset these to null in case some async task tries to register a listener/watch/task - this.$$listeners = {}; - this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; - - // prevent NPEs since these methods have references to properties we nulled out - this.$destroy = this.$digest = this.$apply = noop; - this.$on = this.$watch = function() { return noop; }; + this.$$childTail = this.$root = this.$$watchers = null; }, /** @@ -12839,23 +14598,24 @@ function $RootScopeProvider(){ * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * + * @param {(object)=} locals Local variables object, useful for overriding values in scope. */ - $evalAsync: function(expr) { + $evalAsync: function(expr, locals) { // if we are outside of an $digest loop and this is the first time we are scheduling async // task also schedule async auto-flush - if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { + if (!$rootScope.$$phase && !asyncQueue.length) { $browser.defer(function() { - if ($rootScope.$$asyncQueue.length) { + if (asyncQueue.length) { $rootScope.$digest(); } }); } - this.$$asyncQueue.push({scope: this, expression: expr}); + asyncQueue.push({scope: this, expression: expr, locals: locals}); }, - $$postDigest : function(fn) { - this.$$postDigestQueue.push(fn); + $$postDigest: function(fn) { + postDigestQueue.push(fn); }, /** @@ -12920,6 +14680,33 @@ function $RootScopeProvider(){ } }, + /** + * @ngdoc method + * @name $rootScope.Scope#$applyAsync + * @kind function + * + * @description + * Schedule the invocation of $apply to occur at a later time. The actual time difference + * varies across browsers, but is typically around ~10 milliseconds. + * + * This can be used to queue up multiple expressions which need to be evaluated in the same + * digest. + * + * @param {(string|function())=} exp An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with current `scope` parameter. + */ + $applyAsync: function(expr) { + var scope = this; + expr && applyAsyncQueue.push($applyAsyncExpression); + scheduleApplyAsync(); + + function $applyAsyncExpression() { + scope.$eval(expr); + } + }, + /** * @ngdoc method * @name $rootScope.Scope#$on @@ -12934,7 +14721,8 @@ function $RootScopeProvider(){ * * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or * `$broadcast`-ed. - * - `currentScope` - `{Scope}`: the current scope which is handling the event. + * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the + * event propagates through the scope hierarchy, this property is set to null. * - `name` - `{string}`: name of the event. * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel * further event propagation (available only for events that were `$emit`-ed). @@ -12963,7 +14751,7 @@ function $RootScopeProvider(){ var self = this; return function() { - var indexOfListener = indexOf(namedListeners, listener); + var indexOfListener = namedListeners.indexOf(listener); if (indexOfListener !== -1) { namedListeners[indexOfListener] = null; decrementListenerCount(self, 1, name); @@ -13014,7 +14802,7 @@ function $RootScopeProvider(){ do { namedListeners = scope.$$listeners[name] || empty; event.currentScope = scope; - for (i=0, length=namedListeners.length; i= 8 ) { - normalizedVal = urlResolve(uri).href; - if (normalizedVal !== '' && !normalizedVal.match(regex)) { - return 'unsafe:'+normalizedVal; - } + normalizedVal = urlResolve(uri).href; + if (normalizedVal !== '' && !normalizedVal.match(regex)) { + return 'unsafe:' + normalizedVal; } return uri; }; }; } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + var $sceMinErr = minErr('$sce'); var SCE_CONTEXTS = { @@ -13240,15 +15064,6 @@ var SCE_CONTEXTS = { // Helper functions follow. -// Copied from: -// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 -// Prereq: s is a string. -function escapeForRegexp(s) { - return s.replace(/([-()\[\]{}+?*.$\^|,:# to learn more about them. * You can ensure your document is in standards mode and not quirks mode by adding `` @@ -13683,7 +15498,7 @@ function $SceDelegateProvider() { * * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link - * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the + * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. * * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link @@ -13720,7 +15535,7 @@ function $SceDelegateProvider() { * won't work on all browsers. Also, loading templates from `file://` URL does not work on some * browsers. * - * ## This feels like too much overhead for the developer? + * ## This feels like too much overhead * * It's important to remember that SCE only applies to interpolation expressions. * @@ -13769,7 +15584,7 @@ function $SceDelegateProvider() { * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use * in a whitelist. * - `**`: matches zero or more occurrences of *any* character. As such, it's not - * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. + * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might * not have been the intention.) Its usage at the very end of the path is ok. (e.g. * http://foo.example.com/templates/**). @@ -13777,11 +15592,11 @@ function $SceDelegateProvider() { * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to * accidentally introduce a bug when one updates a complex expression (imho, all regexes should - * have good test coverage.). For instance, the use of `.` in the regex is correct only in a + * have good test coverage). For instance, the use of `.` in the regex is correct only in a * small number of cases. A `.` character in the regex used when matching the scheme or a * subdomain could be matched against a `:` or literal `.` that was likely not intended. It * is highly recommended to use the string patterns and only fall back to regular expressions - * if they as a last resort. + * as a last resort. * - The regular expression must be an instance of RegExp (i.e. not a string.) It is * matched against the **entire** *normalized / absolute URL* of the resource being tested * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags @@ -13791,7 +15606,7 @@ function $SceDelegateProvider() { * remember to escape your regular expression (and be aware that you might need more than * one level of escaping depending on your templating engine and the way you interpolated * the value.) Do make use of your platform's escaping mechanism as it might be good - * enough before coding your own. e.g. Ruby has + * enough before coding your own. E.g. Ruby has * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). * Javascript lacks a similar built in function for escaping. Take a look at Google @@ -13804,7 +15619,7 @@ function $SceDelegateProvider() { * * * - *
    + *
    *

    * User comments
    * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when @@ -13821,17 +15636,17 @@ function $SceDelegateProvider() { * * * - * var mySceApp = angular.module('mySceApp', ['ngSanitize']); - * - * mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { - * var self = this; - * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { - * self.userComments = userComments; - * }); - * self.explicitlyTrustedHtml = $sce.trustAsHtml( - * 'Hover over this text.'); - * }); + * angular.module('mySceApp', ['ngSanitize']) + * .controller('AppController', ['$http', '$templateCache', '$sce', + * function($http, $templateCache, $sce) { + * var self = this; + * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { + * self.userComments = userComments; + * }); + * self.explicitlyTrustedHtml = $sce.trustAsHtml( + * 'Hover over this text.'); + * }]); * * * @@ -13899,7 +15714,7 @@ function $SceProvider() { * @description * Enables/disables SCE and returns the current value. */ - this.enabled = function (value) { + this.enabled = function(value) { if (arguments.length) { enabled = !!value; } @@ -13953,13 +15768,13 @@ function $SceProvider() { * sce.js and sceSpecs.js would need to be aware of this detail. */ - this.$get = ['$parse', '$sniffer', '$sceDelegate', function( - $parse, $sniffer, $sceDelegate) { - // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows + this.$get = ['$parse', '$sceDelegate', function( + $parse, $sceDelegate) { + // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow // the "expression(javascript expression)" syntax which is insecure. - if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { + if (enabled && msie < 8) { throw $sceMinErr('iequirks', - 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + + 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 'mode. You can fix this by adding the text to the top of your HTML ' + 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); } @@ -13977,7 +15792,7 @@ function $SceProvider() { * @description * Returns a boolean indicating if SCE is enabled. */ - sce.isEnabled = function () { + sce.isEnabled = function() { return enabled; }; sce.trustAs = $sceDelegate.trustAs; @@ -14013,9 +15828,9 @@ function $SceProvider() { if (parsed.literal && parsed.constant) { return parsed; } else { - return function sceParseAsTrusted(self, locals) { - return sce.getTrusted(type, parsed(self, locals)); - }; + return $parse(expr, function(value) { + return sce.getTrusted(type, value); + }); } }; @@ -14182,7 +15997,7 @@ function $SceProvider() { * * @description * Shorthand method. `$sce.parseAsHtml(expression string)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} + * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: @@ -14199,7 +16014,7 @@ function $SceProvider() { * * @description * Shorthand method. `$sce.parseAsCss(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} + * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: @@ -14216,7 +16031,7 @@ function $SceProvider() { * * @description * Shorthand method. `$sce.parseAsUrl(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} + * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: @@ -14233,7 +16048,7 @@ function $SceProvider() { * * @description * Shorthand method. `$sce.parseAsResourceUrl(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} + * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: @@ -14250,7 +16065,7 @@ function $SceProvider() { * * @description * Shorthand method. `$sce.parseAsJs(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} + * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: @@ -14266,15 +16081,15 @@ function $SceProvider() { getTrusted = sce.getTrusted, trustAs = sce.trustAs; - forEach(SCE_CONTEXTS, function (enumValue, name) { + forEach(SCE_CONTEXTS, function(enumValue, name) { var lName = lowercase(name); - sce[camelCase("parse_as_" + lName)] = function (expr) { + sce[camelCase("parse_as_" + lName)] = function(expr) { return parse(enumValue, expr); }; - sce[camelCase("get_trusted_" + lName)] = function (value) { + sce[camelCase("get_trusted_" + lName)] = function(value) { return getTrusted(enumValue, value); }; - sce[camelCase("trust_as_" + lName)] = function (value) { + sce[camelCase("trust_as_" + lName)] = function(value) { return trustAs(enumValue, value); }; }); @@ -14291,7 +16106,6 @@ function $SceProvider() { * @requires $document * * @property {boolean} history Does the browser support html5 history api ? - * @property {boolean} hashchange Does the browser support hashchange event ? * @property {boolean} transitions Does the browser support CSS transition events ? * @property {boolean} animations Does the browser support CSS animation events ? * @@ -14305,31 +16119,30 @@ function $SnifferProvider() { int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), boxee = /Boxee/i.test(($window.navigator || {}).userAgent), document = $document[0] || {}, - documentMode = document.documentMode, vendorPrefix, - vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, + vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, bodyStyle = document.body && document.body.style, transitions = false, animations = false, match; if (bodyStyle) { - for(var prop in bodyStyle) { - if(match = vendorRegex.exec(prop)) { + for (var prop in bodyStyle) { + if (match = vendorRegex.exec(prop)) { vendorPrefix = match[0]; vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); break; } } - if(!vendorPrefix) { + if (!vendorPrefix) { vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; } transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); - if (android && (!transitions||!animations)) { + if (android && (!transitions || !animations)) { transitions = isString(document.body.style.webkitTransition); animations = isString(document.body.style.webkitAnimation); } @@ -14348,14 +16161,13 @@ function $SnifferProvider() { // jshint -W018 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), // jshint +W018 - hashchange: 'onhashchange' in $window && - // IE8 compatible mode lies - (!documentMode || documentMode > 7), hasEvent: function(event) { // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have // it. In particular the event is not fired when backspace or delete key are pressed or // when cut operation is performed. - if (event == 'input' && msie == 9) return false; + // IE10+ implements 'input' event but it erroneously fires under various situations, + // e.g. when placeholder changes, or a form is focused. + if (event === 'input' && msie <= 11) return false; if (isUndefined(eventSupport[event])) { var divElm = document.createElement('div'); @@ -14366,18 +16178,203 @@ function $SnifferProvider() { }, csp: csp(), vendorPrefix: vendorPrefix, - transitions : transitions, - animations : animations, - android: android, - msie : msie, - msieDocumentMode: documentMode + transitions: transitions, + animations: animations, + android: android }; }]; } +var $compileMinErr = minErr('$compile'); + +/** + * @ngdoc service + * @name $templateRequest + * + * @description + * The `$templateRequest` service runs security checks then downloads the provided template using + * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request + * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the + * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the + * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted + * when `tpl` is of type string and `$templateCache` has the matching entry. + * + * @param {string|TrustedResourceUrl} tpl The HTTP request template URL + * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty + * + * @return {Promise} the HTTP Promise for the given. + * + * @property {number} totalPendingRequests total amount of pending template requests being downloaded. + */ +function $TemplateRequestProvider() { + this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { + function handleRequestFn(tpl, ignoreRequestError) { + handleRequestFn.totalPendingRequests++; + + // We consider the template cache holds only trusted templates, so + // there's no need to go through whitelisting again for keys that already + // are included in there. This also makes Angular accept any script + // directive, no matter its name. However, we still need to unwrap trusted + // types. + if (!isString(tpl) || !$templateCache.get(tpl)) { + tpl = $sce.getTrustedResourceUrl(tpl); + } + + var transformResponse = $http.defaults && $http.defaults.transformResponse; + + if (isArray(transformResponse)) { + transformResponse = transformResponse.filter(function(transformer) { + return transformer !== defaultHttpResponseTransform; + }); + } else if (transformResponse === defaultHttpResponseTransform) { + transformResponse = null; + } + + var httpOptions = { + cache: $templateCache, + transformResponse: transformResponse + }; + + return $http.get(tpl, httpOptions) + ['finally'](function() { + handleRequestFn.totalPendingRequests--; + }) + .then(function(response) { + return response.data; + }, handleError); + + function handleError(resp) { + if (!ignoreRequestError) { + throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl); + } + return $q.reject(resp); + } + } + + handleRequestFn.totalPendingRequests = 0; + + return handleRequestFn; + }]; +} + +function $$TestabilityProvider() { + this.$get = ['$rootScope', '$browser', '$location', + function($rootScope, $browser, $location) { + + /** + * @name $testability + * + * @description + * The private $$testability service provides a collection of methods for use when debugging + * or by automated test and debugging tools. + */ + var testability = {}; + + /** + * @name $$testability#findBindings + * + * @description + * Returns an array of elements that are bound (via ng-bind or {{}}) + * to expressions matching the input. + * + * @param {Element} element The element root to search from. + * @param {string} expression The binding expression to match. + * @param {boolean} opt_exactMatch If true, only returns exact matches + * for the expression. Filters and whitespace are ignored. + */ + testability.findBindings = function(element, expression, opt_exactMatch) { + var bindings = element.getElementsByClassName('ng-binding'); + var matches = []; + forEach(bindings, function(binding) { + var dataBinding = angular.element(binding).data('$binding'); + if (dataBinding) { + forEach(dataBinding, function(bindingName) { + if (opt_exactMatch) { + var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); + if (matcher.test(bindingName)) { + matches.push(binding); + } + } else { + if (bindingName.indexOf(expression) != -1) { + matches.push(binding); + } + } + }); + } + }); + return matches; + }; + + /** + * @name $$testability#findModels + * + * @description + * Returns an array of elements that are two-way found via ng-model to + * expressions matching the input. + * + * @param {Element} element The element root to search from. + * @param {string} expression The model expression to match. + * @param {boolean} opt_exactMatch If true, only returns exact matches + * for the expression. + */ + testability.findModels = function(element, expression, opt_exactMatch) { + var prefixes = ['ng-', 'data-ng-', 'ng\\:']; + for (var p = 0; p < prefixes.length; ++p) { + var attributeEquals = opt_exactMatch ? '=' : '*='; + var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; + var elements = element.querySelectorAll(selector); + if (elements.length) { + return elements; + } + } + }; + + /** + * @name $$testability#getLocation + * + * @description + * Shortcut for getting the location in a browser agnostic way. Returns + * the path, search, and hash. (e.g. /path?a=b#hash) + */ + testability.getLocation = function() { + return $location.url(); + }; + + /** + * @name $$testability#setLocation + * + * @description + * Shortcut for navigating to a location without doing a full page reload. + * + * @param {string} url The location url (path, search and hash, + * e.g. /path?a=b#hash) to go to. + */ + testability.setLocation = function(url) { + if (url !== $location.url()) { + $location.url(url); + $rootScope.$digest(); + } + }; + + /** + * @name $$testability#whenStable + * + * @description + * Calls the callback when $timeout and $http requests are completed. + * + * @param {function} callback + */ + testability.whenStable = function(callback) { + $browser.notifyWhenNoOutstandingRequests(callback); + }; + + return testability; + }]; +} + function $TimeoutProvider() { - this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', - function($rootScope, $browser, $q, $exceptionHandler) { + this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', + function($rootScope, $browser, $q, $$q, $exceptionHandler) { var deferreds = {}; @@ -14407,15 +16404,15 @@ function $TimeoutProvider() { * */ function timeout(fn, delay, invokeApply) { - var deferred = $q.defer(), + var skipApply = (isDefined(invokeApply) && !invokeApply), + deferred = (skipApply ? $$q : $q).defer(), promise = deferred.promise, - skipApply = (isDefined(invokeApply) && !invokeApply), timeoutId; timeoutId = $browser.defer(function() { try { deferred.resolve(fn()); - } catch(e) { + } catch (e) { deferred.reject(e); $exceptionHandler(e); } @@ -14466,7 +16463,7 @@ function $TimeoutProvider() { // exactly the behavior needed here. There is little value is mocking these out for this // service. var urlParsingNode = document.createElement("a"); -var originUrl = urlResolve(window.location.href, true); +var originUrl = urlResolve(window.location.href); /** @@ -14483,20 +16480,13 @@ var originUrl = urlResolve(window.location.href, true); * * Implementation Notes for IE * --------------------------- - * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other + * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other * browsers. However, the parsed components will not be set if the URL assigned did not specify * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We * work around that by performing the parsing in a 2nd step by taking a previously normalized * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the * properties such as protocol, hostname, port, etc. * - * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one - * uses the inner HTML approach to assign the URL as part of an HTML snippet - - * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. - * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. - * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that - * method and IE < 8 is unsupported. - * * References: * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html @@ -14521,7 +16511,7 @@ var originUrl = urlResolve(window.location.href, true); * | pathname | The pathname, beginning with "/" * */ -function urlResolve(url, base) { +function urlResolve(url) { var href = url; if (msie) { @@ -14581,7 +16571,7 @@ function urlIsSameOrigin(requestUrl) {
    @@ -15502,19 +17602,25 @@ var uppercaseFilter = valueFn(uppercase);

    Output numbers: {{ numbers | limitTo:numLimit }}

    Limit {{letters}} to:

    Output letters: {{ letters | limitTo:letterLimit }}

    + Limit {{longNumber}} to: +

    Output long number: {{ longNumber | limitTo:longNumberLimit }}

    var numLimitInput = element(by.model('numLimit')); var letterLimitInput = element(by.model('letterLimit')); + var longNumberLimitInput = element(by.model('longNumberLimit')); var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); + var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); it('should limit the number array to first three items', function() { expect(numLimitInput.getAttribute('value')).toBe('3'); expect(letterLimitInput.getAttribute('value')).toBe('3'); + expect(longNumberLimitInput.getAttribute('value')).toBe('3'); expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); expect(limitedLetters.getText()).toEqual('Output letters: abc'); + expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); }); // There is a bug in safari and protractor that doesn't like the minus key @@ -15523,8 +17629,11 @@ var uppercaseFilter = valueFn(uppercase); // numLimitInput.sendKeys('-3'); // letterLimitInput.clear(); // letterLimitInput.sendKeys('-3'); + // longNumberLimitInput.clear(); + // longNumberLimitInput.sendKeys('-3'); // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); + // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); // }); it('should not exceed the maximum size of input array', function() { @@ -15532,14 +17641,18 @@ var uppercaseFilter = valueFn(uppercase); numLimitInput.sendKeys('100'); letterLimitInput.clear(); letterLimitInput.sendKeys('100'); + longNumberLimitInput.clear(); + longNumberLimitInput.sendKeys('100'); expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); + expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); }); - */ -function limitToFilter(){ +*/ +function limitToFilter() { return function(input, limit) { + if (isNumber(input)) input = input.toString(); if (!isArray(input) && !isString(input)) return input; if (Math.abs(Number(limit)) === Infinity) { @@ -15548,37 +17661,12 @@ function limitToFilter(){ limit = int(limit); } - if (isString(input)) { - //NaN check on limit - if (limit) { - return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); - } else { - return ""; - } - } - - var out = [], - i, n; - - // if abs(limit) exceeds maximum length, trim it - if (limit > input.length) - limit = input.length; - else if (limit < -input.length) - limit = -input.length; - - if (limit > 0) { - i = 0; - n = limit; + //NaN check on limit + if (limit) { + return limit > 0 ? input.slice(0, limit) : input.slice(limit); } else { - i = input.length + limit; - n = input.length; + return isString(input) ? "" : []; } - - for (; i` operator. + * `<`, `===`, `>` operator. * - `string`: An Angular expression. The result of this expression is used to compare elements * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by * 3 first characters of a property called `name`). The result of a constant expression @@ -15616,6 +17704,43 @@ function limitToFilter(){ * @param {boolean=} reverse Reverse the order of the array. * @returns {Array} Sorted copy of the source array. * + * + * @example + * The example below demonstrates a simple ngRepeat, where the data is sorted + * by age in descending order (predicate is set to `'-age'`). + * `reverse` is not set, which means it defaults to `false`. + + + +
    + + + + + + + + + + + +
    NamePhone NumberAge
    {{friend.name}}{{friend.phone}}{{friend.age}}
    +
    +
    +
    + * + * The predicate and reverse parameters can be controlled dynamically through scope properties, + * as shown in the next example. * @example @@ -15698,59 +17823,82 @@ function limitToFilter(){ */ orderByFilter.$inject = ['$parse']; -function orderByFilter($parse){ +function orderByFilter($parse) { return function(array, sortPredicate, reverseOrder) { if (!(isArrayLike(array))) return array; - sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; + sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate]; if (sortPredicate.length === 0) { sortPredicate = ['+']; } - sortPredicate = map(sortPredicate, function(predicate){ + sortPredicate = sortPredicate.map(function(predicate) { var descending = false, get = predicate || identity; if (isString(predicate)) { if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { descending = predicate.charAt(0) == '-'; predicate = predicate.substring(1); } - if ( predicate === '' ) { + if (predicate === '') { // Effectively no predicate was passed so we compare identity - return reverseComparator(function(a,b) { - return compare(a, b); - }, descending); + return reverseComparator(compare, descending); } get = $parse(predicate); if (get.constant) { var key = get(); - return reverseComparator(function(a,b) { + return reverseComparator(function(a, b) { return compare(a[key], b[key]); }, descending); } } - return reverseComparator(function(a,b){ + return reverseComparator(function(a, b) { return compare(get(a),get(b)); }, descending); }); return slice.call(array).sort(reverseComparator(comparator, reverseOrder)); - function comparator(o1, o2){ - for ( var i = 0; i < sortPredicate.length; i++) { + function comparator(o1, o2) { + for (var i = 0; i < sortPredicate.length; i++) { var comp = sortPredicate[i](o1, o2); if (comp !== 0) return comp; } return 0; } function reverseComparator(comp, descending) { - return toBoolean(descending) - ? function(a,b){return comp(b,a);} + return descending + ? function(a, b) {return comp(b,a);} : comp; } - function compare(v1, v2){ + + function isPrimitive(value) { + switch (typeof value) { + case 'number': /* falls through */ + case 'boolean': /* falls through */ + case 'string': + return true; + default: + return false; + } + } + + function objectToString(value) { + if (value === null) return 'null'; + if (typeof value.valueOf === 'function') { + value = value.valueOf(); + if (isPrimitive(value)) return value; + } + if (typeof value.toString === 'function') { + value = value.toString(); + if (isPrimitive(value)) return value; + } + return ''; + } + + function compare(v1, v2) { var t1 = typeof v1; var t2 = typeof v2; - if (t1 == t2) { - if (isDate(v1) && isDate(v2)) { - v1 = v1.valueOf(); - v2 = v2.valueOf(); - } - if (t1 == "string") { + if (t1 === t2 && t1 === "object") { + v1 = objectToString(v1); + v2 = objectToString(v2); + } + if (t1 === t2) { + if (t1 === "string") { v1 = v1.toLowerCase(); v2 = v2.toLowerCase(); } @@ -15789,28 +17937,15 @@ function ngDirective(directive) { var htmlAnchorDirective = valueFn({ restrict: 'E', compile: function(element, attr) { - - if (msie <= 8) { - - // turn link into a stylable link in IE - // but only if it doesn't have name attribute, in which case it's an anchor - if (!attr.href && !attr.name) { - attr.$set('href', ''); - } - - // add a comment node to anchors to workaround IE bug that causes element content to be reset - // to new attribute content if attribute is updated with value containing @ and element also - // contains value with @ - // see issue #1949 - element.append(document.createComment('IE fix')); - } - if (!attr.href && !attr.xlinkHref && !attr.name) { return function(scope, element) { + // If the linked element is not an anchor tag anymore, do nothing + if (element[0].nodeName.toLowerCase() !== 'a') return; + // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 'xlink:href' : 'href'; - element.on('click', function(event){ + element.on('click', function(event) { // if we have no href url, then don't navigate anywhere. if (!element.attr(href)) { event.preventDefault(); @@ -15837,12 +17972,12 @@ var htmlAnchorDirective = valueFn({ * * The wrong way to write it: * ```html - * + * link1 * ``` * * The correct way to write it: * ```html - * + * link1 * ``` * * @element A @@ -15980,20 +18115,24 @@ var htmlAnchorDirective = valueFn({ * * @description * - * We shouldn't do this, because it will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: + * This directive sets the `disabled` attribute on the element if the + * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. + * + * A special directive is necessary because we cannot use interpolation inside the `disabled` + * attribute. The following example would make the button enabled on Chrome/Firefox + * but not on older IEs: + * * ```html - *
    - * + * + *
    + * *
    * ``` * - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as disabled. (Their presence means true and their absence means false.) + * This is because the HTML specification does not require browsers to preserve the values of + * boolean attributes such as `disabled` (Their presence means true and their absence means false.) * If we put an Angular interpolation expression into such an attribute then the * binding information would be lost when the browser removes the attribute. - * The `ngDisabled` directive solves this problem for the `disabled` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. * * @example @@ -16012,7 +18151,7 @@ var htmlAnchorDirective = valueFn({ * * @element INPUT * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, - * then special attribute "disabled" will be set on the element + * then the `disabled` attribute will be set on the element */ @@ -16171,6 +18310,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) { var normalized = directiveNormalize('ng-' + attrName); ngAttributeAliasDirectives[normalized] = function() { return { + restrict: 'A', priority: 100, link: function(scope, element, attr) { scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { @@ -16181,6 +18321,29 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) { }; }); +// aliased input attrs are evaluated +forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { + ngAttributeAliasDirectives[ngAttr] = function() { + return { + priority: 100, + link: function(scope, element, attr) { + //special case ngPattern when a literal regular expression value + //is used as the expression (this way we don't have to watch anything). + if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { + var match = attr.ngPattern.match(REGEX_STRING_REGEXP); + if (match) { + attr.$set("ngPattern", new RegExp(match[1], match[2])); + return; + } + } + + scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { + attr.$set(ngAttr, value); + }); + } + }; + }; +}); // ng-src, ng-srcset, ng-href are interpolated forEach(['src', 'srcset', 'href'], function(attrName) { @@ -16220,14 +18383,22 @@ forEach(['src', 'srcset', 'href'], function(attrName) { }; }); -/* global -nullFormCtrl */ +/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true + */ var nullFormCtrl = { $addControl: noop, + $$renameControl: nullFormRenameControl, $removeControl: noop, $setValidity: noop, $setDirty: noop, - $setPristine: noop -}; + $setPristine: noop, + $setSubmitted: noop +}, +SUBMITTED_CLASS = 'ng-submitted'; + +function nullFormRenameControl(control, name) { + control.$name = name; +} /** * @ngdoc type @@ -16237,13 +18408,13 @@ var nullFormCtrl = { * @property {boolean} $dirty True if user has already interacted with the form. * @property {boolean} $valid True if all of the containing forms and controls are valid. * @property {boolean} $invalid True if at least one containing control or form is invalid. + * @property {boolean} $submitted True if user has submitted the form even if its invalid. * - * @property {Object} $error Is an object hash, containing references to all invalid controls or - * forms, where: + * @property {Object} $error Is an object hash, containing references to controls or + * forms with failing validators, where: * * - keys are validation tokens (error names), - * - values are arrays of controls or forms that are invalid for given error name. - * + * - values are arrays of controls or forms that have a failing validator for given error name. * * Built-in validation tokens: * @@ -16256,6 +18427,11 @@ var nullFormCtrl = { * - `pattern` * - `required` * - `url` + * - `date` + * - `datetimelocal` + * - `time` + * - `week` + * - `month` * * @description * `FormController` keeps track of all its controls and nested forms as well as the state of them, @@ -16266,34 +18442,59 @@ var nullFormCtrl = { * */ //asks for $scope to fool the BC controller module -FormController.$inject = ['$element', '$attrs', '$scope', '$animate']; -function FormController(element, attrs, $scope, $animate) { +FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; +function FormController(element, attrs, $scope, $animate, $interpolate) { var form = this, - parentForm = element.parent().controller('form') || nullFormCtrl, - invalidCount = 0, // used to easily determine if we are valid - errors = form.$error = {}, controls = []; + var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl; + // init state - form.$name = attrs.name || attrs.ngForm; + form.$error = {}; + form.$$success = {}; + form.$pending = undefined; + form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); form.$dirty = false; form.$pristine = true; form.$valid = true; form.$invalid = false; + form.$submitted = false; parentForm.$addControl(form); - // Setup initial state of the control - element.addClass(PRISTINE_CLASS); - toggleValidCss(true); + /** + * @ngdoc method + * @name form.FormController#$rollbackViewValue + * + * @description + * Rollback all form controls pending updates to the `$modelValue`. + * + * Updates may be pending by a debounced event or because the input is waiting for a some future + * event defined in `ng-model-options`. This method is typically needed by the reset button of + * a form that uses `ng-model-options` to pend updates. + */ + form.$rollbackViewValue = function() { + forEach(controls, function(control) { + control.$rollbackViewValue(); + }); + }; - // convenience method for easy toggling of classes - function toggleValidCss(isValid, validationErrorKey) { - validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; - $animate.setClass(element, - (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey, - (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); - } + /** + * @ngdoc method + * @name form.FormController#$commitViewValue + * + * @description + * Commit all form controls pending updates to the `$modelValue`. + * + * Updates may be pending by a debounced event or because the input is waiting for a some future + * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` + * usually handles calling this in response to input events. + */ + form.$commitViewValue = function() { + forEach(controls, function(control) { + control.$commitViewValue(); + }); + }; /** * @ngdoc method @@ -16315,6 +18516,17 @@ function FormController(element, attrs, $scope, $animate) { } }; + // Private API: rename a form control + form.$$renameControl = function(control, newName) { + var oldName = control.$name; + + if (form[oldName] === control) { + delete form[oldName]; + } + form[newName] = control; + control.$name = newName; + }; + /** * @ngdoc method * @name form.FormController#$removeControl @@ -16328,13 +18540,20 @@ function FormController(element, attrs, $scope, $animate) { if (control.$name && form[control.$name] === control) { delete form[control.$name]; } - forEach(errors, function(queue, validationToken) { - form.$setValidity(validationToken, true, control); + forEach(form.$pending, function(value, name) { + form.$setValidity(name, null, control); + }); + forEach(form.$error, function(value, name) { + form.$setValidity(name, null, control); + }); + forEach(form.$$success, function(value, name) { + form.$setValidity(name, null, control); }); arrayRemove(controls, control); }; + /** * @ngdoc method * @name form.FormController#$setValidity @@ -16344,43 +18563,33 @@ function FormController(element, attrs, $scope, $animate) { * * This method will also propagate to parent forms. */ - form.$setValidity = function(validationToken, isValid, control) { - var queue = errors[validationToken]; - - if (isValid) { - if (queue) { - arrayRemove(queue, control); - if (!queue.length) { - invalidCount--; - if (!invalidCount) { - toggleValidCss(isValid); - form.$valid = true; - form.$invalid = false; - } - errors[validationToken] = false; - toggleValidCss(true, validationToken); - parentForm.$setValidity(validationToken, true, form); + addSetValidityMethod({ + ctrl: this, + $element: element, + set: function(object, property, controller) { + var list = object[property]; + if (!list) { + object[property] = [controller]; + } else { + var index = list.indexOf(controller); + if (index === -1) { + list.push(controller); } } - - } else { - if (!invalidCount) { - toggleValidCss(isValid); + }, + unset: function(object, property, controller) { + var list = object[property]; + if (!list) { + return; } - if (queue) { - if (includes(queue, control)) return; - } else { - errors[validationToken] = queue = []; - invalidCount++; - toggleValidCss(false, validationToken); - parentForm.$setValidity(validationToken, false, form); + arrayRemove(list, controller); + if (list.length === 0) { + delete object[property]; } - queue.push(control); - - form.$valid = false; - form.$invalid = true; - } - }; + }, + parentForm: parentForm, + $animate: $animate + }); /** * @ngdoc method @@ -16414,17 +18623,48 @@ function FormController(element, attrs, $scope, $animate) { * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after * saving or resetting it. */ - form.$setPristine = function () { - $animate.removeClass(element, DIRTY_CLASS); - $animate.addClass(element, PRISTINE_CLASS); + form.$setPristine = function() { + $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); form.$dirty = false; form.$pristine = true; + form.$submitted = false; forEach(controls, function(control) { control.$setPristine(); }); }; -} + /** + * @ngdoc method + * @name form.FormController#$setUntouched + * + * @description + * Sets the form to its untouched state. + * + * This method can be called to remove the 'ng-touched' class and set the form controls to their + * untouched state (ng-untouched class). + * + * Setting a form controls back to their untouched state is often useful when setting the form + * back to its pristine state. + */ + form.$setUntouched = function() { + forEach(controls, function(control) { + control.$setUntouched(); + }); + }; + + /** + * @ngdoc method + * @name form.FormController#$setSubmitted + * + * @description + * Sets the form to its submitted state. + */ + form.$setSubmitted = function() { + $animate.addClass(element, SUBMITTED_CLASS); + form.$submitted = true; + parentForm.$setSubmitted(); + }; +} /** * @ngdoc directive @@ -16459,7 +18699,7 @@ function FormController(element, attrs, $scope, $animate) { * * # Alias: {@link ng.directive:ngForm `ngForm`} * - * In Angular forms can be nested. This means that the outer form is valid when all of the child + * In Angular, forms can be nested. This means that the outer form is valid when all of the child * forms are valid as well. However, browsers do not allow nesting of `
    ` elements, so * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to * `` but can be nested. This allows you to have nested forms, which is very useful when @@ -16474,6 +18714,7 @@ function FormController(element, attrs, $scope, $animate) { * - `ng-invalid` is set if the form is invalid. * - `ng-pristine` is set if the form is pristine. * - `ng-dirty` is set if the form is dirty. + * - `ng-submitted` is set if the form was submitted. * * Keep in mind that ngAnimate can detect each of these classes when added and removed. * @@ -16507,6 +18748,9 @@ function FormController(element, attrs, $scope, $animate) { * hitting enter in any of the input fields will trigger the click handler on the *first* button or * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) * + * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is + * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` + * to have access to the updated model. * * ## Animation Hooks * @@ -16554,11 +18798,11 @@ function FormController(element, attrs, $scope, $animate) { userType: Required!
    - userType = {{userType}}
    - myForm.input.$valid = {{myForm.input.$valid}}
    - myForm.input.$error = {{myForm.input.$error}}
    - myForm.$valid = {{myForm.$valid}}
    - myForm.$error.required = {{!!myForm.$error.required}}
    + userType = {{userType}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    @@ -16593,48 +18837,60 @@ var formDirectiveFactory = function(isNgForm) { name: 'form', restrict: isNgForm ? 'EAC' : 'E', controller: FormController, - compile: function() { + compile: function ngFormCompile(formElement, attr) { + // Setup initial state of the control + formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); + + var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); + return { - pre: function(scope, formElement, attr, controller) { - if (!attr.action) { + pre: function ngFormPreLink(scope, formElement, attr, controller) { + // if `action` attr is not present on the form, prevent the default action (submission) + if (!('action' in attr)) { // we can't use jq events because if a form is destroyed during submission the default // action is not prevented. see #1238 // // IE 9 is not affected because it doesn't fire a submit event and try to do a full // page reload if the form was destroyed by submission of the form via a click handler // on a button in the form. Looks like an IE9 specific bug. - var preventDefaultListener = function(event) { - event.preventDefault - ? event.preventDefault() - : event.returnValue = false; // IE + var handleFormSubmission = function(event) { + scope.$apply(function() { + controller.$commitViewValue(); + controller.$setSubmitted(); + }); + + event.preventDefault(); }; - addEventListenerFn(formElement[0], 'submit', preventDefaultListener); + addEventListenerFn(formElement[0], 'submit', handleFormSubmission); // unregister the preventDefault listener so that we don't not leak memory but in a // way that will achieve the prevention of the default action. formElement.on('$destroy', function() { $timeout(function() { - removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); + removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); }, 0, false); }); } - var parentFormCtrl = formElement.parent().controller('form'), - alias = attr.name || attr.ngForm; + var parentFormCtrl = controller.$$parentForm; - if (alias) { - setter(scope, alias, controller, alias); - } - if (parentFormCtrl) { - formElement.on('$destroy', function() { - parentFormCtrl.$removeControl(controller); - if (alias) { - setter(scope, alias, undefined, alias); - } - extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards + if (nameAttr) { + setter(scope, null, controller.$name, controller, controller.$name); + attr.$observe(nameAttr, function(newValue) { + if (controller.$name === newValue) return; + setter(scope, null, controller.$name, undefined, controller.$name); + parentFormCtrl.$$renameControl(controller, newValue); + setter(scope, null, controller.$name, controller, controller.$name); }); } + formElement.on('$destroy', function() { + parentFormCtrl.$removeControl(controller); + if (nameAttr) { + setter(scope, null, attr[nameAttr], undefined, controller.$name); + } + extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards + }); } }; } @@ -16647,15 +18903,25 @@ var formDirectiveFactory = function(isNgForm) { var formDirective = formDirectiveFactory(); var ngFormDirective = formDirectiveFactory(true); -/* global VALID_CLASS: true, - INVALID_CLASS: true, - PRISTINE_CLASS: true, - DIRTY_CLASS: true +/* global VALID_CLASS: false, + INVALID_CLASS: false, + PRISTINE_CLASS: false, + DIRTY_CLASS: false, + UNTOUCHED_CLASS: false, + TOUCHED_CLASS: false, + ngModelMinErr: false, */ +// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 +var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/; var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; +var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; +var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; +var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; +var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; +var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; var inputType = { @@ -16666,7 +18932,6 @@ var inputType = { * @description * Standard HTML text input with angular data binding, inherited by most of the `input` elements. * - * *NOTE* Not every feature offered is available for all input types. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. @@ -16677,10 +18942,16 @@ var inputType = { * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match + * a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object then this is used directly. + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. @@ -16693,19 +18964,21 @@ var inputType = {
    - Single word: + Single word: Required! Single word only! - text = {{text}}
    + text = {{example.text}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    @@ -16713,9 +18986,9 @@ var inputType = {
    - var text = element(by.binding('text')); + var text = element(by.binding('example.text')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); + var input = element(by.model('example.text')); it('should initialize to model', function() { expect(text.getText()).toContain('guest'); @@ -16741,6 +19014,473 @@ var inputType = { */ 'text': textInputType, + /** + * @ngdoc input + * @name input[date] + * + * @description + * Input with date validation and transformation. In browsers that do not yet support + * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 + * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many + * modern browsers do not yet support this input type, it is important to provide cues to users on the + * expected input format via a placeholder or label. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * + * The timezone to be used to read/write the `Date` instance in the model can be defined using + * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a + * valid ISO date string (yyyy-MM-dd). + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be + * a valid ISO date string (yyyy-MM-dd). + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + Pick a date in 2013: + + + Required! + + Not a valid date! + value = {{example.value | date: "yyyy-MM-dd"}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('example.value')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers (see https://github.com/angular/protractor/issues/562). + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2013-10-22'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-01-01'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
    + */ + 'date': createDateInputType('date', DATE_REGEXP, + createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), + 'yyyy-MM-dd'), + + /** + * @ngdoc input + * @name input[datetime-local] + * + * @description + * Input with datetime validation and transformation. In browsers that do not yet support + * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 + * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * + * The timezone to be used to read/write the `Date` instance in the model can be defined using + * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a + * valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be + * a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + Pick a date between in 2013: + + + Required! + + Not a valid date! + value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('example.value')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers (https://github.com/angular/protractor/issues/562). + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2010-12-28T14:57:00'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-01-01T23:59:00'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
    + */ + 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, + createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), + 'yyyy-MM-ddTHH:mm:ss.sss'), + + /** + * @ngdoc input + * @name input[time] + * + * @description + * Input with time validation and transformation. In browsers that do not yet support + * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 + * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a + * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * + * The timezone to be used to read/write the `Date` instance in the model can be defined using + * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a + * valid ISO time format (HH:mm:ss). + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a + * valid ISO time format (HH:mm:ss). + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + Pick a between 8am and 5pm: + + + Required! + + Not a valid date! + value = {{example.value | date: "HH:mm:ss"}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value | date: "HH:mm:ss"')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('example.value')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers (https://github.com/angular/protractor/issues/562). + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('14:57:00'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('23:59:00'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
    + */ + 'time': createDateInputType('time', TIME_REGEXP, + createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), + 'HH:mm:ss.sss'), + + /** + * @ngdoc input + * @name input[week] + * + * @description + * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support + * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 + * week format (yyyy-W##), for example: `2013-W02`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * + * The timezone to be used to read/write the `Date` instance in the model can be defined using + * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a + * valid ISO week format (yyyy-W##). + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be + * a valid ISO week format (yyyy-W##). + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + Pick a date between in 2013: + + + Required! + + Not a valid date! + value = {{example.value | date: "yyyy-Www"}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value | date: "yyyy-Www"')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('example.value')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers (https://github.com/angular/protractor/issues/562). + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2013-W01'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-W01'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
    + */ + 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), + + /** + * @ngdoc input + * @name input[month] + * + * @description + * Input with month validation and transformation. In browsers that do not yet support + * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 + * month format (yyyy-MM), for example: `2009-01`. + * + * The model must always be a Date object, otherwise Angular will throw an error. + * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. + * If the model is not set to the first of the month, the next view to model update will set it + * to the first of the month. + * + * The timezone to be used to read/write the `Date` instance in the model can be defined using + * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be + * a valid ISO month format (yyyy-MM). + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must + * be a valid ISO month format (yyyy-MM). + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
    + Pick a month in 2013: + + + Required! + + Not a valid month! + value = {{example.value | date: "yyyy-MM"}}
    + myForm.input.$valid = {{myForm.input.$valid}}
    + myForm.input.$error = {{myForm.input.$error}}
    + myForm.$valid = {{myForm.$valid}}
    + myForm.$error.required = {{!!myForm.$error.required}}
    +
    +
    + + var value = element(by.binding('example.value | date: "yyyy-MM"')); + var valid = element(by.binding('myForm.input.$valid')); + var input = element(by.model('example.value')); + + // currently protractor/webdriver does not support + // sending keys to all known HTML5 input controls + // for various browsers (https://github.com/angular/protractor/issues/562). + function setInput(val) { + // set the value of the element and force validation. + var scr = "var ipt = document.getElementById('exampleInput'); " + + "ipt.value = '" + val + "';" + + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; + browser.executeScript(scr); + } + + it('should initialize to model', function() { + expect(value.getText()).toContain('2013-10'); + expect(valid.getText()).toContain('myForm.input.$valid = true'); + }); + + it('should be invalid if empty', function() { + setInput(''); + expect(value.getText()).toEqual('value ='); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + + it('should be invalid if over max', function() { + setInput('2015-01'); + expect(value.getText()).toContain(''); + expect(valid.getText()).toContain('myForm.input.$valid = false'); + }); + +
    + */ + 'month': createDateInputType('month', MONTH_REGEXP, + createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), + 'yyyy-MM'), /** * @ngdoc input @@ -16750,6 +19490,12 @@ var inputType = { * Text input with number validation and transformation. Sets the `number` validation * error if not a valid number. * + *
    + * The model must always be of type `number` otherwise Angular will throw an error. + * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} + * error docs for more information and an example of how to convert your model if necessary. + *
    + * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. @@ -16761,10 +19507,16 @@ var inputType = { * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match + * a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object then this is used directly. + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @@ -16774,17 +19526,19 @@ var inputType = {
    - Number: Required! Not valid number! - value = {{value}}
    + value = {{example.value}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    @@ -16792,9 +19546,9 @@ var inputType = {
    - var value = element(by.binding('value')); + var value = element(by.binding('example.value')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); + var input = element(by.model('example.value')); it('should initialize to model', function() { expect(value.getText()).toContain('12'); @@ -16828,6 +19582,12 @@ var inputType = { * Text input with URL validation. Sets the `url` validation error key if the content is not a * valid URL. * + *
    + * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex + * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify + * the built-in validators (see the {@link guide/forms Forms guide}) + *
    + * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. @@ -16837,10 +19597,16 @@ var inputType = { * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match + * a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object then this is used directly. + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @@ -16850,16 +19616,18 @@ var inputType = {
    - URL: + URL: Required! Not valid url! - text = {{text}}
    + text = {{url.text}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    @@ -16868,9 +19636,9 @@ var inputType = {
    - var text = element(by.binding('text')); + var text = element(by.binding('url.text')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); + var input = element(by.model('url.text')); it('should initialize to model', function() { expect(text.getText()).toContain('http://google.com'); @@ -16905,6 +19673,12 @@ var inputType = { * Text input with email validation. Sets the `email` validation error key if not a valid email * address. * + *
    + * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex + * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can + * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) + *
    + * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. @@ -16914,10 +19688,16 @@ var inputType = { * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of + * any length. + * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string + * that contains the regular expression body that will be converted to a regular expression + * as in the ngPattern directive. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match + * a RegExp found by evaluating the Angular expression given in the attribute value. + * If the expression evaluates to a RegExp object then this is used directly. + * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` + * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @@ -16927,16 +19707,18 @@ var inputType = {
    - Email: + Email: Required! Not valid email! - text = {{text}}
    + text = {{email.text}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    @@ -16945,9 +19727,9 @@ var inputType = {
    - var text = element(by.binding('text')); + var text = element(by.binding('email.text')); var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); + var input = element(by.model('email.text')); it('should initialize to model', function() { expect(text.getText()).toContain('me@example.com'); @@ -16994,7 +19776,9 @@ var inputType = {
    - Red
    - Green
    - Blue
    - color = {{color | json}}
    + Red
    + Green
    + Blue
    + color = {{color.name | json}}
    Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
    it('should change state', function() { - var color = element(by.binding('color')); + var color = element(by.binding('color.name')); expect(color.getText()).toContain('blue'); - element.all(by.model('color')).get(0).click(); + element.all(by.model('color.name')).get(0).click(); expect(color.getText()).toContain('red'); }); @@ -17034,8 +19818,8 @@ var inputType = { * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. - * @param {string=} ngTrueValue The value to which the expression should be set when selected. - * @param {string=} ngFalseValue The value to which the expression should be set when not selected. + * @param {expression=} ngTrueValue The value to which the expression should be set when selected. + * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @@ -17045,28 +19829,30 @@ var inputType = {
    - Value1:
    - Value2:
    - value1 = {{value1}}
    - value2 = {{value2}}
    + Value1:
    + Value2:
    + value1 = {{checkboxModel.value1}}
    + value2 = {{checkboxModel.value2}}
    it('should change state', function() { - var value1 = element(by.binding('value1')); - var value2 = element(by.binding('value2')); + var value1 = element(by.binding('checkboxModel.value1')); + var value2 = element(by.binding('checkboxModel.value2')); expect(value1.getText()).toContain('true'); expect(value2.getText()).toContain('YES'); - element(by.model('value1')).click(); - element(by.model('value2')).click(); + element(by.model('checkboxModel.value1')).click(); + element(by.model('checkboxModel.value2')).click(); expect(value1.getText()).toContain('false'); expect(value2.getText()).toContain('NO'); @@ -17083,50 +19869,19 @@ var inputType = { 'file': noop }; -// A helper function to call $setValidity and return the value / undefined, -// a pattern that is repeated a lot in the input validation logic. -function validate(ctrl, validatorName, validity, value){ - ctrl.$setValidity(validatorName, validity); - return validity ? value : undefined; -} - -function testFlags(validity, flags) { - var i, flag; - if (flags) { - for (i=0; i= minlength, value); - }; + if (parts) { + parts.shift(); + if (date) { + map = { + yyyy: date.getFullYear(), + MM: date.getMonth() + 1, + dd: date.getDate(), + HH: date.getHours(), + mm: date.getMinutes(), + ss: date.getSeconds(), + sss: date.getMilliseconds() / 1000 + }; + } else { + map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; + } - ctrl.$parsers.push(minLengthValidator); - ctrl.$formatters.push(minLengthValidator); - } + forEach(parts, function(part, index) { + if (index < mapping.length) { + map[mapping[index]] = +part; + } + }); + return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); + } + } - // max length validator - if (attr.ngMaxlength) { - var maxlength = int(attr.ngMaxlength); - var maxLengthValidator = function(value) { - return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); - }; + return NaN; + }; +} - ctrl.$parsers.push(maxLengthValidator); - ctrl.$formatters.push(maxLengthValidator); +function createDateInputType(type, regexp, parseDate, format) { + return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { + badInputChecker(scope, element, attr, ctrl); + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; + var previousDate; + + ctrl.$$parserName = type; + ctrl.$parsers.push(function(value) { + if (ctrl.$isEmpty(value)) return null; + if (regexp.test(value)) { + // Note: We cannot read ctrl.$modelValue, as there might be a different + // parser/formatter in the processing chain so that the model + // contains some different data format! + var parsedDate = parseDate(value, previousDate); + if (timezone === 'UTC') { + parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset()); + } + return parsedDate; + } + return undefined; + }); + + ctrl.$formatters.push(function(value) { + if (value && !isDate(value)) { + throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); + } + if (isValidDate(value)) { + previousDate = value; + if (previousDate && timezone === 'UTC') { + var timezoneOffset = 60000 * previousDate.getTimezoneOffset(); + previousDate = new Date(previousDate.getTime() + timezoneOffset); + } + return $filter('date')(value, format, timezone); + } else { + previousDate = null; + return ''; + } + }); + + if (isDefined(attr.min) || attr.ngMin) { + var minVal; + ctrl.$validators.min = function(value) { + return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; + }; + attr.$observe('min', function(val) { + minVal = parseObservedDateValue(val); + ctrl.$validate(); + }); + } + + if (isDefined(attr.max) || attr.ngMax) { + var maxVal; + ctrl.$validators.max = function(value) { + return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; + }; + attr.$observe('max', function(val) { + maxVal = parseObservedDateValue(val); + ctrl.$validate(); + }); + } + + function isValidDate(value) { + // Invalid Date: getTime() returns NaN + return value && !(value.getTime && value.getTime() !== value.getTime()); + } + + function parseObservedDateValue(val) { + return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined; + } + }; +} + +function badInputChecker(scope, element, attr, ctrl) { + var node = element[0]; + var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); + if (nativeValidation) { + ctrl.$parsers.push(function(value) { + var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; + // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430): + // - also sets validity.badInput (should only be validity.typeMismatch). + // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email) + // - can ignore this case as we can still read out the erroneous email... + return validity.badInput && !validity.typeMismatch ? undefined : value; + }); } } -var numberBadFlags = ['badInput']; - function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); + badInputChecker(scope, element, attr, ctrl); + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + ctrl.$$parserName = 'number'; ctrl.$parsers.push(function(value) { - var empty = ctrl.$isEmpty(value); - if (empty || NUMBER_REGEXP.test(value)) { - ctrl.$setValidity('number', true); - return value === '' ? null : (empty ? value : parseFloat(value)); - } else { - ctrl.$setValidity('number', false); - return undefined; + if (ctrl.$isEmpty(value)) return null; + if (NUMBER_REGEXP.test(value)) return parseFloat(value); + return undefined; + }); + + ctrl.$formatters.push(function(value) { + if (!ctrl.$isEmpty(value)) { + if (!isNumber(value)) { + throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); + } + value = value.toString(); } + return value; }); - addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState); - - ctrl.$formatters.push(function(value) { - return ctrl.$isEmpty(value) ? '' : '' + value; - }); - - if (attr.min) { - var minValidator = function(value) { - var min = parseFloat(attr.min); - return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); + if (isDefined(attr.min) || attr.ngMin) { + var minVal; + ctrl.$validators.min = function(value) { + return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; }; - ctrl.$parsers.push(minValidator); - ctrl.$formatters.push(minValidator); + attr.$observe('min', function(val) { + if (isDefined(val) && !isNumber(val)) { + val = parseFloat(val, 10); + } + minVal = isNumber(val) && !isNaN(val) ? val : undefined; + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + }); } - if (attr.max) { - var maxValidator = function(value) { - var max = parseFloat(attr.max); - return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); + if (isDefined(attr.max) || attr.ngMax) { + var maxVal; + ctrl.$validators.max = function(value) { + return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; }; - ctrl.$parsers.push(maxValidator); - ctrl.$formatters.push(maxValidator); + attr.$observe('max', function(val) { + if (isDefined(val) && !isNumber(val)) { + val = parseFloat(val, 10); + } + maxVal = isNumber(val) && !isNaN(val) ? val : undefined; + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + }); } - - ctrl.$formatters.push(function(value) { - return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); - }); } function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); + // Note: no badInputChecker here by purpose as `url` is only a validation + // in browsers, i.e. we can always read out input.value even if it is not valid! + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + stringBasedInputType(ctrl); - var urlValidator = function(value) { - return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); + ctrl.$$parserName = 'url'; + ctrl.$validators.url = function(modelValue, viewValue) { + var value = modelValue || viewValue; + return ctrl.$isEmpty(value) || URL_REGEXP.test(value); }; - - ctrl.$formatters.push(urlValidator); - ctrl.$parsers.push(urlValidator); } function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); + // Note: no badInputChecker here by purpose as `url` is only a validation + // in browsers, i.e. we can always read out input.value even if it is not valid! + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + stringBasedInputType(ctrl); - var emailValidator = function(value) { - return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); + ctrl.$$parserName = 'email'; + ctrl.$validators.email = function(modelValue, viewValue) { + var value = modelValue || viewValue; + return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); }; - - ctrl.$formatters.push(emailValidator); - ctrl.$parsers.push(emailValidator); } function radioInputType(scope, element, attr, ctrl) { @@ -17349,13 +20222,13 @@ function radioInputType(scope, element, attr, ctrl) { element.attr('name', nextUid()); } - element.on('click', function() { + var listener = function(ev) { if (element[0].checked) { - scope.$apply(function() { - ctrl.$setViewValue(attr.value); - }); + ctrl.$setViewValue(attr.value, ev && ev.type); } - }); + }; + + element.on('click', listener); ctrl.$render = function() { var value = attr.value; @@ -17365,30 +20238,42 @@ function radioInputType(scope, element, attr, ctrl) { attr.$observe('value', ctrl.$render); } -function checkboxInputType(scope, element, attr, ctrl) { - var trueValue = attr.ngTrueValue, - falseValue = attr.ngFalseValue; +function parseConstantExpr($parse, context, name, expression, fallback) { + var parseFn; + if (isDefined(expression)) { + parseFn = $parse(expression); + if (!parseFn.constant) { + throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + + '`{1}`.', name, expression); + } + return parseFn(context); + } + return fallback; +} - if (!isString(trueValue)) trueValue = true; - if (!isString(falseValue)) falseValue = false; +function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { + var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); + var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); - element.on('click', function() { - scope.$apply(function() { - ctrl.$setViewValue(element[0].checked); - }); - }); + var listener = function(ev) { + ctrl.$setViewValue(element[0].checked, ev && ev.type); + }; + + element.on('click', listener); ctrl.$render = function() { element[0].checked = ctrl.$viewValue; }; - // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. + // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` + // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert + // it to a boolean. ctrl.$isEmpty = function(value) { - return value !== trueValue; + return value === false; }; ctrl.$formatters.push(function(value) { - return value === trueValue; + return equals(value, trueValue); }); ctrl.$parsers.push(function(value) { @@ -17416,7 +20301,8 @@ function checkboxInputType(scope, element, attr, ctrl) { * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any + * length. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. @@ -17432,10 +20318,14 @@ function checkboxInputType(scope, element, attr, ctrl) { * @restrict E * * @description - * HTML input element control with angular data-binding. Input control follows HTML5 input types - * and polyfills the HTML5 validation behavior for older browsers. + * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, + * input state control, and validation. + * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. * - * *NOTE* Not every feature offered is available for all input types. + *
    + * **Note:** Not every feature offered is available for all input types. + * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. + *
    * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. @@ -17444,7 +20334,8 @@ function checkboxInputType(scope, element, attr, ctrl) { * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. + * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any + * length. * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for * patterns defined as scope expressions. @@ -17488,7 +20379,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
    - var user = element(by.binding('{{user}}')); + var user = element(by.exactBinding('user')); var userNameValid = element(by.binding('myForm.userName.$valid')); var lastNameValid = element(by.binding('myForm.lastName.$valid')); var lastNameError = element(by.binding('myForm.lastName.$error')); @@ -17542,682 +20433,22 @@ function checkboxInputType(scope, element, attr, ctrl) { */ -var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { +var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', + function($browser, $sniffer, $filter, $parse) { return { restrict: 'E', - require: '?ngModel', - link: function(scope, element, attr, ctrl) { - if (ctrl) { - (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, - $browser); + require: ['?ngModel'], + link: { + pre: function(scope, element, attr, ctrls) { + if (ctrls[0]) { + (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, + $browser, $filter, $parse); + } } } }; }]; -var VALID_CLASS = 'ng-valid', - INVALID_CLASS = 'ng-invalid', - PRISTINE_CLASS = 'ng-pristine', - DIRTY_CLASS = 'ng-dirty'; - -/** - * @ngdoc type - * @name ngModel.NgModelController - * - * @property {string} $viewValue Actual string value in the view. - * @property {*} $modelValue The value in the model, that the control is bound to. - * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever - the control reads value from the DOM. Each function is called, in turn, passing the value - through to the next. The last return value is used to populate the model. - Used to sanitize / convert the value as well as validation. For validation, - the parsers should update the validity state using - {@link ngModel.NgModelController#$setValidity $setValidity()}, - and return `undefined` for invalid values. - - * - * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever - the model value changes. Each function is called, in turn, passing the value through to the - next. Used to format / convert values for display in the control and validation. - * ```js - * function formatter(value) { - * if (value) { - * return value.toUpperCase(); - * } - * } - * ngModel.$formatters.push(formatter); - * ``` - * - * @property {Array.} $viewChangeListeners Array of functions to execute whenever the - * view value has changed. It is called with no arguments, and its return value is ignored. - * This can be used in place of additional $watches against the model value. - * - * @property {Object} $error An object hash with all errors as keys. - * - * @property {boolean} $pristine True if user has not interacted with the control yet. - * @property {boolean} $dirty True if user has already interacted with the control. - * @property {boolean} $valid True if there is no error. - * @property {boolean} $invalid True if at least one error on the control. - * - * @description - * - * `NgModelController` provides API for the `ng-model` directive. The controller contains - * services for data-binding, validation, CSS updates, and value formatting and parsing. It - * purposefully does not contain any logic which deals with DOM rendering or listening to - * DOM events. Such DOM related logic should be provided by other directives which make use of - * `NgModelController` for data-binding. - * - * ## Custom Control Example - * This example shows how to use `NgModelController` with a custom control to achieve - * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) - * collaborate together to achieve the desired result. - * - * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element - * contents be edited in place by the user. This will not work on older browsers. - * - * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} - * module to automatically remove "bad" content like inline event listener (e.g. ``). - * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks - * that content using the `$sce` service. - * - * - - [contenteditable] { - border: 1px solid black; - background-color: white; - min-height: 20px; - } - - .ng-invalid { - border: 1px solid red; - } - - - - angular.module('customControl', ['ngSanitize']). - directive('contenteditable', ['$sce', function($sce) { - return { - restrict: 'A', // only activate on element attribute - require: '?ngModel', // get a hold of NgModelController - link: function(scope, element, attrs, ngModel) { - if(!ngModel) return; // do nothing if no ng-model - - // Specify how UI should be updated - ngModel.$render = function() { - element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); - }; - - // Listen for change events to enable binding - element.on('blur keyup change', function() { - scope.$evalAsync(read); - }); - read(); // initialize - - // Write data to the model - function read() { - var html = element.html(); - // When we clear the content editable the browser leaves a
    behind - // If strip-br attribute is provided then we strip this out - if( attrs.stripBr && html == '
    ' ) { - html = ''; - } - ngModel.$setViewValue(html); - } - } - }; - }]); -
    - -
    -
    Change me!
    - Required! -
    - -
    -
    - - it('should data-bind and become invalid', function() { - if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { - // SafariDriver can't handle contenteditable - // and Firefox driver can't clear contenteditables very well - return; - } - var contentEditable = element(by.css('[contenteditable]')); - var content = 'Change me!'; - - expect(contentEditable.getText()).toEqual(content); - - contentEditable.clear(); - contentEditable.sendKeys(protractor.Key.BACK_SPACE); - expect(contentEditable.getText()).toEqual(''); - expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); - }); - - *
    - * - * - */ -var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', - function($scope, $exceptionHandler, $attr, $element, $parse, $animate) { - this.$viewValue = Number.NaN; - this.$modelValue = Number.NaN; - this.$parsers = []; - this.$formatters = []; - this.$viewChangeListeners = []; - this.$pristine = true; - this.$dirty = false; - this.$valid = true; - this.$invalid = false; - this.$name = $attr.name; - - var ngModelGet = $parse($attr.ngModel), - ngModelSet = ngModelGet.assign; - - if (!ngModelSet) { - throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", - $attr.ngModel, startingTag($element)); - } - - /** - * @ngdoc method - * @name ngModel.NgModelController#$render - * - * @description - * Called when the view needs to be updated. It is expected that the user of the ng-model - * directive will implement this method. - */ - this.$render = noop; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$isEmpty - * - * @description - * This is called when we need to determine if the value of the input is empty. - * - * For instance, the required directive does this to work out if the input has data or not. - * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. - * - * You can override this for input directives whose concept of being empty is different to the - * default. The `checkboxInputType` directive does this because in its case a value of `false` - * implies empty. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is empty. - */ - this.$isEmpty = function(value) { - return isUndefined(value) || value === '' || value === null || value !== value; - }; - - var parentForm = $element.inheritedData('$formController') || nullFormCtrl, - invalidCount = 0, // used to easily determine if we are valid - $error = this.$error = {}; // keep invalid keys here - - - // Setup initial state of the control - $element.addClass(PRISTINE_CLASS); - toggleValidCss(true); - - // convenience method for easy toggling of classes - function toggleValidCss(isValid, validationErrorKey) { - validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; - $animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); - $animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); - } - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setValidity - * - * @description - * Change the validity state, and notifies the form when the control changes validity. (i.e. it - * does not notify form if given validator is already marked as invalid). - * - * This method should be called by validators - i.e. the parser or formatter functions. - * - * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign - * to `$error[validationErrorKey]=!isValid` so that it is available for data-binding. - * The `validationErrorKey` should be in camelCase and will get converted into dash-case - * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` - * class and can be bound to as `{{someForm.someControl.$error.myError}}` . - * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). - */ - this.$setValidity = function(validationErrorKey, isValid) { - // Purposeful use of ! here to cast isValid to boolean in case it is undefined - // jshint -W018 - if ($error[validationErrorKey] === !isValid) return; - // jshint +W018 - - if (isValid) { - if ($error[validationErrorKey]) invalidCount--; - if (!invalidCount) { - toggleValidCss(true); - this.$valid = true; - this.$invalid = false; - } - } else { - toggleValidCss(false); - this.$invalid = true; - this.$valid = false; - invalidCount++; - } - - $error[validationErrorKey] = !isValid; - toggleValidCss(isValid, validationErrorKey); - - parentForm.$setValidity(validationErrorKey, isValid, this); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setPristine - * - * @description - * Sets the control to its pristine state. - * - * This method can be called to remove the 'ng-dirty' class and set the control to its pristine - * state (ng-pristine class). - */ - this.$setPristine = function () { - this.$dirty = false; - this.$pristine = true; - $animate.removeClass($element, DIRTY_CLASS); - $animate.addClass($element, PRISTINE_CLASS); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setViewValue - * - * @description - * Update the view value. - * - * This method should be called when the view value changes, typically from within a DOM event handler. - * For example {@link ng.directive:input input} and - * {@link ng.directive:select select} directives call it. - * - * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, - * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to - * `$modelValue` and the **expression** specified in the `ng-model` attribute. - * - * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. - * - * Note that calling this function does not trigger a `$digest`. - * - * @param {string} value Value from the view. - */ - this.$setViewValue = function(value) { - this.$viewValue = value; - - // change to dirty - if (this.$pristine) { - this.$dirty = true; - this.$pristine = false; - $animate.removeClass($element, PRISTINE_CLASS); - $animate.addClass($element, DIRTY_CLASS); - parentForm.$setDirty(); - } - - forEach(this.$parsers, function(fn) { - value = fn(value); - }); - - if (this.$modelValue !== value) { - this.$modelValue = value; - ngModelSet($scope, value); - forEach(this.$viewChangeListeners, function(listener) { - try { - listener(); - } catch(e) { - $exceptionHandler(e); - } - }); - } - }; - - // model -> value - var ctrl = this; - - $scope.$watch(function ngModelWatch() { - var value = ngModelGet($scope); - - // if scope model value and ngModel value are out of sync - if (ctrl.$modelValue !== value) { - - var formatters = ctrl.$formatters, - idx = formatters.length; - - ctrl.$modelValue = value; - while(idx--) { - value = formatters[idx](value); - } - - if (ctrl.$viewValue !== value) { - ctrl.$viewValue = value; - ctrl.$render(); - } - } - - return value; - }); -}]; - - -/** - * @ngdoc directive - * @name ngModel - * - * @element input - * - * @description - * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a - * property on the scope using {@link ngModel.NgModelController NgModelController}, - * which is created and exposed by this directive. - * - * `ngModel` is responsible for: - * - * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` - * require. - * - Providing validation behavior (i.e. required, number, email, url). - * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). - * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`) including animations. - * - Registering the control with its parent {@link ng.directive:form form}. - * - * Note: `ngModel` will try to bind to the property given by evaluating the expression on the - * current scope. If the property doesn't already exist on this scope, it will be created - * implicitly and added to the scope. - * - * For best practices on using `ngModel`, see: - * - * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) - * - * For basic examples, how to use `ngModel`, see: - * - * - {@link ng.directive:input input} - * - {@link input[text] text} - * - {@link input[checkbox] checkbox} - * - {@link input[radio] radio} - * - {@link input[number] number} - * - {@link input[email] email} - * - {@link input[url] url} - * - {@link ng.directive:select select} - * - {@link ng.directive:textarea textarea} - * - * # CSS classes - * The following CSS classes are added and removed on the associated input/select/textarea element - * depending on the validity of the model. - * - * - `ng-valid` is set if the model is valid. - * - `ng-invalid` is set if the model is invalid. - * - `ng-pristine` is set if the model is pristine. - * - `ng-dirty` is set if the model is dirty. - * - * Keep in mind that ngAnimate can detect each of these classes when added and removed. - * - * ## Animation Hooks - * - * Animations within models are triggered when any of the associated CSS classes are added and removed - * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, - * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. - * The animations that are triggered within ngModel are similar to how they work in ngClass and - * animations can be hooked into using CSS transitions, keyframes as well as JS animations. - * - * The following example shows a simple way to utilize CSS transitions to style an input element - * that has been rendered as invalid after it has been validated: - * - *
    - * //be sure to include ngAnimate as a module to hook into more
    - * //advanced animations
    - * .my-input {
    - *   transition:0.5s linear all;
    - *   background: white;
    - * }
    - * .my-input.ng-invalid {
    - *   background: red;
    - *   color:white;
    - * }
    - * 
    - * - * @example - * - - - - Update input to see transitions when valid/invalid. - Integer is a valid value. -
    - -
    -
    - *
    - */ -var ngModelDirective = function() { - return { - require: ['ngModel', '^?form'], - controller: NgModelController, - link: function(scope, element, attr, ctrls) { - // notify others, especially parent forms - - var modelCtrl = ctrls[0], - formCtrl = ctrls[1] || nullFormCtrl; - - formCtrl.$addControl(modelCtrl); - - scope.$on('$destroy', function() { - formCtrl.$removeControl(modelCtrl); - }); - } - }; -}; - - -/** - * @ngdoc directive - * @name ngChange - * - * @description - * Evaluate the given expression when the user changes the input. - * The expression is evaluated immediately, unlike the JavaScript onchange event - * which only triggers at the end of a change (usually, when the user leaves the - * form element or presses the return key). - * The expression is not evaluated when the value change is coming from the model. - * - * Note, this directive requires `ngModel` to be present. - * - * @element input - * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change - * in input value. - * - * @example - * - * - * - *
    - * - * - *
    - * debug = {{confirmed}}
    - * counter = {{counter}}
    - *
    - *
    - * - * var counter = element(by.binding('counter')); - * var debug = element(by.binding('confirmed')); - * - * it('should evaluate the expression if changing from view', function() { - * expect(counter.getText()).toContain('0'); - * - * element(by.id('ng-change-example1')).click(); - * - * expect(counter.getText()).toContain('1'); - * expect(debug.getText()).toContain('true'); - * }); - * - * it('should not evaluate the expression if changing from model', function() { - * element(by.id('ng-change-example2')).click(); - - * expect(counter.getText()).toContain('0'); - * expect(debug.getText()).toContain('true'); - * }); - * - *
    - */ -var ngChangeDirective = valueFn({ - require: 'ngModel', - link: function(scope, element, attr, ctrl) { - ctrl.$viewChangeListeners.push(function() { - scope.$eval(attr.ngChange); - }); - } -}); - - -var requiredDirective = function() { - return { - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - attr.required = true; // force truthy in case we are on non input element - - var validator = function(value) { - if (attr.required && ctrl.$isEmpty(value)) { - ctrl.$setValidity('required', false); - return; - } else { - ctrl.$setValidity('required', true); - return value; - } - }; - - ctrl.$formatters.push(validator); - ctrl.$parsers.unshift(validator); - - attr.$observe('required', function() { - validator(ctrl.$viewValue); - }); - } - }; -}; - - -/** - * @ngdoc directive - * @name ngList - * - * @description - * Text input that converts between a delimited string and an array of strings. The delimiter - * can be a fixed string (by default a comma) or a regular expression. - * - * @element input - * @param {string=} ngList optional delimiter that should be used to split the value. If - * specified in form `/something/` then the value will be converted into a regular expression. - * - * @example - - - -
    - List: - - Required! -
    - names = {{names}}
    - myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
    - myForm.namesInput.$error = {{myForm.namesInput.$error}}
    - myForm.$valid = {{myForm.$valid}}
    - myForm.$error.required = {{!!myForm.$error.required}}
    -
    -
    - - var listInput = element(by.model('names')); - var names = element(by.binding('{{names}}')); - var valid = element(by.binding('myForm.namesInput.$valid')); - var error = element(by.css('span.error')); - - it('should initialize to model', function() { - expect(names.getText()).toContain('["igor","misko","vojta"]'); - expect(valid.getText()).toContain('true'); - expect(error.getCssValue('display')).toBe('none'); - }); - - it('should be invalid if empty', function() { - listInput.clear(); - listInput.sendKeys(''); - - expect(names.getText()).toContain(''); - expect(valid.getText()).toContain('false'); - expect(error.getCssValue('display')).not.toBe('none'); }); - -
    - */ -var ngListDirective = function() { - return { - require: 'ngModel', - link: function(scope, element, attr, ctrl) { - var match = /\/(.*)\//.exec(attr.ngList), - separator = match && new RegExp(match[1]) || attr.ngList || ','; - - var parse = function(viewValue) { - // If the viewValue is invalid (say required but empty) it will be `undefined` - if (isUndefined(viewValue)) return; - - var list = []; - - if (viewValue) { - forEach(viewValue.split(separator), function(value) { - if (value) list.push(trim(value)); - }); - } - - return list; - }; - - ctrl.$parsers.push(parse); - ctrl.$formatters.push(function(value) { - if (isArray(value)) { - return value.join(', '); - } - - return undefined; - }); - - // Override the standard $isEmpty because an empty array means the input is empty. - ctrl.$isEmpty = function(value) { - return !value || !value.length; - }; - } - }; -}; var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; @@ -18226,12 +20457,17 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; * @name ngValue * * @description - * Binds the given expression to the value of `input[select]` or `input[radio]`, so - * that when the element is selected, the `ngModel` of that element is set to the - * bound value. + * Binds the given expression to the value of `
    @@ -20579,29 +24616,84 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { var NG_REMOVED = '$$NG_REMOVED'; var ngRepeatMinErr = minErr('ngRepeat'); + + var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { + // TODO(perf): generate setters to shave off ~40ms or 1-1.5% + scope[valueIdentifier] = value; + if (keyIdentifier) scope[keyIdentifier] = key; + scope.$index = index; + scope.$first = (index === 0); + scope.$last = (index === (arrayLength - 1)); + scope.$middle = !(scope.$first || scope.$last); + // jshint bitwise: false + scope.$odd = !(scope.$even = (index&1) === 0); + // jshint bitwise: true + }; + + var getBlockStart = function(block) { + return block.clone[0]; + }; + + var getBlockEnd = function(block) { + return block.clone[block.clone.length - 1]; + }; + + return { + restrict: 'A', + multiElement: true, transclude: 'element', priority: 1000, terminal: true, $$tlb: true, - link: function($scope, $element, $attr, ctrl, $transclude){ - var expression = $attr.ngRepeat; - var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), - trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, - lhs, rhs, valueIdentifier, keyIdentifier, - hashFnLocals = {$id: hashKey}; + compile: function ngRepeatCompile($element, $attr) { + var expression = $attr.ngRepeat; + var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); - if (!match) { - throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", + var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); + + if (!match) { + throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", expression); - } + } - lhs = match[1]; - rhs = match[2]; - trackByExp = match[3]; + var lhs = match[1]; + var rhs = match[2]; + var aliasAs = match[3]; + var trackByExp = match[4]; - if (trackByExp) { - trackByExpGetter = $parse(trackByExp); + match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); + + if (!match) { + throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", + lhs); + } + var valueIdentifier = match[3] || match[1]; + var keyIdentifier = match[2]; + + if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || + /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { + throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.", + aliasAs); + } + + var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; + var hashFnLocals = {$id: hashKey}; + + if (trackByExp) { + trackByExpGetter = $parse(trackByExp); + } else { + trackByIdArrayFn = function(key, value) { + return hashKey(value); + }; + trackByIdObjFn = function(key) { + return key; + }; + } + + return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { + + if (trackByExpGetter) { trackByIdExpFn = function(key, value, index) { // assign key, value, and $index to the locals so that they can be used in hash functions if (keyIdentifier) hashFnLocals[keyIdentifier] = key; @@ -20609,48 +24701,39 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { hashFnLocals.$index = index; return trackByExpGetter($scope, hashFnLocals); }; - } else { - trackByIdArrayFn = function(key, value) { - return hashKey(value); - }; - trackByIdObjFn = function(key) { - return key; - }; } - match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); - if (!match) { - throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", - lhs); - } - valueIdentifier = match[3] || match[1]; - keyIdentifier = match[2]; - // Store a list of elements from previous run. This is a hash where key is the item from the // iterator, and the value is objects with following properties. // - scope: bound scope // - element: previous element. // - index: position - var lastBlockMap = {}; + // + // We are using no-proto object so that we don't need to guard against inherited props via + // hasOwnProperty. + var lastBlockMap = createMap(); //watch props - $scope.$watchCollection(rhs, function ngRepeatAction(collection){ + $scope.$watchCollection(rhs, function ngRepeatAction(collection) { var index, length, - previousNode = $element[0], // current position of the node + previousNode = $element[0], // node that cloned nodes should be inserted after + // initialized to the comment node anchor nextNode, // Same as lastBlockMap but it has the current state. It will become the // lastBlockMap on the next iteration. - nextBlockMap = {}, - arrayLength, - childScope, + nextBlockMap = createMap(), + collectionLength, key, value, // key/value of iteration trackById, trackByIdFn, collectionKeys, block, // last object information {scope, element, id} - nextBlockOrder = [], + nextBlockOrder, elementsToRemove; + if (aliasAs) { + $scope[aliasAs] = collection; + } if (isArrayLike(collection)) { collectionKeys = collection; @@ -20659,121 +24742,110 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { trackByIdFn = trackByIdExpFn || trackByIdObjFn; // if object, extract keys, sort them and use to determine order of iteration over obj props collectionKeys = []; - for (key in collection) { - if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { - collectionKeys.push(key); + for (var itemKey in collection) { + if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) != '$') { + collectionKeys.push(itemKey); } } collectionKeys.sort(); } - arrayLength = collectionKeys.length; + collectionLength = collectionKeys.length; + nextBlockOrder = new Array(collectionLength); // locate existing items - length = nextBlockOrder.length = collectionKeys.length; - for(index = 0; index < length; index++) { - key = (collection === collectionKeys) ? index : collectionKeys[index]; - value = collection[key]; - trackById = trackByIdFn(key, value, index); - assertNotHasOwnProperty(trackById, '`track by` id'); - if(lastBlockMap.hasOwnProperty(trackById)) { - block = lastBlockMap[trackById]; - delete lastBlockMap[trackById]; - nextBlockMap[trackById] = block; - nextBlockOrder[index] = block; - } else if (nextBlockMap.hasOwnProperty(trackById)) { - // restore lastBlockMap - forEach(nextBlockOrder, function(block) { - if (block && block.scope) lastBlockMap[block.id] = block; - }); - // This is a duplicate and we need to throw an error - throw ngRepeatMinErr('dupes', + for (index = 0; index < collectionLength; index++) { + key = (collection === collectionKeys) ? index : collectionKeys[index]; + value = collection[key]; + trackById = trackByIdFn(key, value, index); + if (lastBlockMap[trackById]) { + // found previously seen block + block = lastBlockMap[trackById]; + delete lastBlockMap[trackById]; + nextBlockMap[trackById] = block; + nextBlockOrder[index] = block; + } else if (nextBlockMap[trackById]) { + // if collision detected. restore lastBlockMap and throw an error + forEach(nextBlockOrder, function(block) { + if (block && block.scope) lastBlockMap[block.id] = block; + }); + throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", - expression, trackById, toJson(value)); - } else { - // new never before seen block - nextBlockOrder[index] = { id: trackById }; - nextBlockMap[trackById] = false; - } - } - - // remove existing items - for (key in lastBlockMap) { - // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn - if (lastBlockMap.hasOwnProperty(key)) { - block = lastBlockMap[key]; - elementsToRemove = getBlockElements(block.clone); - $animate.leave(elementsToRemove); - forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); - block.scope.$destroy(); + expression, trackById, value); + } else { + // new never before seen block + nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; + nextBlockMap[trackById] = true; } } + // remove leftover items + for (var blockKey in lastBlockMap) { + block = lastBlockMap[blockKey]; + elementsToRemove = getBlockNodes(block.clone); + $animate.leave(elementsToRemove); + if (elementsToRemove[0].parentNode) { + // if the element was not removed yet because of pending animation, mark it as deleted + // so that we can ignore it later + for (index = 0, length = elementsToRemove.length; index < length; index++) { + elementsToRemove[index][NG_REMOVED] = true; + } + } + block.scope.$destroy(); + } + // we are not using forEach for perf reasons (trying to avoid #call) - for (index = 0, length = collectionKeys.length; index < length; index++) { + for (index = 0; index < collectionLength; index++) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; block = nextBlockOrder[index]; - if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); if (block.scope) { // if we have already seen this object, then we need to reuse the // associated scope/element - childScope = block.scope; nextNode = previousNode; + + // skip nodes that are already pending removal via leave animation do { nextNode = nextNode.nextSibling; - } while(nextNode && nextNode[NG_REMOVED]); + } while (nextNode && nextNode[NG_REMOVED]); if (getBlockStart(block) != nextNode) { // existing item which got moved - $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); + $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); } previousNode = getBlockEnd(block); + updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); } else { // new item which we don't know about - childScope = $scope.$new(); - } + $transclude(function ngRepeatTransclude(clone, scope) { + block.scope = scope; + // http://jsperf.com/clone-vs-createcomment + var endNode = ngRepeatEndComment.cloneNode(false); + clone[clone.length++] = endNode; - childScope[valueIdentifier] = value; - if (keyIdentifier) childScope[keyIdentifier] = key; - childScope.$index = index; - childScope.$first = (index === 0); - childScope.$last = (index === (arrayLength - 1)); - childScope.$middle = !(childScope.$first || childScope.$last); - // jshint bitwise: false - childScope.$odd = !(childScope.$even = (index&1) === 0); - // jshint bitwise: true - - if (!block.scope) { - $transclude(childScope, function(clone) { - clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); + // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? $animate.enter(clone, null, jqLite(previousNode)); - previousNode = clone; - block.scope = childScope; + previousNode = endNode; // Note: We only need the first/last node of the cloned nodes. // However, we need to keep the reference to the jqlite wrapper as it might be changed later // by a directive with templateUrl when its template arrives. block.clone = clone; nextBlockMap[block.id] = block; + updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); }); } } lastBlockMap = nextBlockMap; }); + }; } }; - - function getBlockStart(block) { - return block.clone[0]; - } - - function getBlockEnd(block) { - return block.clone[block.clone.length - 1]; - } }]; +var NG_HIDE_CLASS = 'ng-hide'; +var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; /** * @ngdoc directive * @name ngShow @@ -20793,15 +24865,10 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { *
    * ``` * - * When the `ngShow` expression evaluates to false then the `.ng-hide` CSS class is added to the class attribute - * on the element causing it to become hidden. When true, the `.ng-hide` CSS class is removed + * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class + * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed * from the element causing the element not to appear hidden. * - *
    - * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):
    - * "f" / "0" / "false" / "no" / "n" / "[]" - *
    - * * ## Why is !important used? * * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector @@ -20815,17 +24882,18 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { * * ### Overriding `.ng-hide` * - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change + * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` - * class in CSS: + * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope + * with extra animation classes that can be added. * * ```css - * .ng-hide { - * //this is just another form of hiding an element - * display:block!important; - * position:absolute; - * top:-9999px; - * left:-9999px; + * .ng-hide:not(.ng-hide-animate) { + * /* this is just another form of hiding an element */ + * display: block!important; + * position: absolute; + * top: -9999px; + * left: -9999px; * } * ``` * @@ -20843,7 +24911,15 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { * //a working example can be found at the bottom of this page * // * .my-element.ng-hide-add, .my-element.ng-hide-remove { - * transition:0.5s linear all; + * /* this is required as of 1.3x to properly + * apply all styling in a show/hide animation */ + * transition: 0s linear all; + * } + * + * .my-element.ng-hide-add-active, + * .my-element.ng-hide-remove-active { + * /* the transition is defined in the active class */ + * transition: 1s linear all; * } * * .my-element.ng-hide-add { ... } @@ -20852,7 +24928,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { * .my-element.ng-hide-remove.ng-hide-remove-active { ... } * ``` * - * Keep in mind that, as of AngularJS version 1.2.17 (and 1.3.0-beta.11), there is no need to change the display + * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display * property to block during animation states--ngAnimate will handle the style toggling automatically for you. * * @animations @@ -20881,29 +24957,33 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
    - @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); + @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); .animate-show { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - line-height:20px; - opacity:1; - padding:10px; - border:1px solid black; - background:white; + line-height: 20px; + opacity: 1; + padding: 10px; + border: 1px solid black; + background: white; + } + + .animate-show.ng-hide-add.ng-hide-add-active, + .animate-show.ng-hide-remove.ng-hide-remove-active { + -webkit-transition: all linear 0.5s; + transition: all linear 0.5s; } .animate-show.ng-hide { - line-height:0; - opacity:0; - padding:0 10px; + line-height: 0; + opacity: 0; + padding: 0 10px; } .check-element { - padding:10px; - border:1px solid black; - background:white; + padding: 10px; + border: 1px solid black; + background: white; } @@ -20923,10 +25003,20 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
    */ var ngShowDirective = ['$animate', function($animate) { - return function(scope, element, attr) { - scope.$watch(attr.ngShow, function ngShowWatchAction(value){ - $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); - }); + return { + restrict: 'A', + multiElement: true, + link: function(scope, element, attr) { + scope.$watch(attr.ngShow, function ngShowWatchAction(value) { + // we're adding a temporary, animation-specific class for ng-hide since this way + // we can control when the element is actually displayed on screen without having + // to have a global/greedy CSS selector that breaks when other animations are run. + // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 + $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { + tempClasses: NG_HIDE_IN_PROGRESS_CLASS + }); + }); + } }; }]; @@ -20950,15 +25040,10 @@ var ngShowDirective = ['$animate', function($animate) { *
    * ``` * - * When the `.ngHide` expression evaluates to true then the `.ng-hide` CSS class is added to the class attribute - * on the element causing it to become hidden. When false, the `.ng-hide` CSS class is removed + * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class + * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed * from the element causing the element not to appear hidden. * - *
    - * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):
    - * "f" / "0" / "false" / "no" / "n" / "[]" - *
    - * * ## Why is !important used? * * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector @@ -20972,17 +25057,17 @@ var ngShowDirective = ['$animate', function($animate) { * * ### Overriding `.ng-hide` * - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change + * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` * class in CSS: * * ```css * .ng-hide { - * //this is just another form of hiding an element - * display:block!important; - * position:absolute; - * top:-9999px; - * left:-9999px; + * /* this is just another form of hiding an element */ + * display: block!important; + * position: absolute; + * top: -9999px; + * left: -9999px; * } * ``` * @@ -20999,7 +25084,7 @@ var ngShowDirective = ['$animate', function($animate) { * //a working example can be found at the bottom of this page * // * .my-element.ng-hide-add, .my-element.ng-hide-remove { - * transition:0.5s linear all; + * transition: 0.5s linear all; * } * * .my-element.ng-hide-add { ... } @@ -21008,7 +25093,7 @@ var ngShowDirective = ['$animate', function($animate) { * .my-element.ng-hide-remove.ng-hide-remove-active { ... } * ``` * - * Keep in mind that, as of AngularJS version 1.2.17 (and 1.3.0-beta.11), there is no need to change the display + * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display * property to block during animation states--ngAnimate will handle the style toggling automatically for you. * * @animations @@ -21037,29 +25122,29 @@ var ngShowDirective = ['$animate', function($animate) {
    - @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); + @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); .animate-hide { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - line-height:20px; - opacity:1; - padding:10px; - border:1px solid black; - background:white; + -webkit-transition: all linear 0.5s; + transition: all linear 0.5s; + line-height: 20px; + opacity: 1; + padding: 10px; + border: 1px solid black; + background: white; } .animate-hide.ng-hide { - line-height:0; - opacity:0; - padding:0 10px; + line-height: 0; + opacity: 0; + padding: 0 10px; } .check-element { - padding:10px; - border:1px solid black; - background:white; + padding: 10px; + border: 1px solid black; + background: white; } @@ -21079,10 +25164,18 @@ var ngShowDirective = ['$animate', function($animate) { */ var ngHideDirective = ['$animate', function($animate) { - return function(scope, element, attr) { - scope.$watch(attr.ngHide, function ngHideWatchAction(value){ - $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); - }); + return { + restrict: 'A', + multiElement: true, + link: function(scope, element, attr) { + scope.$watch(attr.ngHide, function ngHideWatchAction(value) { + // The comment inside of the ngShowDirective explains why we add and + // remove a temporary class for the show/hide animation + $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { + tempClasses: NG_HIDE_IN_PROGRESS_CLASS + }); + }); + } }; }]; @@ -21183,8 +25276,8 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) { * * * @scope - * @priority 800 - * @param {*} ngSwitch|on expression to match against ng-switch-when. + * @priority 1200 + * @param {*} ngSwitch|on expression to match against ng-switch-when. * On child elements add: * * * `ngSwitchWhen`: the case statement to match against. If match then this @@ -21201,7 +25294,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
    - selection={{selection}} + selection={{selection}}
    @@ -21282,37 +25375,39 @@ var ngSwitchDirective = ['$animate', function($animate) { var watchExpr = attr.ngSwitch || attr.on, selectedTranscludes = [], selectedElements = [], - previousElements = [], + previousLeaveAnimations = [], selectedScopes = []; + var spliceFactory = function(array, index) { + return function() { array.splice(index, 1); }; + }; + scope.$watch(watchExpr, function ngSwitchWatchAction(value) { var i, ii; - for (i = 0, ii = previousElements.length; i < ii; ++i) { - previousElements[i].remove(); + for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { + $animate.cancel(previousLeaveAnimations[i]); } - previousElements.length = 0; + previousLeaveAnimations.length = 0; for (i = 0, ii = selectedScopes.length; i < ii; ++i) { - var selected = selectedElements[i]; + var selected = getBlockNodes(selectedElements[i].clone); selectedScopes[i].$destroy(); - previousElements[i] = selected; - $animate.leave(selected, function() { - previousElements.splice(i, 1); - }); + var promise = previousLeaveAnimations[i] = $animate.leave(selected); + promise.then(spliceFactory(previousLeaveAnimations, i)); } selectedElements.length = 0; selectedScopes.length = 0; if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { - scope.$eval(attr.change); forEach(selectedTranscludes, function(selectedTransclude) { - var selectedScope = scope.$new(); - selectedScopes.push(selectedScope); - selectedTransclude.transclude(selectedScope, function(caseElement) { + selectedTransclude.transclude(function(caseElement, selectedScope) { + selectedScopes.push(selectedScope); var anchor = selectedTransclude.element; + caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); + var block = { clone: caseElement }; - selectedElements.push(caseElement); + selectedElements.push(block); $animate.enter(caseElement, anchor.parent(), anchor); }); }); @@ -21324,8 +25419,9 @@ var ngSwitchDirective = ['$animate', function($animate) { var ngSwitchWhenDirective = ngDirective({ transclude: 'element', - priority: 800, + priority: 1200, require: '^ngSwitch', + multiElement: true, link: function(scope, element, attrs, ctrl, $transclude) { ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); @@ -21334,8 +25430,9 @@ var ngSwitchWhenDirective = ngDirective({ var ngSwitchDefaultDirective = ngDirective({ transclude: 'element', - priority: 800, + priority: 1200, require: '^ngSwitch', + multiElement: true, link: function(scope, element, attr, ctrl, $transclude) { ctrl.cases['?'] = (ctrl.cases['?'] || []); ctrl.cases['?'].push({ transclude: $transclude, element: element }); @@ -21345,7 +25442,7 @@ var ngSwitchDefaultDirective = ngDirective({ /** * @ngdoc directive * @name ngTransclude - * @restrict AC + * @restrict EAC * * @description * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. @@ -21366,7 +25463,7 @@ var ngSwitchDefaultDirective = ngDirective({ scope: { title:'@' }, template: '
    ' + '
    {{title}}
    ' + - '
    ' + + '' + '
    ' }; }) @@ -21376,7 +25473,7 @@ var ngSwitchDefaultDirective = ngDirective({ }]);
    -
    +

    {{text}}
    @@ -21397,6 +25494,7 @@ var ngSwitchDefaultDirective = ngDirective({ * */ var ngTranscludeDirective = ngDirective({ + restrict: 'EAC', link: function($scope, $element, $attrs, controller, $transclude) { if (!$transclude) { throw minErr('ngTransclude')('orphan', @@ -21474,28 +25572,75 @@ var ngOptionsMinErr = minErr('ngOptions'); * * The `ngOptions` attribute can be used to dynamically generate a list of `
    "+e[1]+a.replace(le,"<$1>")+e[2];d.removeChild(d.firstChild);for(a=e[0];a--;)d=d.lastChild;a=0;for(e=d.childNodes.length;a=R?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function Na(b,a){var c=typeof b,d;"function"==c||"object"==c&&null!==b?"function"==typeof(d= -b.$$hashKey)?d=b.$$hashKey():d===u&&(d=b.$$hashKey=(a||ib)()):d=b;return c+":"+d}function db(b,a){if(a){var c=0;this.nextUid=function(){return++c}}r(b,this.put,this)}function qc(b){var a,c;"function"===typeof b?(a=b.$inject)||(a=[],b.length&&(c=b.toString().replace(oe,""),c=c.match(pe),r(c[1].split(qe),function(b){b.replace(re,function(b,c,d){a.push(d)})})),b.$inject=a):L(b)?(c=b.length-1,Ya(b[c],"fn"),a=b.slice(0,c)):Ya(b,"fn",!0);return a}function ec(b){function a(a){return function(b,c){if(T(b))r(b, -Yb(a));else return a(b,c)}}function c(a,b){Ea(a,"service");if(N(b)||L(b))b=n.instantiate(b);if(!b.$get)throw eb("pget",a);return l[a+h]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[],c,d,f,h;r(a,function(a){if(!m.get(a)){m.put(a,!0);try{if(G(a))for(c=$a(a),b=b.concat(e(c.requires)).concat(c._runBlocks),d=c._invokeQueue,f=0,h=d.length;f 4096 bytes)!"));else{if(k.cookie!== -ca)for(ca=k.cookie,d=ca.split("; "),M={},f=0;fm&&this.remove(q.key),b},get:function(a){if(m").parent()[0])});var f=O(a,b,a,c,d,e);ba(a,"ng-scope");return function(b,c,d,e){Db(b,"scope");var g=c?Oa.clone.call(a):a;r(d,function(a,b){g.data("$"+b+"Controller",a)});d=0;for(var k=g.length;darguments.length&& -(b=a,a=u);Ia&&(c=ca);return n(a,b,c)}var y,Q,B,M,C,P,ca={},ra;y=c===f?d:ha(d,new Ob(A(f),d.$attr));Q=y.$$element;if(K){var ue=/^\s*([@=&])(\??)\s*(\w*)\s*$/;P=e.$new(!0);!I||I!==K&&I!==K.$$originalDirective?Q.data("$isolateScopeNoTemplate",P):Q.data("$isolateScope",P);ba(Q,"ng-isolate-scope");r(K.scope,function(a,c){var d=a.match(ue)||[],f=d[3]||c,g="?"==d[2],d=d[1],k,l,n,q;P.$$isolateBindings[c]=d+f;switch(d){case "@":y.$observe(f,function(a){P[c]=a});y.$$observers[f].$$scope=e;y[f]&&(P[c]=b(y[f])(e)); -break;case "=":if(g&&!y[f])break;l=p(y[f]);q=l.literal?Ca:function(a,b){return a===b||a!==a&&b!==b};n=l.assign||function(){k=P[c]=l(e);throw ja("nonassign",y[f],K.name);};k=P[c]=l(e);P.$watch(function(){var a=l(e);q(a,P[c])||(q(a,k)?n(e,a=P[c]):P[c]=a);return k=a},null,l.literal);break;case "&":l=p(y[f]);P[c]=function(a){return l(e,a)};break;default:throw ja("iscp",K.name,c,a);}})}ra=n&&w;O&&r(O,function(a){var b={$scope:a===K||a.$$isolateScope?P:e,$element:Q,$attrs:y,$transclude:ra},c;C=a.controller; -"@"==C&&(C=y[a.name]);c=s(C,b);ca[a.name]=c;Ia||Q.data("$"+a.name+"Controller",c);a.controllerAs&&(b.$scope[a.controllerAs]=c)});g=0;for(B=k.length;gH.priority)break;if(V=H.scope)M=M||H,H.templateUrl||(fb("new/isolated scope",K,H,x),T(V)&&(K=H));z=H.name;!H.templateUrl&&H.controller&&(V=H.controller,O=O||{},fb("'"+z+"' controller",O[z],H,x),O[z]=H);if(V=H.transclude)F=!0,H.$$tlb|| -(fb("transclusion",ea,H,x),ea=H),"element"==V?(Ia=!0,y=H.priority,V=x,x=d.$$element=A(X.createComment(" "+z+": "+d[z]+" ")),c=x[0],ra(f,wa.call(V,0),c),S=B(V,e,y,g&&g.name,{nonTlbTranscludeDirective:ea})):(V=A(Kb(c)).contents(),x.empty(),S=B(V,e));if(H.template)if(E=!0,fb("template",I,H,x),I=H,V=N(H.template)?H.template(x,d):H.template,V=W(V),H.replace){g=H;V=Ib.test(V)?A($(V)):[];c=V[0];if(1!=V.length||1!==c.nodeType)throw ja("tplrt",z,"");ra(f,x,c);sa={$attr:{}};V=ca(c,[],sa);var Z=a.splice(Ha+ -1,a.length-(Ha+1));K&&D(V);a=a.concat(V).concat(Z);v(d,sa);sa=a.length}else x.html(V);if(H.templateUrl)E=!0,fb("template",I,H,x),I=H,H.replace&&(g=H),J=te(a.splice(Ha,a.length-Ha),x,d,f,F&&S,k,q,{controllerDirectives:O,newIsolateScopeDirective:K,templateDirective:I,nonTlbTranscludeDirective:ea}),sa=a.length;else if(H.compile)try{R=H.compile(x,d,S),N(R)?w(null,R,U,Y):R&&w(R.pre,R.post,U,Y)}catch(ve){l(ve,ia(x))}H.terminal&&(J.terminal=!0,y=Math.max(y,H.priority))}J.scope=M&&!0===M.scope;J.transcludeOnThisElement= -F;J.templateOnThisElement=E;J.transclude=S;n.hasElementTranscludeDirective=Ia;return J}function D(a){for(var b=0,c=a.length;bq.priority)&&-1!=q.restrict.indexOf(f)&&(m&&(q=$b(q,{$$start:m,$$end:n})),b.push(q),p=q)}catch(y){l(y)}}return p}function v(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;r(a,function(d,e){"$"!= -e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});r(b,function(b,f){"class"==f?(ba(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==f?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==f.charAt(0)||a.hasOwnProperty(f)||(a[f]=b,d[f]=c[f])})}function te(a,b,c,d,e,f,g,k){var p=[],l,m,w=b[0],s=a.shift(),y=E({},s,{templateUrl:null,transclude:null,replace:null,$$originalDirective:s}),J=N(s.templateUrl)?s.templateUrl(b,c):s.templateUrl; -b.empty();n.get(t.getTrustedResourceUrl(J),{cache:q}).success(function(q){var n,t;q=W(q);if(s.replace){q=Ib.test(q)?A($(q)):[];n=q[0];if(1!=q.length||1!==n.nodeType)throw ja("tplrt",s.name,J);q={$attr:{}};ra(d,b,n);var B=ca(n,[],q);T(s.scope)&&D(B);a=B.concat(a);v(c,q)}else n=w,b.html(q);a.unshift(y);l=I(a,n,c,e,b,s,f,g,k);r(d,function(a,c){a==n&&(d[c]=b[0])});for(m=O(b[0].childNodes,e);p.length;){q=p.shift();t=p.shift();var K=p.shift(),C=p.shift(),B=b[0];if(t!==w){var P=t.className;k.hasElementTranscludeDirective&& -s.replace||(B=Kb(n));ra(K,A(t),B);ba(A(B),P)}t=l.transcludeOnThisElement?M(q,l.transclude,C):C;l(m,q,B,d,t)}p=null}).error(function(a,b,c,d){throw ja("tpload",d.url);});return function(a,b,c,d,e){a=e;p?(p.push(b),p.push(c),p.push(d),p.push(a)):(l.transcludeOnThisElement&&(a=M(b,l.transclude,e)),l(m,b,c,d,a))}}function F(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.namea.status?d:n.reject(d)}var c={method:"get",transformRequest:e.transformRequest,transformResponse:e.transformResponse},d=function(a){var b=e.headers,c=E({},a.headers),d,f,b=E({},b.common,b[x(a.method)]); -a:for(d in b){a=x(d);for(f in c)if(x(f)===a)continue a;c[d]=b[d]}(function(a){var b;r(a,function(c,d){N(c)&&(b=c(),null!=b?a[d]=b:delete a[d])})})(c);return c}(a);E(c,a);c.headers=d;c.method=La(c.method);var f=[function(a){d=a.headers;var c=vc(a.data,uc(d),a.transformRequest);F(c)&&r(d,function(a,b){"content-type"===x(b)&&delete d[b]});F(a.withCredentials)&&!F(e.withCredentials)&&(a.withCredentials=e.withCredentials);return s(a,c,d).then(b,b)},u],g=n.when(c);for(r(t,function(a){(a.request||a.requestError)&& -f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var h=f.shift(),g=g.then(a,h)}g.success=function(a){g.then(function(b){a(b.data,b.status,b.headers,c)});return g};g.error=function(a){g.then(null,function(b){a(b.data,b.status,b.headers,c)});return g};return g}function s(c,f,g){function m(a,b,c,e){C&&(200<=a&&300>a?C.put(A,[a,b,tc(c),e]):C.remove(A));q(b,a,c,e);d.$$phase||d.$apply()}function q(a,b,d,e){b=Math.max(b,0);(200<= -b&&300>b?t.resolve:t.reject)({data:a,status:b,headers:uc(d),config:c,statusText:e})}function s(){var a=Ta(p.pendingRequests,c);-1!==a&&p.pendingRequests.splice(a,1)}var t=n.defer(),r=t.promise,C,I,A=J(c.url,c.params);p.pendingRequests.push(c);r.then(s,s);!c.cache&&!e.cache||(!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method)||(C=T(c.cache)?c.cache:T(e.cache)?e.cache:w);if(C)if(I=C.get(A),D(I)){if(I&&N(I.then))return I.then(s,s),I;L(I)?q(I[1],I[0],ha(I[2]),I[3]):q(I,200,{},"OK")}else C.put(A,r);F(I)&& -((I=Pb(c.url)?b.cookies()[c.xsrfCookieName||e.xsrfCookieName]:u)&&(g[c.xsrfHeaderName||e.xsrfHeaderName]=I),a(c.method,A,f,m,g,c.timeout,c.withCredentials,c.responseType));return r}function J(a,b){if(!b)return a;var c=[];Sc(b,function(a,b){null===a||F(a)||(L(a)||(a=[a]),r(a,function(a){T(a)&&(a=va(a)?a.toISOString():oa(a));c.push(Da(b)+"="+Da(a))}))});0=R&&(!b.match(/^(get|post|head|put|delete|options)$/i)|| -!W.XMLHttpRequest))return new W.ActiveXObject("Microsoft.XMLHTTP");if(W.XMLHttpRequest)return new W.XMLHttpRequest;throw z("$httpBackend")("noxhr");}function Ud(){this.$get=["$browser","$window","$document",function(b,a,c){return ye(b,xe,b.defer,a.angular.callbacks,c[0])}]}function ye(b,a,c,d,e){function f(a,b,c){var f=e.createElement("script"),g=null;f.type="text/javascript";f.src=a;f.async=!0;g=function(a){bb(f,"load",g);bb(f,"error",g);e.body.removeChild(f);f=null;var h=-1,s="unknown";a&&("load"!== -a.type||d[b].called||(a={type:"error"}),s=a.type,h="error"===a.type?404:200);c&&c(h,s)};sb(f,"load",g);sb(f,"error",g);8>=R&&(f.onreadystatechange=function(){G(f.readyState)&&/loaded|complete/.test(f.readyState)&&(f.onreadystatechange=null,g({type:"load"}))});e.body.appendChild(f);return g}var g=-1;return function(e,k,m,l,n,q,p,s){function J(){t=g;K&&K();B&&B.abort()}function w(a,d,e,f,g){O&&c.cancel(O);K=B=null;0===d&&(d=e?200:"file"==xa(k).protocol?404:0);a(1223===d?204:d,e,f,g||"");b.$$completeOutstandingRequest(v)} -var t;b.$$incOutstandingRequestCount();k=k||b.url();if("jsonp"==x(e)){var y="_"+(d.counter++).toString(36);d[y]=function(a){d[y].data=a;d[y].called=!0};var K=f(k.replace("JSON_CALLBACK","angular.callbacks."+y),y,function(a,b){w(l,a,d[y].data,"",b);d[y]=v})}else{var B=a(e);B.open(e,k,!0);r(n,function(a,b){D(a)&&B.setRequestHeader(b,a)});B.onreadystatechange=function(){if(B&&4==B.readyState){var a=null,b=null,c="";t!==g&&(a=B.getAllResponseHeaders(),b="response"in B?B.response:B.responseText);t===g&& -10>R||(c=B.statusText);w(l,t||B.status,b,a,c)}};p&&(B.withCredentials=!0);if(s)try{B.responseType=s}catch(ba){if("json"!==s)throw ba;}B.send(m||null)}if(0=h&&(n.resolve(p),l(q.$$intervalId),delete e[q.$$intervalId]);s||b.$apply()},g);e[q.$$intervalId]=n;return q}var e={};d.cancel= -function(b){return b&&b.$$intervalId in e?(e[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId),delete e[b.$$intervalId],!0):!1};return d}]}function ad(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "), -SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function Qb(b){b=b.split("/");for(var a=b.length;a--;)b[a]= -mb(b[a]);return b.join("/")}function xc(b,a,c){b=xa(b,c);a.$$protocol=b.protocol;a.$$host=b.hostname;a.$$port=U(b.port)||ze[b.protocol]||null}function yc(b,a,c){var d="/"!==b.charAt(0);d&&(b="/"+b);b=xa(b,c);a.$$path=decodeURIComponent(d&&"/"===b.pathname.charAt(0)?b.pathname.substring(1):b.pathname);a.$$search=cc(b.search);a.$$hash=decodeURIComponent(b.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function ta(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ga(b){var a= -b.indexOf("#");return-1==a?b:b.substr(0,a)}function Rb(b){return b.substr(0,Ga(b).lastIndexOf("/")+1)}function zc(b,a){this.$$html5=!0;a=a||"";var c=Rb(b);xc(b,this,b);this.$$parse=function(a){var e=ta(c,a);if(!G(e))throw Sb("ipthprfx",a,c);yc(e,this,b);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Cb(this.$$search),b=this.$$hash?"#"+mb(this.$$hash):"";this.$$url=Qb(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$parseLinkUrl=function(d, -e){var f,g;(f=ta(b,d))!==u?(g=f,g=(f=ta(a,f))!==u?c+(ta("/",f)||f):b+g):(f=ta(c,d))!==u?g=c+f:c==d+"/"&&(g=c);g&&this.$$parse(g);return!!g}}function Tb(b,a){var c=Rb(b);xc(b,this,b);this.$$parse=function(d){var e=ta(b,d)||ta(c,d),e="#"==e.charAt(0)?ta(a,e):this.$$html5?e:"";if(!G(e))throw Sb("ihshprfx",d,a);yc(e,this,b);d=this.$$path;var f=/^\/[A-Z]:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));f.exec(e)||(d=(e=f.exec(d))?e[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=Cb(this.$$search), -e=this.$$hash?"#"+mb(this.$$hash):"";this.$$url=Qb(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ga(b)==Ga(a)?(this.$$parse(a),!0):!1}}function Ac(b,a){this.$$html5=!0;Tb.apply(this,arguments);var c=Rb(b);this.$$parseLinkUrl=function(d,e){var f,g;b==Ga(d)?f=d:(g=ta(c,d))?f=b+a+g:c===d+"/"&&(f=c);f&&this.$$parse(f);return!!f};this.$$compose=function(){var c=Cb(this.$$search),e=this.$$hash?"#"+mb(this.$$hash):"";this.$$url=Qb(this.$$path)+ -(c?"?"+c:"")+e;this.$$absUrl=b+a+this.$$url}}function tb(b){return function(){return this[b]}}function Bc(b,a){return function(c){if(F(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Vd(){var b="",a=!1;this.hashPrefix=function(a){return D(a)?(b=a,this):b};this.html5Mode=function(b){return D(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,f){function g(a){c.$broadcast("$locationChangeSuccess",h.absUrl(),a)}var h,k=d.baseHref(),m=d.url(); -a?(k=m.substring(0,m.indexOf("/",m.indexOf("//")+2))+(k||"/"),e=e.history?zc:Ac):(k=Ga(m),e=Tb);h=new e(k,"#"+b);h.$$parseLinkUrl(m,m);var l=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(!a.ctrlKey&&!a.metaKey&&2!=a.which){for(var b=A(a.target);"a"!==x(b[0].nodeName);)if(b[0]===f[0]||!(b=b.parent())[0])return;var e=b.prop("href"),g=b.attr("href")||b.attr("xlink:href");T(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=xa(e.animVal).href);l.test(e)||(!e||(b.attr("target")||a.isDefaultPrevented())|| -!h.$$parseLinkUrl(e,g))||(a.preventDefault(),h.absUrl()!=d.url()&&(c.$apply(),W.angular["ff-684208-preventDefault"]=!0))}});h.absUrl()!=m&&d.url(h.absUrl(),!0);d.onUrlChange(function(a){h.absUrl()!=a&&(c.$evalAsync(function(){var b=h.absUrl();h.$$parse(a);c.$broadcast("$locationChangeStart",a,b).defaultPrevented?(h.$$parse(b),d.url(b)):g(b)}),c.$$phase||c.$digest())});var n=0;c.$watch(function(){var a=d.url(),b=h.$$replace;n&&a==h.absUrl()||(n++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart", -h.absUrl(),a).defaultPrevented?h.$$parse(a):(d.url(h.absUrl(),b),g(a))}));h.$$replace=!1;return n});return h}]}function Wd(){var b=!0,a=this;this.debugEnabled=function(a){return D(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||v;a=!1;try{a=!!e.apply}catch(k){}return a? -function(){var a=[];r(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function ka(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw la("isecfld",a);return b}function ma(b,a){if(b){if(b.constructor===b)throw la("isecfn",a);if(b.document&& -b.location&&b.alert&&b.setInterval)throw la("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw la("isecdom",a);if(b===Object)throw la("isecobj",a);}return b}function ub(b,a,c,d,e){ma(b,d);e=e||{};a=a.split(".");for(var f,g=0;1g?Cc(f[0],f[1],f[2],f[3],f[4],c,a):function(b,d){var e=0,h;do h=Cc(f[e++],f[e++],f[e++],f[e++],f[e++],c,a)(b,d),d=u,b=h;while(eb.length?a=b.length:a<-b.length&&(a=-b.length);0a||37<=a&&40>=a)||p()});if(e.hasEvent("paste"))a.on("paste cut",p)}a.on("change",n);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)};var s=c.ngPattern;s&&((e=s.match(/^\/(.*)\/([gim]*)$/))? -(s=RegExp(e[1],e[2]),e=function(a){return ua(d,"pattern",d.$isEmpty(a)||s.test(a),a)}):e=function(c){var e=b.$eval(s);if(!e||!e.test)throw z("ngPattern")("noregexp",s,e,ia(a));return ua(d,"pattern",d.$isEmpty(c)||e.test(c),c)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var r=U(c.ngMinlength);e=function(a){return ua(d,"minlength",d.$isEmpty(a)||a.length>=r,a)};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var w=U(c.ngMaxlength);e=function(a){return ua(d,"maxlength",d.$isEmpty(a)|| -a.length<=w,a)};d.$parsers.push(e);d.$formatters.push(e)}}function Wb(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;dR?function(b){b=b.nodeName?b:b[0];return b.scopeName&&"HTML"!=b.scopeName?La(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var Za=function(){if(D(Za.isActive_))return Za.isActive_;var b=!(!X.querySelector("[ng-csp]")&& -!X.querySelector("[data-ng-csp]"));if(!b)try{new Function("")}catch(a){b=!0}return Za.isActive_=b},Xc=/[A-Z]/g,$c={full:"1.2.28",major:1,minor:2,dot:28,codeName:"finnish-disembarkation"};S.expando="ng339";var cb=S.cache={},me=1,sb=W.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},bb=W.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)};S._data=function(b){return this.cache[b[this.expando]]|| -{}};var he=/([\:\-\_]+(.))/g,ie=/^moz([A-Z])/,Hb=z("jqLite"),je=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,Ib=/<|&#?\w+;/,ke=/<([\w:]+)/,le=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,da={option:[1,'"],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};da.optgroup=da.option;da.tbody=da.tfoot=da.colgroup= -da.caption=da.thead;da.th=da.td;var Oa=S.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===X.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),S(W).on("load",a))},toString:function(){var b=[];r(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?A(this[b]):A(this[this.length+b])},length:0,push:Pe,sort:[].sort,splice:[].splice},rb={};r("multiple selected checked disabled readOnly required open".split(" "),function(b){rb[x(b)]=b}); -var pc={};r("input select option textarea button form details".split(" "),function(b){pc[La(b)]=!0});r({data:Mb,removeData:Lb},function(b,a){S[a]=b});r({data:Mb,inheritedData:qb,scope:function(b){return A.data(b,"$scope")||qb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return A.data(b,"$isolateScope")||A.data(b,"$isolateScopeNoTemplate")},controller:mc,injector:function(b){return qb(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Nb,css:function(b, -a,c){a=ab(a);if(D(c))b.style[a]=c;else{var d;8>=R&&(d=b.currentStyle&&b.currentStyle[a],""===d&&(d="auto"));d=d||b.style[a];8>=R&&(d=""===d?u:d);return d}},attr:function(b,a,c){var d=x(a);if(rb[d])if(D(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||v).specified?d:u;else if(D(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?u:b},prop:function(b,a,c){if(D(c))b[a]=c;else return b[a]},text:function(){function b(b, -d){var e=a[b.nodeType];if(F(d))return e?b[e]:"";b[e]=d}var a=[];9>R?(a[1]="innerText",a[3]="nodeValue"):a[1]=a[3]="textContent";b.$dv="";return b}(),val:function(b,a){if(F(a)){if("SELECT"===Pa(b)&&b.multiple){var c=[];r(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(F(a))return b.innerHTML;for(var c=0,d=b.childNodes;c":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a, -c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Ue={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Ub=function(a){this.options=a};Ub.prototype={constructor:Ub,lex:function(a){this.text=a;this.index=0;this.ch=u;this.lastCh=":";for(this.tokens=[];this.index=a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)}, -throwError:function(a,c,d){d=d||this.index;c=D(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw la("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index","<=",">="))a=this.binaryFn(a,c.fn,this.relational());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.fn,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.fn,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(gb.ZERO,a.fn, -this.unary()):(a=this.expect("!"))?this.unaryFn(a.fn,this.unary()):this.primary()},fieldAccess:function(a){var c=this,d=this.expect().text,e=Dc(d,this.options,this.text);return E(function(c,d,h){return e(h||a(c,d))},{assign:function(e,g,h){(h=a(e,h))||a.assign(e,h={});return ub(h,d,g,c.text,c.options)}})},objectIndex:function(a){var c=this,d=this.expression();this.consume("]");return E(function(e,f){var g=a(e,f),h=d(e,f),k;ka(h,c.text);if(!g)return u;(g=ma(g[h],c.text))&&(g.then&&c.options.unwrapPromises)&& -(k=g,"$$v"in g||(k.$$v=u,k.then(function(a){k.$$v=a})),g=g.$$v);return g},{assign:function(e,f,g){var h=ka(d(e,g),c.text);(g=ma(a(e,g),c.text))||a.assign(e,g={});return g[h]=f}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression());while(this.expect(","))}this.consume(")");var e=this;return function(f,g){for(var h=[],k=c?c(f,g):f,m=0;ma.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Vb(Math[0< -a?"floor":"ceil"](a/60),2)+Vb(Math.abs(a%60),2))}},Le=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,Ke=/^\-?\d+$/;Ic.$inject=["$locale"];var Ie=aa(x),Je=aa(La);Kc.$inject=["$parse"];var cd=aa({restrict:"E",compile:function(a,c){8>=R&&(c.href||c.name||c.$set("href",""),a.append(X.createComment("IE fix")));if(!c.href&&!c.xlinkHref&&!c.name)return function(a,c){var f="[object SVGAnimatedString]"===Ba.call(c.prop("href"))?"xlink:href":"href";c.on("click",function(a){c.attr(f)|| -a.preventDefault()})}}}),Fb={};r(rb,function(a,c){if("multiple"!=a){var d=qa("ng-"+c);Fb[d]=function(){return{priority:100,link:function(a,f,g){a.$watch(g[d],function(a){g.$set(c,!!a)})}}}}});r(["src","srcset","href"],function(a){var c=qa("ng-"+a);Fb[c]=function(){return{priority:99,link:function(d,e,f){var g=a,h=a;"href"===a&&"[object SVGAnimatedString]"===Ba.call(e.prop("href"))&&(h="xlinkHref",f.$attr[h]="xlink:href",g=null);f.$observe(c,function(c){c?(f.$set(h,c),R&&g&&e.prop(g,f[h])):"href"=== -a&&f.$set(h,null)})}}}});var yb={$addControl:v,$removeControl:v,$setValidity:v,$setDirty:v,$setPristine:v};Nc.$inject=["$element","$attrs","$scope","$animate"];var Qc=function(a){return["$timeout",function(c){return{name:"form",restrict:a?"EAC":"E",controller:Nc,compile:function(){return{pre:function(a,e,f,g){if(!f.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};sb(e[0],"submit",h);e.on("$destroy",function(){c(function(){bb(e[0],"submit",h)},0,!1)})}var k=e.parent().controller("form"), -m=f.name||f.ngForm;m&&ub(a,m,g,m);if(k)e.on("$destroy",function(){k.$removeControl(g);m&&ub(a,m,u,m);E(g,yb)})}}}}}]},dd=Qc(),qd=Qc(!0),Ve=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,We=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,Xe=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,Rc={text:Ab,number:function(a,c,d,e,f,g){Ab(a,c,d,e,f,g);e.$parsers.push(function(a){var c=e.$isEmpty(a);if(c||Xe.test(a))return e.$setValidity("number", -!0),""===a?null:c?a:parseFloat(a);e.$setValidity("number",!1);return u});Ne(e,"number",Ye,null,e.$$validityState);e.$formatters.push(function(a){return e.$isEmpty(a)?"":""+a});d.min&&(a=function(a){var c=parseFloat(d.min);return ua(e,"min",e.$isEmpty(a)||a>=c,a)},e.$parsers.push(a),e.$formatters.push(a));d.max&&(a=function(a){var c=parseFloat(d.max);return ua(e,"max",e.$isEmpty(a)||a<=c,a)},e.$parsers.push(a),e.$formatters.push(a));e.$formatters.push(function(a){return ua(e,"number",e.$isEmpty(a)|| -jb(a),a)})},url:function(a,c,d,e,f,g){Ab(a,c,d,e,f,g);a=function(a){return ua(e,"url",e.$isEmpty(a)||Ve.test(a),a)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,f,g){Ab(a,c,d,e,f,g);a=function(a){return ua(e,"email",e.$isEmpty(a)||We.test(a),a)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){F(d.name)&&c.attr("name",ib());c.on("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue}; -d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var f=d.ngTrueValue,g=d.ngFalseValue;G(f)||(f=!0);G(g)||(g=!1);c.on("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return a!==f};e.$formatters.push(function(a){return a===f});e.$parsers.push(function(a){return a?f:g})},hidden:v,button:v,submit:v,reset:v,file:v},Ye=["badInput"],hc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel", -link:function(d,e,f,g){g&&(Rc[x(f.type)]||Rc.text)(d,e,f,g,c,a)}}}],wb="ng-valid",xb="ng-invalid",Ra="ng-pristine",zb="ng-dirty",Ze=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate",function(a,c,d,e,f,g){function h(a,c){c=c?"-"+nb(c,"-"):"";g.removeClass(e,(a?xb:wb)+c);g.addClass(e,(a?wb:xb)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name= -d.name;var k=f(d.ngModel),m=k.assign;if(!m)throw z("ngModel")("nonassign",d.ngModel,ia(e));this.$render=v;this.$isEmpty=function(a){return F(a)||""===a||null===a||a!==a};var l=e.inheritedData("$formController")||yb,n=0,q=this.$error={};e.addClass(Ra);h(!0);this.$setValidity=function(a,c){q[a]!==!c&&(c?(q[a]&&n--,n||(h(!0),this.$valid=!0,this.$invalid=!1)):(h(!1),this.$invalid=!0,this.$valid=!1,n++),q[a]=!c,h(c,a),l.$setValidity(a,c,this))};this.$setPristine=function(){this.$dirty=!1;this.$pristine= -!0;g.removeClass(e,zb);g.addClass(e,Ra)};this.$setViewValue=function(d){this.$viewValue=d;this.$pristine&&(this.$dirty=!0,this.$pristine=!1,g.removeClass(e,Ra),g.addClass(e,zb),l.$setDirty());r(this.$parsers,function(a){d=a(d)});this.$modelValue!==d&&(this.$modelValue=d,m(a,d),r(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}}))};var p=this;a.$watch(function(){var c=k(a);if(p.$modelValue!==c){var d=p.$formatters,e=d.length;for(p.$modelValue=c;e--;)c=d[e](c);p.$viewValue!==c&&(p.$viewValue= -c,p.$render())}return c})}],Fd=function(){return{require:["ngModel","^?form"],controller:Ze,link:function(a,c,d,e){var f=e[0],g=e[1]||yb;g.$addControl(f);a.$on("$destroy",function(){g.$removeControl(f)})}}},Hd=aa({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),ic=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var f=function(a){if(d.required&&e.$isEmpty(a))e.$setValidity("required",!1);else return e.$setValidity("required", -!0),a};e.$formatters.push(f);e.$parsers.unshift(f);d.$observe("required",function(){f(e.$viewValue)})}}}},Gd=function(){return{require:"ngModel",link:function(a,c,d,e){var f=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){if(!F(a)){var c=[];a&&r(a.split(f),function(a){a&&c.push($(a))});return c}});e.$formatters.push(function(a){return L(a)?a.join(", "):u});e.$isEmpty=function(a){return!a||!a.length}}}},$e=/^(true|false|\d+)$/,Id=function(){return{priority:100, -compile:function(a,c){return $e.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},id=Aa({compile:function(a){a.addClass("ng-binding");return function(a,d,e){d.data("$binding",e.ngBind);a.$watch(e.ngBind,function(a){d.text(a==u?"":a)})}}}),kd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}], -jd=["$sce","$parse",function(a,c){return{compile:function(d){d.addClass("ng-binding");return function(d,f,g){f.data("$binding",g.ngBindHtml);var h=c(g.ngBindHtml);d.$watch(function(){return(h(d)||"").toString()},function(c){f.html(a.getTrustedHtml(h(d))||"")})}}}}],ld=Wb("",!0),nd=Wb("Odd",0),md=Wb("Even",1),od=Aa({compile:function(a,c){c.$set("ngCloak",u);a.removeClass("ng-cloak")}}),pd=[function(){return{scope:!0,controller:"@",priority:500}}],jc={},af={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "), -function(a){var c=qa("ng-"+a);jc[c]=["$parse","$rootScope",function(d,e){return{compile:function(f,g){var h=d(g[c],!0);return function(c,d){d.on(a,function(d){var f=function(){h(c,{$event:d})};af[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var sd=["$animate",function(a){return{transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,k,m;c.$watch(e.ngIf,function(f){Wa(f)?k||(k=c.$new(),g(k,function(c){c[c.length++]=X.createComment(" end ngIf: "+e.ngIf+ -" ");h={clone:c};a.enter(c,d.parent(),d)})):(m&&(m.remove(),m=null),k&&(k.$destroy(),k=null),h&&(m=Eb(h.clone),a.leave(m,function(){m=null}),h=null))})}}}],td=["$http","$templateCache","$anchorScroll","$animate","$sce",function(a,c,d,e,f){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:Xa.noop,compile:function(g,h){var k=h.ngInclude||h.src,m=h.onload||"",l=h.autoscroll;return function(g,h,p,r,J){var w=0,t,y,u,B=function(){y&&(y.remove(),y=null);t&&(t.$destroy(),t=null); -u&&(e.leave(u,function(){y=null}),y=u,u=null)};g.$watch(f.parseAsResourceUrl(k),function(f){var k=function(){!D(l)||l&&!g.$eval(l)||d()},p=++w;f?(a.get(f,{cache:c}).success(function(a){if(p===w){var c=g.$new();r.template=a;a=J(c,function(a){B();e.enter(a,null,h,k)});t=c;u=a;t.$emit("$includeContentLoaded");g.$eval(m)}}).error(function(){p===w&&B()}),g.$emit("$includeContentRequested")):(B(),r.template=null)})}}}}],Jd=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude", -link:function(c,d,e,f){d.html(f.template);a(d.contents())(c)}}}],ud=Aa({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),vd=Aa({terminal:!0,priority:1E3}),wd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,f,g){var h=g.count,k=g.$attr.when&&f.attr(g.$attr.when),m=g.offset||0,l=e.$eval(k)||{},n={},q=c.startSymbol(),p=c.endSymbol(),s=/^when(Minus)?(.+)$/;r(g,function(a,c){s.test(c)&&(l[x(c.replace("when","").replace("Minus","-"))]= -f.attr(g.$attr[c]))});r(l,function(a,e){n[e]=c(a.replace(d,q+h+"-"+m+p))});e.$watch(function(){var c=parseFloat(e.$eval(h));if(isNaN(c))return"";c in l||(c=a.pluralCat(c-m));return n[c](e,f,!0)},function(a){f.text(a)})}}}],xd=["$parse","$animate",function(a,c){var d=z("ngRepeat");return{transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,link:function(e,f,g,h,k){var m=g.ngRepeat,l=m.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/),n,q,p,s,u,w,t={$id:Na};if(!l)throw d("iexp", -m);g=l[1];h=l[2];(l=l[3])?(n=a(l),q=function(a,c,d){w&&(t[w]=a);t[u]=c;t.$index=d;return n(e,t)}):(p=function(a,c){return Na(c)},s=function(a){return a});l=g.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!l)throw d("iidexp",g);u=l[3]||l[1];w=l[2];var y={};e.$watchCollection(h,function(a){var g,h,l=f[0],n,t={},D,C,I,x,G,v,z,F=[];if(Sa(a))v=a,G=q||p;else{G=q||s;v=[];for(I in a)a.hasOwnProperty(I)&&"$"!=I.charAt(0)&&v.push(I);v.sort()}D=v.length;h=F.length=v.length;for(g=0;gC;)d=u.pop(),q.removeOption(d.label),d.element.remove()}for(;B.length>Q;)B.pop()[0].element.remove()}var k;if(!(k=s.match(d)))throw bf("iexp",s,ia(f));var l=c(k[2]||k[1]), -m=k[4]||k[6],n=k[5],r=c(k[3]||""),x=c(k[2]?k[1]:m),A=c(k[7]),w=k[8]?c(k[8]):null,B=[[{element:f,label:""}]];z&&(a(z)(e),z.removeClass("ng-scope"),z.remove());f.empty();f.on("change",function(){e.$apply(function(){var a,c=A(e)||[],d={},k,l,q,r,s,t,v;if(p)for(l=[],r=0,t=B.length;r@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}.ng-animate-block-transitions{transition:0s all!important;-webkit-transition:0s all!important;}.ng-hide-add-active,.ng-hide-remove{display:block!important;}'); +(function(R,W,u){'use strict';function S(b){return function(){var a=arguments[0],c;c="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.3.20/"+(b?b+"/":"")+a;for(a=1;a").append(b).html();try{return b[0].nodeType===bb?K(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+K(b)})}catch(d){return K(c)}}function qc(b){try{return decodeURIComponent(b)}catch(a){}} +function rc(b){var a={},c,d;r((b||"").split("&"),function(b){b&&(c=b.replace(/\+/g,"%20").split("="),d=qc(c[0]),y(d)&&(b=y(c[1])?qc(c[1]):!0,sc.call(a,d)?H(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Pb(b){var a=[];r(b,function(b,d){H(b)?r(b,function(b){a.push(Ea(d,!0)+(!0===b?"":"="+Ea(b,!0)))}):a.push(Ea(d,!0)+(!0===b?"":"="+Ea(b,!0)))});return a.length?a.join("&"):""}function sb(b){return Ea(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Ea(b,a){return encodeURIComponent(b).replace(/%40/gi, +"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function Id(b,a){var c,d,e=tb.length;b=B(b);for(d=0;d/,">"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);a.unshift("ng");d=cb(a,c.strictDi);d.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector", +d);c(b)(a)})}]);return d},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;R&&e.test(R.name)&&(c.debugInfoEnabled=!0,R.name=R.name.replace(e,""));if(R&&!f.test(R.name))return d();R.name=R.name.replace(f,"");ca.resumeBootstrap=function(b){r(b,function(b){a.push(b)});return d()};z(ca.resumeDeferredBootstrap)&&ca.resumeDeferredBootstrap()}function Kd(){R.name="NG_ENABLE_DEBUG_INFO!"+R.name;R.location.reload()}function Ld(b){b=ca.element(b).injector();if(!b)throw Ja("test");return b.get("$$testability")} +function uc(b,a){a=a||"_";return b.replace(Md,function(b,d){return(d?a:"")+b.toLowerCase()})}function Nd(){var b;vc||((ta=R.jQuery)&&ta.fn.on?(B=ta,w(ta.fn,{scope:Ka.scope,isolateScope:Ka.isolateScope,controller:Ka.controller,injector:Ka.injector,inheritedData:Ka.inheritedData}),b=ta.cleanData,ta.cleanData=function(a){var c;if(Qb)Qb=!1;else for(var d=0,e;null!=(e=a[d]);d++)(c=ta._data(e,"events"))&&c.$destroy&&ta(e).triggerHandler("$destroy");b(a)}):B=T,ca.element=B,vc=!0)}function Rb(b,a,c){if(!b)throw Ja("areq", +a||"?",c||"required");return b}function La(b,a,c){c&&H(b)&&(b=b[b.length-1]);Rb(z(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function Ma(b,a){if("hasOwnProperty"===b)throw Ja("badname",a);}function wc(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g")+d[2];for(d=d[0];d--;)c=c.lastChild;f=Za(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";r(f,function(a){e.appendChild(a)}); +return e}function T(b){if(b instanceof T)return b;var a;x(b)&&(b=N(b),a=!0);if(!(this instanceof T)){if(a&&"<"!=b.charAt(0))throw Tb("nosel");return new T(b)}if(a){a=W;var c;b=(c=gf.exec(b))?[a.createElement(c[1])]:(c=Gc(b,a))?c.childNodes:[]}Hc(this,b)}function Ub(b){return b.cloneNode(!0)}function xb(b,a){a||yb(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;d 4096 bytes)!"));else{if(p.cookie!==y)for(y=p.cookie,d=y.split("; "),fa={},f=0;fk&&this.remove(q.key),b},get:function(a){if(k").parent()[0])});var f=O(a,b,a,c,d,e);G.$$addScopeClass(a); +var g=null;return function(b,c,d){Rb(b,"scope");d=d||{};var e=d.parentBoundTranscludeFn,h=d.transcludeControllers;d=d.futureParentElement;e&&e.$$boundTransclude&&(e=e.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==wa(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?B(Xb(g,B("
    ").append(a).html())):c?Ka.clone.call(a):a;if(h)for(var l in h)d.data("$"+l+"Controller",h[l].instance);G.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,e);return d}}function O(a,b,c,d,e,f){function g(a, +c,d,e){var f,l,k,q,p,s,t;if(m)for(t=Array(c.length),q=0;qJ.priority)break;if(V=J.scope)J.templateUrl||(L(V)?(Oa("new/isolated scope",Q||C,J,w),Q=J):Oa("new/isolated scope",Q,J,w)),C=C||J;da=J.name;!J.templateUrl&&J.controller&&(V=J.controller,O=O||{},Oa("'"+da+"' controller",O[da],J,w),O[da]=J);if(V=J.transclude)la=!0,J.$$tlb||(Oa("transclusion",fa,J,w),fa=J),"element"==V?(A=!0,E=J.priority,V=w,w=e.$$element=B(W.createComment(" "+da+": "+ +e[da]+" ")),d=w[0],T(g,$a.call(V,0),d),hb=G(V,f,E,l&&l.name,{nonTlbTranscludeDirective:fa})):(V=B(Ub(d)).contents(),w.empty(),hb=G(V,f));if(J.template)if(D=!0,Oa("template",na,J,w),na=J,V=z(J.template)?J.template(w,e):J.template,V=Sc(V),J.replace){l=J;V=Sb.test(V)?Tc(Xb(J.templateNamespace,N(V))):[];d=V[0];if(1!=V.length||d.nodeType!==qa)throw ma("tplrt",da,"");T(g,w,d);R={$attr:{}};V=X(d,[],R);var aa=a.splice(K+1,a.length-(K+1));Q&&y(V);a=a.concat(V).concat(aa);S(e,R);R=a.length}else w.html(V);if(J.templateUrl)D= +!0,Oa("template",na,J,w),na=J,J.replace&&(l=J),I=of(a.splice(K,a.length-K),w,e,g,la&&hb,k,p,{controllerDirectives:O,newIsolateScopeDirective:Q,templateDirective:na,nonTlbTranscludeDirective:fa}),R=a.length;else if(J.compile)try{za=J.compile(w,e,hb),z(za)?s(null,za,Pa,U):za&&s(za.pre,za.post,Pa,U)}catch(pf){c(pf,xa(w))}J.terminal&&(I.terminal=!0,E=Math.max(E,J.priority))}I.scope=C&&!0===C.scope;I.transcludeOnThisElement=la;I.elementTranscludeOnThisElement=A;I.templateOnThisElement=D;I.transclude=hb; +m.hasElementTranscludeDirective=A;return I}function y(a){for(var b=0,c=a.length;bq.priority)&&-1!=q.restrict.indexOf(f)&&(h&&(q=Ob(q,{$$start:h,$$end:k})),b.push(q),l=q)}catch(I){c(I)}}return l}function D(b){if(d.hasOwnProperty(b))for(var c=a.get(b+"Directive"),e=0,f=c.length;e"+b+"";return c.childNodes[0].childNodes;default:return b}}function R(a,b){if("srcdoc"== +b)return $.HTML;var c=wa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return $.RESOURCE_URL}function Pa(a,c,d,e,f){var h=R(a,e);f=g[e]||f;var k=b(d,!0,h,f);if(k){if("multiple"===e&&"select"===wa(a))throw ma("selmulti",xa(a));c.push({priority:100,compile:function(){return{pre:function(a,c,g){c=g.$$observers||(g.$$observers={});if(l.test(e))throw ma("nodomevents");var p=g[e];p!==d&&(k=p&&b(p,!0,h,f),d=p);k&&(g[e]=k(a),(c[e]||(c[e]=[])).$$inter=!0,(g.$$observers&&g.$$observers[e].$$scope|| +a).$watch(k,function(a,b){"class"===e&&a!=b?g.$updateClass(a,b):g.$set(e,a)}))}}}})}}function T(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,l;if(a)for(g=0,l=a.length;g=a)return b;for(;a--;)8===b[a].nodeType&&qf.call(b,a,1);return b}function Fe(){var b={},a=!1,c=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,c){Ma(a,"controller");L(a)?w(b,a):b[a]=c};this.allowGlobals=function(){a= +!0};this.$get=["$injector","$window",function(d,e){function f(a,b,c,d){if(!a||!L(a.$scope))throw S("$controller")("noscp",d,b);a.$scope[b]=c}return function(g,h,l,k){var n,p,q;l=!0===l;k&&x(k)&&(q=k);if(x(g)){k=g.match(c);if(!k)throw rf("ctrlfmt",g);p=k[1];q=q||k[3];g=b.hasOwnProperty(p)?b[p]:wc(h.$scope,p,!0)||(a?wc(e,p,!0):u);La(g,p,!0)}if(l)return l=(H(g)?g[g.length-1]:g).prototype,n=Object.create(l||null),q&&f(h,q,n,p||g.name),w(function(){d.invoke(g,n,h,p);return n},{instance:n,identifier:q}); +n=d.instantiate(g,h,p);q&&f(h,q,n,p||g.name);return n}}]}function Ge(){this.$get=["$window",function(b){return B(b.document)}]}function He(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Zb(b,a){if(x(b)){var c=b.replace(sf,"").trim();if(c){var d=a("Content-Type");(d=d&&0===d.indexOf(Vc))||(d=(d=c.match(tf))&&uf[d[0]].test(c));d&&(b=pc(c))}}return b}function Wc(b){var a=ja(),c,d,e;if(!b)return a;r(b.split("\n"),function(b){e=b.indexOf(":");c=K(N(b.substr(0, +e)));d=N(b.substr(e+1));c&&(a[c]=a[c]?a[c]+", "+d:d)});return a}function Xc(b){var a=L(b)?b:u;return function(c){a||(a=Wc(b));return c?(c=a[K(c)],void 0===c&&(c=null),c):a}}function Yc(b,a,c,d){if(z(d))return d(b,a,c);r(d,function(d){b=d(b,a,c)});return b}function Ke(){var b=this.defaults={transformResponse:[Zb],transformRequest:[function(a){return L(a)&&"[object File]"!==Ca.call(a)&&"[object Blob]"!==Ca.call(a)&&"[object FormData]"!==Ca.call(a)?ab(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"}, +post:sa($b),put:sa($b),patch:sa($b)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},a=!1;this.useApplyAsync=function(b){return y(b)?(a=!!b,this):a};var c=this.interceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(d,e,f,g,h,l){function k(a){function c(a){var b=w({},a);b.data=a.data?Yc(a.data,a.headers,a.status,e.transformResponse):a.data;a=a.status;return 200<=a&&300>a?b:h.reject(b)}function d(a){var b,c={};r(a,function(a,d){z(a)?(b= +a(),null!=b&&(c[d]=b)):c[d]=a});return c}if(!ca.isObject(a))throw S("$http")("badreq",a);var e=w({method:"get",transformRequest:b.transformRequest,transformResponse:b.transformResponse},a);e.headers=function(a){var c=b.headers,e=w({},a.headers),f,g,c=w({},c.common,c[K(a.method)]);a:for(f in c){a=K(f);for(g in e)if(K(g)===a)continue a;e[f]=c[f]}return d(e)}(a);e.method=vb(e.method);var f=[function(a){var d=a.headers,e=Yc(a.data,Xc(d),u,a.transformRequest);D(e)&&r(d,function(a,b){"content-type"===K(b)&& +delete d[b]});D(a.withCredentials)&&!D(b.withCredentials)&&(a.withCredentials=b.withCredentials);return n(a,e).then(c,c)},u],g=h.when(e);for(r(t,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var l=f.shift(),g=g.then(a,l)}g.success=function(a){La(a,"fn");g.then(function(b){a(b.data,b.status,b.headers,e)});return g};g.error=function(a){La(a,"fn");g.then(null,function(b){a(b.data, +b.status,b.headers,e)});return g};return g}function n(c,f){function l(b,c,d,e){function f(){m(c,b,d,e)}E&&(200<=b&&300>b?E.put(Q,[b,c,Wc(d),e]):E.remove(Q));a?g.$applyAsync(f):(f(),g.$$phase||g.$apply())}function m(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?I.resolve:I.reject)({data:a,status:b,headers:Xc(d),config:c,statusText:e})}function n(a){m(a.data,a.status,sa(a.headers()),a.statusText)}function t(){var a=k.pendingRequests.indexOf(c);-1!==a&&k.pendingRequests.splice(a,1)}var I=h.defer(),M=I.promise, +E,G,O=c.headers,Q=p(c.url,c.params);k.pendingRequests.push(c);M.then(t,t);!c.cache&&!b.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(E=L(c.cache)?c.cache:L(b.cache)?b.cache:q);E&&(G=E.get(Q),y(G)?G&&z(G.then)?G.then(n,n):H(G)?m(G[1],G[0],sa(G[2]),G[3]):m(G,200,{},"OK"):E.put(Q,M));D(G)&&((G=Zc(c.url)?e.cookies()[c.xsrfCookieName||b.xsrfCookieName]:u)&&(O[c.xsrfHeaderName||b.xsrfHeaderName]=G),d(c.method,Q,f,l,O,c.timeout,c.withCredentials,c.responseType));return M}function p(a,b){if(!b)return a; +var c=[];Ed(b,function(a,b){null===a||D(a)||(H(a)||(a=[a]),r(a,function(a){L(a)&&(a=ha(a)?a.toISOString():ab(a));c.push(Ea(b)+"="+Ea(a))}))});0=l&&(s.resolve(q),p(F.$$intervalId),delete f[F.$$intervalId]);t||b.$apply()},h);f[F.$$intervalId]=s;return F}var f={};e.cancel=function(b){return b&&b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId),delete f[b.$$intervalId],!0): +!1};return e}]}function Rd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "), +DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a",ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"]},pluralCat:function(b){return 1===b?"one":"other"}}}}function bc(b){b=b.split("/");for(var a=b.length;a--;)b[a]=sb(b[a]); +return b.join("/")}function $c(b,a){var c=Aa(b);a.$$protocol=c.protocol;a.$$host=c.hostname;a.$$port=aa(c.port)||xf[c.protocol]||null}function ad(b,a){var c="/"!==b.charAt(0);c&&(b="/"+b);var d=Aa(b);a.$$path=decodeURIComponent(c&&"/"===d.pathname.charAt(0)?d.pathname.substring(1):d.pathname);a.$$search=rc(d.search);a.$$hash=decodeURIComponent(d.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function ua(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ga(b){var a=b.indexOf("#"); +return-1==a?b:b.substr(0,a)}function Gb(b){return b.replace(/(#.+)|#$/,"$1")}function cc(b,a,c){this.$$html5=!0;c=c||"";$c(b,this);this.$$parse=function(b){var c=ua(a,b);if(!x(c))throw Hb("ipthprfx",b,a);ad(c,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var b=Pb(this.$$search),c=this.$$hash?"#"+sb(this.$$hash):"";this.$$url=bc(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=a+this.$$url.substr(1)};this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)), +!0;var f,g;(f=ua(b,d))!==u?(g=f,g=(f=ua(c,f))!==u?a+(ua("/",f)||f):b+g):(f=ua(a,d))!==u?g=a+f:a==d+"/"&&(g=a);g&&this.$$parse(g);return!!g}}function dc(b,a,c){$c(b,this);this.$$parse=function(d){var e=ua(b,d)||ua(a,d),f;D(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",D(e)&&(b=d,this.replace())):(f=ua(c,e),D(f)&&(f=e));ad(f,this);d=this.$$path;var e=b,g=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&&(f=f.replace(e,""));g.exec(f)||(d=(f=g.exec(d))?f[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var a= +Pb(this.$$search),e=this.$$hash?"#"+sb(this.$$hash):"";this.$$url=bc(this.$$path)+(a?"?"+a:"")+e;this.$$absUrl=b+(this.$$url?c+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ga(b)==Ga(a)?(this.$$parse(a),!0):!1}}function bd(b,a,c){this.$$html5=!0;dc.apply(this,arguments);this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;b==Ga(d)?f=d:(g=ua(a,d))?f=b+c+g:a===d+"/"&&(f=a);f&&this.$$parse(f);return!!f};this.$$compose=function(){var a=Pb(this.$$search), +e=this.$$hash?"#"+sb(this.$$hash):"";this.$$url=bc(this.$$path)+(a?"?"+a:"")+e;this.$$absUrl=b+c+this.$$url}}function Ib(b){return function(){return this[b]}}function cd(b,a){return function(c){if(D(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Me(){var b="",a={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(a){return y(a)?(b=a,this):b};this.html5Mode=function(b){return Xa(b)?(a.enabled=b,this):L(b)?(Xa(b.enabled)&&(a.enabled=b.enabled),Xa(b.requireBase)&& +(a.requireBase=b.requireBase),Xa(b.rewriteLinks)&&(a.rewriteLinks=b.rewriteLinks),this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(c,d,e,f,g){function h(a,b,c){var e=k.url(),f=k.$$state;try{d.url(a,b,c),k.$$state=d.state()}catch(g){throw k.url(e),k.$$state=f,g;}}function l(a,b){c.$broadcast("$locationChangeSuccess",k.absUrl(),a,k.$$state,b)}var k,n;n=d.baseHref();var p=d.url(),q;if(a.enabled){if(!n&&a.requireBase)throw Hb("nobase");q=p.substring(0,p.indexOf("/", +p.indexOf("//")+2))+(n||"/");n=e.history?cc:bd}else q=Ga(p),n=dc;var t=q.substr(0,Ga(q).lastIndexOf("/")+1);k=new n(q,t,"#"+b);k.$$parseLinkUrl(p,p);k.$$state=d.state();var s=/^\s*(javascript|mailto):/i;f.on("click",function(b){if(a.rewriteLinks&&!b.ctrlKey&&!b.metaKey&&!b.shiftKey&&2!=b.which&&2!=b.button){for(var e=B(b.target);"a"!==wa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var l=e.prop("href"),h=e.attr("href")||e.attr("xlink:href");L(l)&&"[object SVGAnimatedString]"===l.toString()&&(l= +Aa(l.animVal).href);s.test(l)||!l||e.attr("target")||b.isDefaultPrevented()||!k.$$parseLinkUrl(l,h)||(b.preventDefault(),k.absUrl()!=d.url()&&(c.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});Gb(k.absUrl())!=Gb(p)&&d.url(k.absUrl(),!0);var F=!0;d.onUrlChange(function(a,b){D(ua(t,a))?g.location.href=a:(c.$evalAsync(function(){var d=k.absUrl(),e=k.$$state,f;k.$$parse(a);k.$$state=b;f=c.$broadcast("$locationChangeStart",a,d,b,e).defaultPrevented;k.absUrl()===a&&(f?(k.$$parse(d),k.$$state=e,h(d, +!1,e)):(F=!1,l(d,e)))}),c.$$phase||c.$digest())});c.$watch(function(){var a=Gb(d.url()),b=Gb(k.absUrl()),f=d.state(),g=k.$$replace,q=a!==b||k.$$html5&&e.history&&f!==k.$$state;if(F||q)F=!1,c.$evalAsync(function(){var b=k.absUrl(),d=c.$broadcast("$locationChangeStart",b,a,k.$$state,f).defaultPrevented;k.absUrl()===b&&(d?(k.$$parse(a),k.$$state=f):(q&&h(b,g,f===k.$$state?null:k.$$state),l(a,f)))});k.$$replace=!1});return k}]}function Ne(){var b=!0,a=this;this.debugEnabled=function(a){return y(a)?(b= +a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||A;a=!1;try{a=!!e.apply}catch(l){}return a?function(){var a=[];r(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"), +debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function va(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw ga("isecfld",a);return b}function dd(b,a){b+="";if(!x(b))throw ga("iseccst",a);return b}function oa(b,a){if(b){if(b.constructor===b)throw ga("isecfn",a);if(b.window===b)throw ga("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw ga("isecdom",a);if(b===Object)throw ga("isecobj", +a);}return b}function ec(b){return b.constant}function jb(b,a,c,d,e){oa(b,e);oa(a,e);c=c.split(".");for(var f,g=0;1h?ed(g[0],g[1],g[2],g[3],g[4],c,d):function(a,b){var e=0,f;do f=ed(g[e++],g[e++],g[e++],g[e++],g[e++],c,d)(a,b),b=u,a=f;while(e< +h);return f};else{var l="";d&&(l+="s = eso(s, fe);\nl = eso(l, fe);\n");var k=d;r(g,function(a,b){va(a,c);var e=(b?"s":'((l&&l.hasOwnProperty("'+a+'"))?l:s)')+"."+a;if(d||Qa(a))e="eso("+e+", fe)",k=!0;l+="if(s == null) return undefined;\ns="+e+";\n"});l+="return s;";a=new Function("s","l","eso","fe",l);a.toString=ea(l);k&&(a=yf(a,c));f=a}f.sharedGetter=!0;f.assign=function(a,c,d){return jb(a,d,b,c,b)};return e[b]=f}function fc(b){return z(b.valueOf)?b.valueOf():Cf.call(b)}function Oe(){var b=ja(), +a=ja();this.$get=["$filter","$sniffer",function(c,d){function e(a){var b=a;a.sharedGetter&&(b=function(b,c){return a(b,c)},b.literal=a.literal,b.constant=a.constant,b.assign=a.assign);return b}function f(a,b){for(var c=0,d=a.length;c=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,e,f=0,g=d.length;fa)for(b in k++,f)e.hasOwnProperty(b)||(t--,delete f[b])}else f!==e&&(f=e,k++);return k}}c.$stateful=!0;var d=this,e,f,g,l=1r&&(u=4-r,P[u]||(P[u]=[]),P[u].push({msg:z(b.exp)?"fn: "+(b.exp.name||b.exp.toString()):b.exp,newVal:f,oldVal:h}));else if(b===d){n=!1;break a}}catch(D){g(D)}if(!(k=F.$$childHead||F!==this&&F.$$nextSibling))for(;F!==this&&!(k=F.$$nextSibling);)F=F.$parent}while(F=k);if((n||m.length)&&!r--)throw v.$$phase=null,c("infdig",a,P);}while(n||m.length);for(v.$$phase=null;C.length;)try{C.shift()()}catch(B){g(B)}}, +$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(this!==v){for(var b in this.$$listenerCount)q(this,this.$$listenerCount[b],b);a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync= +this.$applyAsync=A;this.$on=this.$watch=this.$watchGroup=function(){return A};this.$$listeners={};this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=this.$$watchers=null}}},$eval:function(a,b){return h(a)(this,b)},$evalAsync:function(a,b){v.$$phase||m.length||l.defer(function(){m.length&&v.$digest()});m.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){C.push(a)},$apply:function(a){try{return p("$apply"),this.$eval(a)}catch(b){g(b)}finally{v.$$phase= +null;try{v.$digest()}catch(c){throw g(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&u.push(b);F()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,q(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,f=!1,l={name:a,targetScope:e,stopPropagation:function(){f=!0},preventDefault:function(){l.defaultPrevented= +!0},defaultPrevented:!1},h=Za([l],arguments,1),k,q;do{d=e.$$listeners[a]||c;l.currentScope=e;k=0;for(q=d.length;kRa)throw Ba("iequirks");var d=sa(pa);d.isEnabled=function(){return b};d.trustAs=c.trustAs;d.getTrusted=c.getTrusted;d.valueOf=c.valueOf;b||(d.trustAs=d.getTrusted=function(a,b){return b},d.valueOf=ra);d.parseAs=function(b,c){var e=a(c);return e.literal&&e.constant?e:a(c,function(a){return d.getTrusted(b,a)})};var e=d.parseAs,f=d.getTrusted,g=d.trustAs;r(pa,function(a,b){var c=K(b);d[fb("parse_as_"+c)]=function(b){return e(a,b)};d[fb("get_trusted_"+c)]=function(b){return f(a,b)};d[fb("trust_as_"+ +c)]=function(b){return g(a,b)}});return d}]}function Ue(){this.$get=["$window","$document",function(b,a){var c={},d=aa((/android (\d+)/.exec(K((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},g,h=/^(Moz|webkit|ms)(?=[A-Z])/,l=f.body&&f.body.style,k=!1,n=!1;if(l){for(var p in l)if(k=h.exec(p)){g=k[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}g||(g="WebkitOpacity"in l&&"webkit");k=!!("transition"in l||g+"Transition"in l);n=!!("animation"in l||g+"Animation"in +l);!d||k&&n||(k=x(f.body.style.webkitTransition),n=x(f.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hasEvent:function(a){if("input"===a&&11>=Ra)return!1;if(D(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:db(),vendorPrefix:g,transitions:k,animations:n,android:d}}]}function We(){this.$get=["$templateCache","$http","$q","$sce",function(b,a,c,d){function e(f,g){e.totalPendingRequests++;x(f)&&b.get(f)||(f=d.getTrustedResourceUrl(f));var h= +a.defaults&&a.defaults.transformResponse;H(h)?h=h.filter(function(a){return a!==Zb}):h===Zb&&(h=null);return a.get(f,{cache:b,transformResponse:h})["finally"](function(){e.totalPendingRequests--}).then(function(a){return a.data},function(a){if(!g)throw ma("tpload",f);return c.reject(a)})}e.totalPendingRequests=0;return e}]}function Xe(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var g=[];r(a,function(a){var d= +ca.element(a).data("$binding");d&&r(d,function(d){c?(new RegExp("(^|\\s)"+gd(b)+"(\\s|\\||$)")).test(d)&&g.push(a):-1!=d.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,c){for(var g=["ng-","data-ng-","ng\\:"],h=0;hb;b=Math.abs(b);var g=b+"",h="",l=[],k=!1;if(-1!==g.indexOf("e")){var n=g.match(/([\d\.]+)e(-?)(\d+)/);n&&"-"==n[2]&&n[3]>e+1?b=0:(h=g,k=!0)}if(k)0b&&(h=b.toFixed(e),b=parseFloat(h));else{g=(g.split(od)[1]||"").length;D(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);var g=(""+b).split(od),k=g[0],g=g[1]||"",p=0,q=a.lgSize, +t=a.gSize;if(k.length>=q+t)for(p=k.length-q,n=0;nb&&(d="-",b=-b);for(b=""+b;b.length-c)e+= +c;0===e&&-12==c&&(e=12);return Jb(e,a,d)}}function Kb(b,a){return function(c,d){var e=c["get"+b](),f=vb(a?"SHORT"+b:b);return d[f][e]}}function pd(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function qd(b){return function(a){var c=pd(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return Jb(a,b)}}function hc(b,a){return 0>=b.getFullYear()?a.ERAS[0]:a.ERAS[1]}function kd(b){function a(a){var b;if(b=a.match(c)){a= +new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,l=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=aa(b[9]+b[10]),g=aa(b[9]+b[11]));h.call(a,aa(b[1]),aa(b[2])-1,aa(b[3]));f=aa(b[4]||0)-f;g=aa(b[5]||0)-g;h=aa(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));l.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e,f){var g="",h=[],l,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;x(c)&&(c=Kf.test(c)? +aa(c):a(c));Y(c)&&(c=new Date(c));if(!ha(c))return c;for(;e;)(k=Lf.exec(e))?(h=Za(h,k,1),e=h.pop()):(h.push(e),e=null);f&&"UTC"===f&&(c=new Date(c.getTime()),c.setMinutes(c.getMinutes()+c.getTimezoneOffset()));r(h,function(a){l=Mf[a];g+=l?l(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Ff(){return function(b,a){D(a)&&(a=2);return ab(b,a)}}function Gf(){return function(b,a){Y(b)&&(b=b.toString());return H(b)||x(b)?(a=Infinity===Math.abs(Number(a))?Number(a): +aa(a))?0b||37<=b&&40>=b||n(a,this,this.value)});if(e.hasEvent("paste"))a.on("paste cut",n)}a.on("change",l);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)}}function Nb(b,a){return function(c,d){var e,f;if(ha(c))return c;if(x(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length-1)&&(c=c.substring(1,c.length-1));if(Nf.test(c))return new Date(c);b.lastIndex=0;if(e=b.exec(c))return e.shift(),f=d?{yyyy:d.getFullYear(),MM:d.getMonth()+1, +dd:d.getDate(),HH:d.getHours(),mm:d.getMinutes(),ss:d.getSeconds(),sss:d.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},r(e,function(b,c){c=r};g.$observe("min",function(a){r=q(a);h.$validate()})}if(y(g.max)||g.ngMax){var v; +h.$validators.max=function(a){return!p(a)||D(v)||c(a)<=v};g.$observe("max",function(a){v=q(a);h.$validate()})}}}function td(b,a,c,d){(d.$$hasNativeValidators=L(a[0].validity))&&d.$parsers.push(function(b){var c=a.prop("validity")||{};return c.badInput&&!c.typeMismatch?u:b})}function ud(b,a,c,d,e){if(y(d)){b=b(d);if(!b.constant)throw nb("constexpr",c,d);return b(a)}return e}function jc(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;d(?:<\/\1>|)$/,Sb=/<|&#?\w+;/,ef=/<([\w:]+)/,ff=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ka={option:[1,'"],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "], +td:[3,"","
    "],_default:[0,"",""]};ka.optgroup=ka.option;ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead;ka.th=ka.td;var Ka=T.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===W.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),T(R).on("load",a))},toString:function(){var b=[];r(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?B(this[b]):B(this[this.length+b])},length:0,push:Pf,sort:[].sort, +splice:[].splice},Fb={};r("multiple selected checked disabled readOnly required open".split(" "),function(b){Fb[K(b)]=b});var Nc={};r("input select option textarea button form details".split(" "),function(b){Nc[b]=!0});var Oc={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};r({data:Vb,removeData:yb},function(b,a){T[a]=b});r({data:Vb,inheritedData:Eb,scope:function(b){return B.data(b,"$scope")||Eb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return B.data(b, +"$isolateScope")||B.data(b,"$isolateScopeNoTemplate")},controller:Jc,injector:function(b){return Eb(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Bb,css:function(b,a,c){a=fb(a);if(y(c))b.style[a]=c;else return b.style[a]},attr:function(b,a,c){var d=b.nodeType;if(d!==bb&&2!==d&&8!==d)if(d=K(a),Fb[d])if(y(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||A).specified?d:u;else if(y(c))b.setAttribute(a,c);else if(b.getAttribute)return b= +b.getAttribute(a,2),null===b?u:b},prop:function(b,a,c){if(y(c))b[a]=c;else return b[a]},text:function(){function b(a,b){if(D(b)){var d=a.nodeType;return d===qa||d===bb?a.textContent:""}a.textContent=b}b.$dv="";return b}(),val:function(b,a){if(D(a)){if(b.multiple&&"select"===wa(b)){var c=[];r(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(D(a))return b.innerHTML;xb(b,!0);b.innerHTML=a},empty:Kc},function(b,a){T.prototype[a]= +function(a,d){var e,f,g=this.length;if(b!==Kc&&(2==b.length&&b!==Bb&&b!==Jc?a:d)===u){if(L(a)){for(e=0;e":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a, +c)||e(a,c)},"!":function(a,c,d){return!d(a,c)},"=":!0,"|":!0}),Zf={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},gc=function(a){this.options=a};gc.prototype={constructor:gc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"=== +typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=y(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw ga("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.indexa){a=this.tokens[a];var g=a.text;if(g===c||g===d||g===e||g===f||!(c||d||e||f))return a}return!1},expect:function(a, +c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},consume:function(a){if(0===this.tokens.length)throw ga("ueoe",this.text);var c=this.expect(a);c||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return c},unaryFn:function(a,c){var d=qb[a];return w(function(a,f){return d(a,f,c)},{constant:c.constant,inputs:[c]})},binaryFn:function(a,c,d,e){var f=qb[c];return w(function(c,e){return f(c,e,a,d)},{constant:a.constant&&d.constant,inputs:!e&&[a,d]})},identifier:function(){for(var a= +this.consume().text;this.peek(".")&&this.peekAhead(1).identifier&&!this.peekAhead(2,"(");)a+=this.consume().text+this.consume().text;return zf(a,this.options,this.text)},constant:function(){var a=this.consume().value;return w(function(){return a},{constant:!0,literal:!0})},statements:function(){for(var a=[];;)if(0","<=",">=");)a=this.binaryFn(a,c.text,this.additive());return a}, +additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.text,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.text,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(kb.ZERO,a.text,this.unary()):(a=this.expect("!"))?this.unaryFn(a.text,this.unary()):this.primary()},fieldAccess:function(a){var c=this.identifier(); +return w(function(d,e,f){d=f||a(d,e);return null==d?u:c(d)},{assign:function(d,e,f){var g=a(d,f);g||a.assign(d,g={},f);return c.assign(g,e)}})},objectIndex:function(a){var c=this.text,d=this.expression();this.consume("]");return w(function(e,f){var g=a(e,f),h=dd(d(e,f),c);va(h,c);return g?oa(g[h],c):u},{assign:function(e,f,g){var h=va(dd(d(e,g),c),c),l=oa(a(e,g),c);l||a.assign(e,l={},g);return l[h]=f}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression()); +while(this.expect(","))}this.consume(")");var e=this.text,f=d.length?[]:null;return function(g,h){var l=c?c(g,h):y(c)?u:g,k=a(g,h,l)||A;if(f)for(var n=d.length;n--;)f[n]=oa(d[n](g,h),e);oa(l,e);if(k){if(k.constructor===k)throw ga("isecfn",e);if(k===Wf||k===Xf||k===Yf)throw ga("isecff",e);}l=k.apply?k.apply(l,f):k(f[0],f[1],f[2],f[3],f[4]);f&&(f.length=0);return oa(l,e)}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(",")) +}this.consume("]");return w(function(c,d){for(var e=[],f=0,g=a.length;fa.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Jb(Math[0=a.getFullYear()?c.ERANAMES[0]:c.ERANAMES[1]}},Lf=/((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, +Kf=/^\-?\d+$/;kd.$inject=["$locale"];var Hf=ea(K),If=ea(vb);md.$inject=["$parse"];var Td=ea({restrict:"E",compile:function(a,c){if(!c.href&&!c.xlinkHref&&!c.name)return function(a,c){if("a"===c[0].nodeName.toLowerCase()){var f="[object SVGAnimatedString]"===Ca.call(c.prop("href"))?"xlink:href":"href";c.on("click",function(a){c.attr(f)||a.preventDefault()})}}}}),wb={};r(Fb,function(a,c){if("multiple"!=a){var d=ya("ng-"+c);wb[d]=function(){return{restrict:"A",priority:100,link:function(a,f,g){a.$watch(g[d], +function(a){g.$set(c,!!a)})}}}}});r(Oc,function(a,c){wb[c]=function(){return{priority:100,link:function(a,e,f){if("ngPattern"===c&&"/"==f.ngPattern.charAt(0)&&(e=f.ngPattern.match(Of))){f.$set("ngPattern",new RegExp(e[1],e[2]));return}a.$watch(f[c],function(a){f.$set(c,a)})}}}});r(["src","srcset","href"],function(a){var c=ya("ng-"+a);wb[c]=function(){return{priority:99,link:function(d,e,f){var g=a,h=a;"href"===a&&"[object SVGAnimatedString]"===Ca.call(e.prop("href"))&&(h="xlinkHref",f.$attr[h]="xlink:href", +g=null);f.$observe(c,function(c){c?(f.$set(h,c),Ra&&g&&e.prop(g,f[h])):"href"===a&&f.$set(h,null)})}}}});var Lb={$addControl:A,$$renameControl:function(a,c){a.$name=c},$removeControl:A,$setValidity:A,$setDirty:A,$setPristine:A,$setSubmitted:A};rd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var yd=function(a){return["$timeout",function(c){return{name:"form",restrict:a?"EAC":"E",controller:rd,compile:function(d,e){d.addClass(Sa).addClass(ob);var f=e.name?"name":a&&e.ngForm?"ngForm": +!1;return{pre:function(a,d,e,k){if(!("action"in e)){var n=function(c){a.$apply(function(){k.$commitViewValue();k.$setSubmitted()});c.preventDefault()};d[0].addEventListener("submit",n,!1);d.on("$destroy",function(){c(function(){d[0].removeEventListener("submit",n,!1)},0,!1)})}var p=k.$$parentForm;f&&(jb(a,null,k.$name,k,k.$name),e.$observe(f,function(c){k.$name!==c&&(jb(a,null,k.$name,u,k.$name),p.$$renameControl(k,c),jb(a,null,k.$name,k,k.$name))}));d.on("$destroy",function(){p.$removeControl(k); +f&&jb(a,null,e[f],u,k.$name);w(k,Lb)})}}}}}]},Ud=yd(),ge=yd(!0),Nf=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,$f=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,ag=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,bg=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,zd=/^(\d{4})-(\d{2})-(\d{2})$/,Ad=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,kc=/^(\d{4})-W(\d\d)$/,Bd=/^(\d{4})-(\d\d)$/, +Cd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Dd={text:function(a,c,d,e,f,g){lb(a,c,d,e,f,g);ic(e)},date:mb("date",zd,Nb(zd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":mb("datetimelocal",Ad,Nb(Ad,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:mb("time",Cd,Nb(Cd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:mb("week",kc,function(a,c){if(ha(a))return a;if(x(a)){kc.lastIndex=0;var d=kc.exec(a);if(d){var e=+d[1],f=+d[2],g=d=0,h=0,l=0,k=pd(e),f=7*(f-1);c&&(d=c.getHours(),g= +c.getMinutes(),h=c.getSeconds(),l=c.getMilliseconds());return new Date(e,0,k.getDate()+f,d,g,h,l)}}return NaN},"yyyy-Www"),month:mb("month",Bd,Nb(Bd,["yyyy","MM"]),"yyyy-MM"),number:function(a,c,d,e,f,g){td(a,c,d,e);lb(a,c,d,e,f,g);e.$$parserName="number";e.$parsers.push(function(a){return e.$isEmpty(a)?null:bg.test(a)?parseFloat(a):u});e.$formatters.push(function(a){if(!e.$isEmpty(a)){if(!Y(a))throw nb("numfmt",a);a=a.toString()}return a});if(y(d.min)||d.ngMin){var h;e.$validators.min=function(a){return e.$isEmpty(a)|| +D(h)||a>=h};d.$observe("min",function(a){y(a)&&!Y(a)&&(a=parseFloat(a,10));h=Y(a)&&!isNaN(a)?a:u;e.$validate()})}if(y(d.max)||d.ngMax){var l;e.$validators.max=function(a){return e.$isEmpty(a)||D(l)||a<=l};d.$observe("max",function(a){y(a)&&!Y(a)&&(a=parseFloat(a,10));l=Y(a)&&!isNaN(a)?a:u;e.$validate()})}},url:function(a,c,d,e,f,g){lb(a,c,d,e,f,g);ic(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||$f.test(d)}},email:function(a,c,d,e,f,g){lb(a,c,d,e,f,g);ic(e); +e.$$parserName="email";e.$validators.email=function(a,c){var d=a||c;return e.$isEmpty(d)||ag.test(d)}},radio:function(a,c,d,e){D(d.name)&&c.attr("name",++rb);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,g,h,l){var k=ud(l,a,"ngTrueValue",d.ngTrueValue,!0),n=ud(l,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,a&& +a.type)});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return!1===a};e.$formatters.push(function(a){return ia(a,k)});e.$parsers.push(function(a){return a?k:n})},hidden:A,button:A,submit:A,reset:A,file:A},yc=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:{pre:function(f,g,h,l){l[0]&&(Dd[K(h.type)]||Dd.text)(f,g,h,l[0],c,a,d,e)}}}}],cg=/^(true|false|\d+)$/,ye=function(){return{restrict:"A",priority:100,compile:function(a, +c){return cg.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},Zd=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);e=e[0];c.$watch(f.ngBind,function(a){e.textContent=a===u?"":a})}}}}],ae=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,g){d=a(f.attr(g.$attr.ngBindTemplate)); +c.$$addBindingInfo(f,d.expressions);f=f[0];g.$observe("ngBindTemplate",function(a){f.textContent=a===u?"":a})}}}}],$d=["$sce","$parse","$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var g=c(f.ngBindHtml),h=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(h,function(){e.html(a.getTrustedHtml(g(c))||"")})}}}}],xe=ea({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}), +be=jc("",!0),de=jc("Odd",0),ce=jc("Even",1),ee=Ia({compile:function(a,c){c.$set("ngCloak",u);a.removeClass("ng-cloak")}}),fe=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Dc={},dg={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=ya("ng-"+a);Dc[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,g){var h= +d(g[c],null,!0);return function(c,d){d.on(a,function(d){var f=function(){h(c,{$event:d})};dg[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var ie=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,l,k;c.$watch(e.ngIf,function(c){c?l||g(function(c,f){l=f;c[c.length++]=W.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),l&&(l.$destroy(),l=null),h&&(k= +ub(h.clone),a.leave(k).then(function(){k=null}),h=null))})}}}],je=["$templateRequest","$anchorScroll","$animate",function(a,c,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:ca.noop,compile:function(e,f){var g=f.ngInclude||f.src,h=f.onload||"",l=f.autoscroll;return function(e,f,p,q,r){var s=0,u,v,m,C=function(){v&&(v.remove(),v=null);u&&(u.$destroy(),u=null);m&&(d.leave(m).then(function(){v=null}),v=m,m=null)};e.$watch(g,function(g){var p=function(){!y(l)||l&&!e.$eval(l)|| +c()},M=++s;g?(a(g,!0).then(function(a){if(M===s){var c=e.$new();q.template=a;a=r(c,function(a){C();d.enter(a,null,f).then(p)});u=c;m=a;u.$emit("$includeContentLoaded",g);e.$eval(h)}},function(){M===s&&(C(),e.$emit("$includeContentError",g))}),e.$emit("$includeContentRequested",g)):(C(),q.template=null)})}}}}],Ae=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(Gc(f.template,W).childNodes)(c,function(a){d.append(a)}, +{futureParentElement:d})):(d.html(f.template),a(d.contents())(c))}}}],ke=Ia({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),we=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",g="false"!==d.ngTrim,h=g?N(f):f;e.$parsers.push(function(a){if(!D(a)){var c=[];a&&r(a.split(h),function(a){a&&c.push(g?N(a):a)});return c}});e.$formatters.push(function(a){return H(a)?a.join(f):u});e.$isEmpty=function(a){return!a|| +!a.length}}}},ob="ng-valid",vd="ng-invalid",Sa="ng-pristine",Mb="ng-dirty",xd="ng-pending",nb=S("ngModel"),eg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,c,d,e,f,g,h,l,k,n){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=u;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1; +this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=u;this.$name=n(d.name||"",!1)(a);var p=f(d.ngModel),q=p.assign,t=p,s=q,F=null,v,m=this;this.$$setOptions=function(a){if((m.$options=a)&&a.getterSetter){var c=f(d.ngModel+"()"),g=f(d.ngModel+"($$$p)");t=function(a){var d=p(a);z(d)&&(d=c(a));return d};s=function(a,c){z(p(a))?g(a,{$$$p:m.$modelValue}):q(a,m.$modelValue)}}else if(!p.assign)throw nb("nonassign",d.ngModel,xa(e));};this.$render=A;this.$isEmpty=function(a){return D(a)|| +""===a||null===a||a!==a};var C=e.inheritedData("$formController")||Lb,w=0;sd({ctrl:this,$element:e,set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},parentForm:C,$animate:g});this.$setPristine=function(){m.$dirty=!1;m.$pristine=!0;g.removeClass(e,Mb);g.addClass(e,Sa)};this.$setDirty=function(){m.$dirty=!0;m.$pristine=!1;g.removeClass(e,Sa);g.addClass(e,Mb);C.$setDirty()};this.$setUntouched=function(){m.$touched=!1;m.$untouched=!0;g.setClass(e,"ng-untouched","ng-touched")};this.$setTouched= +function(){m.$touched=!0;m.$untouched=!1;g.setClass(e,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){h.cancel(F);m.$viewValue=m.$$lastCommittedViewValue;m.$render()};this.$validate=function(){if(!Y(m.$modelValue)||!isNaN(m.$modelValue)){var a=m.$$rawModelValue,c=m.$valid,d=m.$modelValue,e=m.$options&&m.$options.allowInvalid;m.$$runValidators(a,m.$$lastCommittedViewValue,function(f){e||c===f||(m.$modelValue=f?a:u,m.$modelValue!==d&&m.$$writeModelToScope())})}};this.$$runValidators= +function(a,c,d){function e(){var d=!0;r(m.$validators,function(e,f){var h=e(a,c);d=d&&h;g(f,h)});return d?!0:(r(m.$asyncValidators,function(a,c){g(c,null)}),!1)}function f(){var d=[],e=!0;r(m.$asyncValidators,function(f,h){var l=f(a,c);if(!l||!z(l.then))throw nb("$asyncValidators",l);g(h,u);d.push(l.then(function(){g(h,!0)},function(a){e=!1;g(h,!1)}))});d.length?k.all(d).then(function(){h(e)},A):h(!0)}function g(a,c){l===w&&m.$setValidity(a,c)}function h(a){l===w&&d(a)}w++;var l=w;(function(){var a= +m.$$parserName||"parse";if(v===u)g(a,null);else return v||(r(m.$validators,function(a,c){g(c,null)}),r(m.$asyncValidators,function(a,c){g(c,null)})),g(a,v),v;return!0})()?e()?f():h(!1):h(!1)};this.$commitViewValue=function(){var a=m.$viewValue;h.cancel(F);if(m.$$lastCommittedViewValue!==a||""===a&&m.$$hasNativeValidators)m.$$lastCommittedViewValue=a,m.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var c=m.$$lastCommittedViewValue;if(v=D(c)?u:!0)for(var d= +0;dE;)d=u.pop(),n(P,d.label,!1),d.element.remove()}for(;S.length>x;){k=S.pop();for(E=1;Ea&&q.removeOption(c)})}var v;if(!(v=s.match(d)))throw gg("iexp",s,xa(f));var z=c(v[2]||v[1]),A=v[4]||v[6],B=/ as /.test(v[0])&&v[1],x=B?c(B): +null,I=v[5],M=c(v[3]||""),E=c(v[2]?v[1]:A),L=c(v[7]),K=v[8]?c(v[8]):null,R={},S=[[{element:f,label:""}]],T={};w&&(a(w)(e),w.removeClass("ng-scope"),w.remove());f.empty();f.on("change",function(){e.$apply(function(){var a=L(e)||[],c;if(t)c=[],r(f.val(),function(d){d=K?R[d]:d;c.push("?"===d?u:""===d?null:h(x?x:E,d,a[d]))});else{var d=K?R[f.val()]:f.val();c="?"===d?u:""===d?null:h(x?x:E,d,a[d])}g.$setViewValue(c);p()})});g.$render=p;e.$watchCollection(L,k);e.$watchCollection(function(){var a=L(e),c; +if(a&&H(a)){c=Array(a.length);for(var d=0,f=a.length;df||e.$isEmpty(c)||c.length<=f}}}}},Bc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("minlength",function(a){f=aa(a)||0;e.$validate()});e.$validators.minlength= +function(a,c){return e.$isEmpty(c)||c.length>=f}}}}};R.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(Nd(),Pd(ca),B(W).ready(function(){Jd(W,tc)}))})(window,document);!window.angular.$$csp()&&window.angular.element(document.head).prepend(''); //# sourceMappingURL=angular.min.js.map diff --git a/vendor/assets/components/angular/angular.min.js.gzip b/vendor/assets/components/angular/angular.min.js.gzip index 8f86428b973a269489f3f872065e1ad2b1412ec1..ede8ca32658dde9a1f3b56bf8944e090bfb929bc 100644 GIT binary patch literal 46451 zcmV(pK=8jGiwFP_4hvQQ1EgDNbK5x5{_bC4vAkY#1k1RvLM#Uguqxq7?Z`L(mN zwGqssIP7fyYo|ROi_VVt_rK(?;mWEjz15wq?X3;`{I>cgO-Hd}t0#%S-iZGF5!_{mIqJFPkgwaF)nCN)z{jU9*|@_|Mr?txoeSlKDTo<;T@a zH}D=amCv$dwOZjl-imBh_#zw4Zd8&_`&$DhyV7_1PTLK7LEGW@&+*!#?F>qURFcVd-{@^M$R+uoz}W!v5MebIAzShJ2r&IC}z z#x`2M(!R1%;`f;Q^mMF$x9~wcJvqtjCQunpSK^P!Tf4Mk zV|z6Mq#DD_4VYlkNKFV*1KGgC&riaWcpB;zo-@hPJSBL58)B0JXVXcxqv!>lwKbf0{7yaif`=+rxmvXb>os?@Z+SG~TXUH7 zw^`^vGY@h%gf6uWuWePA0}@B&mC%O1O(*5Ck-x>p{;&iz+Qq*TQ0x!;qd_<5#{*yC zXFe}ZvxFx?Sj3u$Y^qKYC?VV4^6E+FfgwgQ?anQbk(m;?0stavHG2;vwbabsz}i@e zYIJlzc@g@@2D8XC+(k*4-eI~0 z`TE(cHFYcEz3D?Db?;8(a$L{*yc<9br;|9!U57cI_BvAAP6-PmcPslzS}6Jf6mQ_Swn{}2 zmU7#Ne%C|_fd){)Z#8Hk^jR3?(OUx>;H&P>hc47{YuDEVBLv~FCENCv?0hJU*<_)1 zsG3JlPnro{KF-p+)wjZR*Mkw<1)E~fug!(Tgo>tCA`Le<; z(4aQ9p%lt1Z7d9KS9T_|>6pHUrUlh6JeOsyL_t?_R5g)@AhPo5JpJK-k?VqI8V!A5X3rNf0k@Q6q~q2aRrK!fDk1Yj^xcUtoyStFHSMK{XzJg$|v z=bFz(c`;#{&N8W3U`Ks7q;u|O|F18b6z_SEMxt6Qruhl+ltP8F?eS9`XW@)ZOJ4MV zz=tyC(Xs$Y25Y5}UM8i}Ld;89coYR{_HKtLGsZo-`-fVTNaFycKq9y8(l@=H$ zAzF=f=6Iz!V;qP}mplN*(9XVmb(gTLM28 zloBl?d$i5i3cbnaKvQUKEocxFOXv(HpX^1S^dei}2)Vjfthugilkkd&U;hz0P^aIq z>1cR0%92dA6=PPMh6$)xjd=Q}K5#+17mhE{g$bWm;DjcYTo$&PO&WU;G>pykZZA=` z9I1usPf^CURn8*QM2g&9EBq{dwA?}}ZMU%VEOqQv>&g#o$K(A{rU^O9C2+uLE)q$C zX6!>xa9P559#=ivMs&nv5+iJ4I0B;MYPT&&Lj4iR9sgU<_IlEvaV=wLIcwJ^XNTw44u{rVlltnx!}EiW?~l%|9Ii!^>^DX) z56(XvIh8S!R#Y5RCSrNOS?a~C`&sh=7FzUnIN6K}-a_IWu;Z{1Cr|7=g(6FnYU>~z zJ#NRHI`|0ueM*sJoF>rIZk|0n1|4-XfiQb>sqH^-o*RVu?y9Eb5H& zo48n6ifI|O6xnqU^xh$phX@r8FOdta_^DAW_@OCbVHK*yQV7ARk7(rvnY80Qa!=jNbz#39oV z26y)LVB4`&c9V zG11eK!<#6d*anV;O!;{g`xr&;);?Wz+wIWPQA18Xtu5HSmQc!apevOhmL)^T($t&1 zeYP3=Y3AmZj8HA3?K`jqPPe7A4*0`8fRBP%t`_^HkqmUmhStOP_+OFxwP(P*!y3K9+zH%D{|xMN!uBc?H*W@hnQdnFAH6 z6Q~3;0WG@jIbkZb=X1C=+ub?`3vz5S2^}O1l??4}LM!6DAy=eP<0>@NE<-r^$j2vhW$mx|XGBeiSQ>1y%h9IAbSN!EAJr4AY}T1hERKYNrhW z;7KS_6sr#gY_`=50Cag zzWI7`_WHa9FJ?X+9=$%g__}|7esy_uvG<|1*}Yg_SDjoZLIP}O46GtBx@gp1AWEBw;jL>4k56NIChwKf4UW* zLp4-crosv!j`i|K*V3bRR5mS;b(Rv-X`9bDQk=qZV-*0$sR(U2r=f{!u&XknK!&_l zE}~c~u&NkDxH@9fmI-`cqhS$5aNG~D!F6yjE33Hra^R~|FDz;MK3wpwW`-LgjHcTi zN_kGxJCz*>L}esQ74WmLxN-`*w-UC*%>_93?RPSGpkyvO!=#fYJ$K*F1-o!a~PQ8U|<$OoSkJw@Y5=JgO52jrI7#@y>!?ci1WJG?WZnvGbc&&RZ$hK z+3E;=x)oenJ?`mD5NDealeAI*A5&>@c$H8r!|Q!rsc@RvtImO}0|z417g1E?w`!Ks zCQwNy8q9-CiSK3Y5z=uthOcm`3{&4U z?Nd@w?fOmBX=<~`rIR$vNStdUROf!i+0;31KNwJghd;iAzc2EuOWWU2s&HBOz`!T2 zImLyt!_4Lw@2sueM9EQ>`E~Q@>9Pv3Y1K^{&SKlG7Zygxa|?GRLpEaLmxNbC6Nd%Jgg{~n=ji8$-}Ls52{F*n`%tAe_aDESQP|^|;sN^#EgVLp zeYm6VQy#LFuh}dqdWseHVai90h{cDj2(dCwdsXq4Qw~!~DQNqLg$czdzE4$l8%eeB zy%@!J9jz^V)noXkO@>JFh0ofw1?G`P0tGDVEabVG&d+mab6tBG?!1a z{0>d6yUVACpd9#ptBTnRv7}RdH=w}<{)2(~+;%#uRPIVB#?f>>?~d2kf4E~yRJ^(E zg+FNRy@QlK24$$ibBTQ>5ByLtm=cwQ{EmUaBA)U{u$984@O@U?b#u$3%77(lI^laP zh_En~EKlD~iMUXNEWOd~iDWoik|W^YnSc}jSXPPnA$o^J`vGlJBIWmJzAljFKhZIE zfuEvzrmqP3XEck5m?zj^x|e0*fgiJ5l}%~c?;*hkbNN`Xn4%JisD!^`v&p1r-@ zI1U7f^`lH@3GsVt&ZxdyM@jrNWTg$_muSlAgogn$A=-76j}Ob>mcI)TLaM-roxCu$ zBNdRXJ4IYa)}*e>G{#wf;B`uEfmp=F@I~m&%|uM8rw`c6X-x&?acyl1`Oc%C>SGr1 zOTtXpn^alEz4SVj*tw5_UqR!eWHQV712_t)K#U-$4H3BEA44{kSv1M{C$x07%CFJN zA7aJ-2w6JOeES@-WVBCW@n_jO&TnGAR`#yc&4j`?E)+%%VlkcaplTi=AmXy>o`)e1 zO)nMSxzf#A^M=%aP1ud7b&MnOF||&HJkoy?8P72I&2*xif2Y=2nh+|KlJJ-YKE$(3 z#Go0!q3(s6zz_0-+Lz<>j;Fe%k2=xKy9f;BKkD}N7>H+eAM!0`an7eSbgpSL+TWX%dT<|-6$T7U5;`fS8L=O3cLP)~1?v&vg-;)~Sx(AsxkpthS zPw(ySjkqs52l|`cT}eitw7q?(&?*uCspbt~sVTeH{BjrAy+hd$zDHPIIZm@nN&Msc zij}6AAD|t;vBC87L$oDKj(S~qk0TnWx&9j6vVy?hprh`k(ufwJGC#&(VaJaogBwI^ zqLT)GqKZD;2!D%?e&qJ9Xzar%kS#n#zf8*bd)*oFGyK)1XMT=Wm8tU&=>6co;}?G} z@Mj8-c4__|LQzJv?-livXo!@Ne?&Vk)Zl;)%0F2n#7dYUeoX+aw2l8klcF1yLPI~J zZ5|%*KWSdJ_nNO!L(mh0gTlUXF~rcfAE(H0cres?e(e{Ac0sbv_vDbRG^z@SVCuSJ zFGRZck&LNH_c#npwKPN?{yzP=wr!XBUQ3g~)=$)`I9|V1dwQZ*4F739pm-w}%XOj% zP7e#tzs|%+r^9gBzPYEr3{2U8t`7AzzA6g)2Z)73w}enQ5EoAOmTC?mV)bE1-7Bdt z#ge zy)To+Go2bzeyTGTmx7n(N)3(ff_I&keWg{zyjSEBQ@EZwL!~=^LSt?obqN2uc)x=D zOZuaVt6s0a$bEe^G2ob1$eP!)sm}X@`LWbUvc~(Lrqbw}CQau?-hE%(jzPE|f~q9i zDD>_jgQze5pMP|nUv$#6#puz=!W%{arAu79m@XSnVCXW&B3n8fBBz)Z&wG>)3H(WT zfiq#p6d|BRA@#=Eh7!Ut4w&6YR9&q=$&L$;Z>BOeip8NIYSjvbjFn6KcJck^QoLl17mlm14a(KhZ@0%V7ry@NKN^%c_9v9-ufd4V=F2W|KiTCWZKq34 z5?3L3Eh>Ry&r>iPocCLTG%L`;!&=JIFOg|0&1o+Hk$l-LA5{!%TcDq6TJPaVQ10|$ z=hiLfe)AP&^IE01vAs(o*mf7LTO3KDqT}fc%{B>XeNWK;l6=Vrbb&>~<3_on zw*TeR3!iZ7ZfyI$^){|1FkJ(*ebb&(iRT4k(aR6(?SrfjrL9mEJ9u?rtlb{?qJHcq ztD+o9+w(`Il5TTM%%tBY5pd~Q72E3N(83llw8q6Qj9d7=l?}l?BAz&F^gl4r`*$m4 zAxhgl>jJmZ#s6pR+P~YjvGni$D`c*ZOIC<1x3e=-(x}F9Y&U5f+j+F^%E}x*C5@<; zDaUav{qK9f3xEVAB{My{=d>0dAOQjw_xatMpQNQLDL-q==(}2ac{-y)LHc~`rYNvM z<89WM)})34Pd!($ph%;FfaIxSr|64xTEcGDY zBU@eZ41-qMgniGBbHt^@{LB*5@W5&`2ye#qciG;|%nD?@WfFj?3ziEa2+Jv?H-yRK zy9Mb2UI=o0uXJkHAo-uuq_&8<9T@sfP5NKdAW3J$#_B3!iKnBXzKOexi0#p+t2d0Y zH5RYou=+vQdHLq*!+VSvKKb{jH}8+%o*wf#?WN1OCdcyH%gSvTLt&@QSZE{k>W<|? zT8q`1fwq#hMyo&pRChx7B}v(&W$%f;%sK{3?=udt&pYUaOC62JX);tEwOQC_tLfch zN-AWc1B9;jvQ5-IV6{r!Q?lOT?RS=!dKdGE%A zF%aaNV_5}IXS(#edVUOI$hY<~gQgV=k#9v z6OU>U4F9DSMy%t*89-m6nwg7#9qu1%AXE8(OPUx`-pW)VGn=)%F>JmHk-EQcvL9-> zE-}*On8%+b9+|@G+_GZ(mTWe|ZwI_i2;h7eZXvK{GhVF_s^(_1`vo6ZJZPL%76{9R zEs1ScF#_Tmpmt5NwNrka3r&D*9|ixiBz|#wcRT4d55x{+b<^?aW3q__V2btER2SSz z+bgG$3Ve=w6Zz+3mF;q!4D)`MbuZ^9v z&~;{X>R7ovvo5J0rLVnbyBSg&jklgB8Oy>TGt_Eue=ne_RGl&Q(e5p_J}X-bl;Ja< zFj`VOfynNt{{6^S==z3MIHVH?HJH9V{YlwSKybHp(ncxvBT{D~D|MTc6X#y&wK|r#@xBVGP2Vh8uLJLr!#_{%Dh-Vcg z@;q}jZdG9J#5>TEto!g1i)U7=Mf>1-ayCa$q z`UV_P`T;uh0y25F#j*!#@60sB>>+As_}l3v8n{ zQY=O$@%KrQsGkapb6G(>#U7N-UXQ`MO1HP^)ekvbq_R#_`h^#S5=4R&MI%C;yJzi!R*}MQbA{!(o zwRIBXm;`#v2k(PTP;s;Q5rapdo^^L$V;> zqDFz&il&RjbnN+m7#ML|;gchUmfrdd89yS_qksPBwng*|ej!#J&Gq+Fy56qoOJ-+q z0@J`Hog@f5)X@MPX^+YC$CuETh>s)LfNrL)eY<%62rC>dbQ6aL!;S+I2MQ47>)(~K zXe*CG5W)AWa(ZplZE0ewVXfA7Lnu``wWBIC{42AcO{|PzGPPG0dG}YA3m~jpenhN@ z1%fF@yy}FgZ{~VsIu96Eiz}3H_SBm0?LE1bI>m4| zcdzcg1Yk9IDI+%tna{Xultt?nEfLlbAP z2J$q#GZ#Jqmp38FazU!S)KyK!vMo*!w4IP5V!U*h_X|i-Fa;d~mnk48@qw%dYC#l3Z~R;b=`Ax_0P$-u!~nu$x|B1q2%1hD}2>spBkP#rCGzzZvD(j`St z+73|~;2e&La&_p2y;x+j@ol|cbk|xvlMJsNey8;19Qiv}e9zrYOv~jmE2YpRP`Tyj z`}^mid+t%=b4aP+oYcNk^#=RNKFx~4*Y=moYwwy`#hrQSdI&wg59u>!mvtFI_%Fcr zB*zs}s5WWQj(Hk8VpOO;6F z;OndSdf2H_kq$n(fXEb`Ti@&49^C3gdR;fk(Qac-F?^IX&v_`T9*LeOt#~>e4O4p$ z8l{jyvZAlZ5wMW^&P+GoQuK*~VKSJ7i-B~SIcbtX`gsPggM2@@7mfB|<5f6m9RBpT zcB9?)`M<{b#e2usV1z`v|MvA{)^E$9qrS?1JrfN&6n|;|^w*;V9_NP;Ui=e&dE+|{ zgMHT^jj0$$`C|jPcw(20>0M*`DZR@8QA`?98i%OjHC!|v!lJR5P8&Ii$F$)(&4f(( zpBs38BYIqXg*hx^up)b>F z{~^1x&5rC)j*wNxyAQ@e8zwNlP5A)s;p*n&daHhu#Cqj%$Vjdb2_(pw*+KwuK>V8& z)S{*XDc`~;M83f%Rw7z1NsYt>s{~10zL*q?Nu1_#i$e2>M%Z!7H-_6Aq%I%DpJBBK3j7-O~2;4ue|#z@20L-g}ZVF zUf-E3UOh)v05L=|5jb9odLSMS#SG>){j?@ZVAOS_orvFsqy@Z&o|0r{MG9mfkyof8 zJ`GbV1-O12pXYe4GaPcHOiKn7UU~ouA`wr+1ny0tT>%U+iNjeK4;LgF6s)&(aeVX7 z-;Q5@I298#oAEeF=U^6UB^k&kpi}GAL!%oVt-r^ZniE=RGCuGlpGN5;jNt$_47)gA zSmhfST#}QpYBojA*1PEcJlfyql~HjFcaGXpG&jnjvc}B6Hi(D^q0_WfX92q*bjRq- zL`wcI?w|(|gRAq@;cxYWxsWgw8 zu?&qU>ZUzb9CQFQ)>CIla+E_KB=@(8gdE$z4iI)bOnUlqUpB2_x3o4)GaT&9skyMyB`BO_Buhw zJ0LN1o&fy*?hX)T`bz>+6m(MXA<3ly^+XT4w;yjmcDxQv8vdvguX8}mhVD;^#-DxZ zHJ5;|hy0<`yYT_(F_x6kn5k)19z81^BT(Uvw-L0*@_NboM6&4yC61GTg+Li>X?G_4 zT`$VF)}5Bl27fu@k(eL2&jb)U^SOK$Y|f*gk0&g^W!DG0;iEfkwa% zj7^(+PbvMeh&5aFfWxC1EXz@G6|6jVXI?$4M$t(K-SwTyr(kpH_rY+H=A^A}V=0fS z64f`Jf(Mf$iT*cRdAj@I807z_`~Svc_5i-sj7G4dLNKMb5e#W zHgo%~dBCAe=)i0kJ|OelIVxu1_I{i<*Z_dcOO?YNKk!DBa0K=Gy8X|RF|^}G3}(aC@)kXxB_ozW%txo zdd1IbpKQQU*}c7}{wf`T-MH=reVW`pu+7ji@!=hZcz-(VPpS&B4N~!JIzZ+J->8o01^v!*_a<`tVzYcuXvzl>TZubJM+Z7wdpl{zK|~Kq#9MVFpwHhTy@s3Vb?gSbN-GO00RY3)rA&=4Dw@tiolAerTZ`;d6BNbPd+ zeo&?jcCNYlMs*hj`%DJ?`|Zz=b%>k`#f)lqh(J53zYAC-zc})IRSMC~XbN+hQ=l1m zWk||c=>_5*QFTKK0}gcSNgvbj+d*+u;y4OKvvUrh{t*Quc|)7O=m~xV&dQ>b_;_95 zW^z>4H4EL5;C4ALIuEPMPi1P5vQu zyPc;(SLddCc6VR8bCGr2>}zX54`kct)#Ki(L^%cZ$(GC98z|9T2qIG$w!3XHmgD6z z3lwy~*j2Mm->;Vdx~^)ZtTQ(4e?qc&0`1KZ+U_A;A{B3MuNq9kDJ-Stk4K-r!t?xC zY~^@9(rq^Qow9(+;og+nwO@fK_n?Z4JSaHH(8fhSA-T0N5B(ewn#a&pkwf8}I{b7l$) zi*fRd6MkJnnOe|lNIBr?{>+yh&WTZRGupE%{fUISr^XF*m)J3VoG8LDgLy;uf9!D~ z)6&Pr1T`yCCD49c26P2gayj)$(;(LPFb#5-Ac>_2yH#QfD7xNsXYOC`+* zuzq64#s`H~8#`ngGLlH$fjA;AVd)7)#XnWpC}ujm(x3-SjWPXRnIpPZ+RPwdnXndg zO?&dya`~z<>{#-6q5K07$y&u920V=HO3IPyI-;&4-F393>*!B)9Z}cOwytMonJ6m{ zpqdI+dr4n$pvF0(4@Q~GSd7%J1Wk>|E>}7xb`JukrI&nUZ>$mRKl-i_ZHNwzS)Tbg zl{obR=ObJ)e4je3)h;4k^<#OL(dTRTY2TClFlGV0tD{=~&|~BsSW&7ycf?2dW8nT_ zZ9C~kJ%fC#6jC}PflN9cE3Z*Kv4!1Hw8hGbCD=*vf~yYMh4wX$%^>Qyc*%O9E?xJc znsnm-)eEc~NTx(4UW;r8HHHxA%DY^-4|tkT-h06@2Zc^Zo#s2oottu|{F}0?T&F|F z;`d0QK*ms!#VOf~!${nSD>4*l&N}XuOvMcwDMp3z-5WC3G7o`4R^;3p6y6K2spnG& zc2+mqbS0-N!$=P3m77M6ByN964ff!0JHekj_p%nm)?U&*RYVIIlbvh)iyC!u@0^lu zVg~2m2-YhK|4LYjjz5gZewTJtt-$#Q{#_J~B42ew)&P2-=}+!LAooYut&a;YI5Dkc z6dW&?HdBR=40r84ZRq_sH2c);t~&uQp5S}wbJZ`ko!$-m&{#{? z8$wc>%I*z=oBMKae}Augt-4iZbwYWHT0s4lDrS-m;lN^u<8=$wPu@>YEYFrp?fcd@ z+_|S)d{@_-*KDmPDG}Q(<9OwYQ^qsP%y~czrc?sq*VPIjEs%fJ+M7l|PNJ+{Enhd{ zx~d3*Tw!ulqr0M;_UM!;h77eUI^|E{8mul(J%HYA!R|=Mhel3w!S~gzT%M`sq$iQu zM7-xN%sa!-->TDvs)~51(|7?>SOq}x(u$DHPAYxn`|nFN9AxbT6+@ZOd~jn&pkJYu z&I=H)upY>2&Z-ec!O3s5*+*Q2zB52!XZ48Z>shJ*S2SZm*4pEUe0HD1CzkQPFdpvW2~GYFCZd8c)v^;Mo}kwviBXooLV*XZQi$B|I;mGMeM<+ zyA|Fd_B3jwug&psX)^}>qcPVpD@ZK#d*Xk{$SPVJT!Yx0D2$NZ-@kB+x}2?UQqvQU z*qBn*e~*Z^!0xU~o3AV(UtRie2gB5*#-|?AokKblS>1BZl`+$ovl1JPe<-3K^hv=g zPci6j)ii9sYY@g)?$lKJ(ifKl|ci| zAW$@)o>ZRwSN5XQR($RL=5^dlZ0q#lKrda=P<}>1T>XSb{Q!*AE6@LGGj8LNY&;<) zFGw(Gwo`X&-Rc{+|BL*7NvA~*aOMgT9zm=hH9DEJW@>}c#F8qP@!ThWa0${<>G&rG? zJ~2}onh}qfdU$gBg?(ZnB+`7Q$A3xyHNj&CXHGv*w+3*kW_mnWH2_!l4<6@tsE*F-+?m`2=A#!ygSqpRG)1uRZL9joS6C<9%Am4 z-Qun2oW29YEXTK2<)_lOUh$X_wjw|8bt46vAav9bba)Drb3bhh5l>c{8By# zT8SDagtyjBM$c7oM;CS@zc{WJhhTbwPaTA|4|IQ~9bKhJl~3W`=~d8tl?x(YAJp~# z3crGG6?slu?+$w`0Ocdc@wy09i8HVAQmJge(+#n`>3?@yTdPjXSt|#|?1Dv>F`7hXSO0|UN9!E1;am;sf7wUh34KQm{kKwUT z&l&4)3kRN3v>1m09b>IXN-iaQR6b0&o zbKDSwX{hINTHt!inTb;OhQ@Fv-|1O&<5(R=zvm+`8|m*z6TT6VPa?L+t;IR z4eQ!=D79h|V4v&=YqmeaBvCRI6SYTQOamp%qF!7ksD{1l#rDwj_<)1|(@+##9G`)M zx}3kzXM2a?VW=Y__HDcthx7 zRs|9tsb+U1(dF1$@q*NvmtIK^lA450Y=w#>KGq@0UlS9b_m=USv=4~Uwi!hm`-#Zm zm;^vCln?)9Bg$k6CK73%XoL1QE6rc*>l5qF?kxE7@~>&Ao^XTL>Y4S1_VRh!o2JB@ zwrmovXH`5{V=A#>nB(H_;O7T7G>39!%T;hRk}Kj5uUX+k&Ow}|70_n;nRw4( z_fhT(6xEycx{r@rabVWN<%*Hr%2ya&1OwEmnd?~(S_N6K@N5|vaSd^j98+FOXI{3u zvd~gw-v+v*1LXx?D8W9W$U7j>IWE+YBUVG=dS}q3fugn8831!FrES8XlI#-vcIn(r_$~Tg9Eoz z7R%{J#}2ZiD#Szh+sm2Nvc=2Vx1yol9J7k|E20`HT{=Ij!5PZIgL2}FY{H8A{`jThma2?0V zFe=vvxfn!kIL^h`NMH8ls2|0(L4BohPI1jIK_q4ngiy-x7aesuJIg5G4=7k1S-tqQ zD9J*a7o{R>(pKt3*BB&k53zOwaG7!kgnhDwd_eqYC@hpyD1le%V&RpXV_J76YP098 zDYuHOmAeq-z!j_-X221Zf}uQ&_Wkd9<$JGk_dWkZJ<9Q|IaZH~euI6~`=0L$SE4=L z)-c@nRj@DKO&k|EL}RSI>W`*VxckVb-G5GUF0G+UqNta&Xju6YJ^$m9KGC4k_TiOt z)#boBF3}jJR9HFe!h|h;JAYhuO|nSzghsLw$;}6HbL~0QKz2U)bltSX?G)oSSTbT3 zNOX~ZSOP^?riKn-T5zOrxN?#A`p_THoQ};5wCplqoL59bx(iSfDknuJU~0hpl(}^y zxC@8RGnz|Db|{@k$iah2+><~x9X0~QZUeqDc7Y%q;=7X>F#t<)Ed~Wr1;8R*>~81o zZ!r%8@o<((KEo*mnyvHuaWVyzz$B(3Vi&gHJdM1E2N))3k}sEZFp2B)coTGx6Epn% z;rqk>Kz(-H{FcjoWRRiZ`xNc}?__{a)j8Vxr@OyFsE;n|o5nUaZUl)`1s+kSTjRW+ z094o8^#s;eGmvZdaK_%F+J+5A>8Zg3eE&(@-LnsKZnK^UD?K&2`1qM=y;t{2I@0%3 zlLx~K%U5u&ZCT0MgVuY8G2z&RI(n!Z+LH#tEtF|LeKe)N&xsqMb{{(tBuELc;;HFk zpO?^2i69~KrCr~Z{eYE*r2<47(;R_6;!4o5L)tj_ct|yeFRbo%C<5SsL{dO(K`Q$> zxh5H|mR2JF;Dt@#Y}9-MqiJmdykxjYiYklhHJZM^0Ql_O4qy2fvBFG4af0oo*RC#p)JzkfVXRP?d=pxN*Qv zFBzR5F^3$EU}|$xu;nTR8qjWM1X}<@ji{Nrs*MCWY=qs;0DVV zZAdAAn<(4dH{=wslI4WlS(Fsm3?URs(OVLwlEP1Yi+$UAkyDtlML9L$V)&`!r{XC% z3531P`L(|~y#0shqsow_VLeA)`h!$!{AxDS6>oIe)=j>R$-&RQllOx94EFUZkWYA1d1Sf zcRZbh4@W8DBSR#R9{m((pZ)*jNUaHN#Xe2>pLG1k{vX~_ZNZ!!=CS{bb_+mEFo&4K z`edo@@_AoiN;tIJ`+9zMymxW>w)x)UYp=Nr`r4jzRoI*Gmg_t_!a8N~^fxi7(aug{ z0#BE%uLOWzJ&_{WI=z?tgD?Gf71LPe z4x*JpX z2Y<)3$PfjMPM2`)U6kJqNC31N80VA7&lU)a097X(KLPhmgd!TBPb(mS?l9b=aHm3r z3YLyR2QyFN9(!xYCTR?f$2eOIr@hc)gypqWlT=rW)cS%;)`y5kIWwcVo@W=_TaR?6 zzltdc_=BcW3b`MvMWsZ4;;M8uKoV}2K>G-|OhvEX*Di#TQijIkEpc+*ayyb{W-PW# zOOUc)>2qhL@#rS;&9Mox?X316Hua+a#r*loaHrSwGONRe(4~xbqrEjvt!7*hFQ6lM z%YO4)CRGhUEpRt7T`Y!;dB(y435Qe(cqbc**Xu^>)vTbvYJ0KlrmW++hTdjay+uhT zqdG4Ee+Cb}1cuOy!9=bPn_%)B=VzgZ%_X%`sm7ONu1E`VTr{CmUzrPPwGK?Gh21oc z`i0$;VQTF;)EV-2F%gO7fH)};-n=|11#7xLL7LbVBR9-Oz7PzR7^>h*-x7=?-OZxT zzckKz)s0Ws`u9iJBTG>5;oR`wk#DW0j{F}yjH#X|?3PWMB}}XmAy!Gf$(Z0DRR{Ne z;fb7egRp57&L0sD1S>*pxdW}a(C=f2X+t|BaXf<Papiede7s zFCeC*n#_?H^XB~Y{Rg*<s% znoz(-ln!GSLaU2u>-^N9BZMrJT0?3xW)Qr{t2Eibx@4EFLLY;MDv=M|MhDEX?ge%F z3B6q}sH7S_de;G@kC&oj?ppywEd=i-Eh}R!w_sSFkxo(&j+|To%MFtsZTTgk-oc+O z1&2VpQby%482SFKpR!Z95rbkyGYaTttPLiSiNzvMcrhJddD1_K=`(CH|6k8{!qw3O zT5DVDU66EdY0(qx065t<|3dEfFe-K@WYeBpx_j!IzF&f@JF9+ezk5xeLl2{qu4!#^ z?+E7wSl(06c!fO?kQFz5;$KYY6oQ&LD}2@FXjUmZMLFHPwP3@Wlvc*Rl&v~qNcv)$ zS%QN{5&_%tJl;V4C%%bw1-g*WX%kETIh!iNeacX;{f#LHb-t(x=P-Pt3iEqYC#xpY z-hC;f735Osov23l4rv{D*OSZ1^mgLawh(L~&jAyuN+JMAeLP<5<`mO5mx@FEEXHP+ znk3xl!pPiQ|Lfqdkm#od5*ZLrK&VsP>;3lK)~|1Rv9iH=4Q3?;zK0*M~p%?B9%m* zVTT^=%Nq5DWshNTi0ZWCJAD59Pw}beM(WRLBAb(1p>s;;!a?Zuf3o+`5irn1KbuZR zJo)LRFd~q38=N3YHe=@?LqjkR5t z&V;+rxaO`T5G}N|@tHV4VR|tjzsYm-thU6EPkL6Rj7|N)i&-FoW9sKn`m%j_2H9#R zveoXc9Qe8GyVEKZX0Y+8ID&;wbTN)ua8uAB1MM5UuS8oMtle;vMks+g$WaEBxQNB~ z+JpzjU|xj+a-IB}XL#TF&#O2}&4CY1!9wNEEysSriBh|;E@@iZlL@|k!61!6SOyD9 zodrph)cK3~HIc#|!k~jN6FijDzD;C^51o^c&8d4*owgE|f05PVAmy~oSbN&fP-m(s z+zlm%O(=nMTT-Z#T!!?)=w)@xsPPgcI>Ipi^ti${Cew<&+TzsrSp5ba8STmnL@btL zX@bxk-N}pS3e`~a=s>mLn%>4wGcvnsb%OI#7TI1jYtX1*pW_qBvQl88=7Em2@EcBf ztj3jL5Cn6Y;A1$9vY>hF_Z^&sxf)Xqdc0dmw;@KeB{$4zlce`JRQ-l)H!387OW}!y ztYGMr_D=H1M( zf$vv=NE9eeja_iUEkxm0=NFKB$>)2Q_m$5C$d6SWalq@DIC zJg6NzDlGoVh}{~;6>7NEizcqoGuH_me5CNAZRKcp_v&xt8S`_a??Ir|%r7@$y&3zR z@`WzVaY$-L&9qcQq`h0I6j_!0@lvMN1M&McG;xQANe;@Q3Fwglb=D~tca28hleV%R zvKo}f)=!>#m6f*G9KD6)L>BtQnILDq-=_ct5Vx02_O6j``BVF4^X$j*=~?^e;yiqK z)oirdNAG@|H$Q}4)l(;Dr=LE^nexBtViVlwFaT*?|aeT)| ze=6E@ewfp~!^6?}bstBQY`|rEe5eZt=kS~SQ;qZiwzI1ZKer7(w+(;4J~)W~+SI&n zE}4e}8=lftjZbH1&G(IOKhD3k5x%uluZ}L77p-=)7>oAl`-|71_a>fP$MgH`<1Cr; zK|7wO!|kKl{O5>{@3-GvPcr%$-ESXV4~X|~pOd_jfgNw3ri&^5zo%^-7re?+KJeTu z+UKuN&n}9m;YoN19$_AU_R&1rMyqHMjg{KjdUfhvWa3tT&OP zi{bU0502+Q@$a1Er zZHB~=+hK}gWujy>|3{WTt7WdlhDmJtvy3_JV`0`8D;nU)4kvz4vei0`qy^l;f#!YS zWuq=R9YMVzQKlb;OW?fVUZTjQtaRmVHaB^~t=C^fg)v543Xh{F0LxlC zv2WRe*gVUN*gm){yu`n>n%?Fwz)C!5+fye=w2)32$ie~Y2M)2#agm*XCzp(ftFkcP zRyrx=ELcmYI3|j3ipaSyVH_=F;Thp{LVD7mE-~v`npfcuhBkJi8gQ>0zRCM)C3!WsaJ~ zn|e&zMK!svUJs`T&QsJiRk2>--}D!D#evwAmNqFXmg5omHlF z+;odqJBc;Peda&!o!++4pSLs>G_2#6=2TP@jVDg;+Kdc1M0qPOh*Ee43yRZ{T|#nso5}bDG>3$O@_HIA0ZKx* z*TBQXgjo8e$17M=BD7s*4eKvNffP$NG_^AWS}! zEI5I&vS2Cpte{uOqBLu@_T)*SmN7$BLgLKA zk&0Rg)baxfuEzqYl+$HaVIC0y$iJ>7i;~2IEGM(`Vxd}dWUL|_x6qg_J-LP+o2x>m|XdO@KfCNP}r4v$x5bPw0AE(l#@e(`L9CE8P;aaZ3 z%YsIvXO~`UOnvdH_>k9Z1G|FbmARKn!y94wL(a}l@~aX3IxIXKFg|c#len_d%(5_&aFYpEe;1|ES5?4Llr7f48p7w zJ8`qo_v~KptB8uP-LY)~{Nd*pr!pu>Dy64AhkfA+VJc&BHBJFHu7SQeHl-IVwND?_ zbzFQ-wois;A1=?O7Jzm(C~C75(PZ~`hH)$gAW`7k%K6k=u4gTSeLc6KP&1M=(buZ_ zu9N47fBXCQkB|R;ST4q4h;xp>LtIEvA{9yZbB@D-lC32NrI>_v$N6qY&#Z)AKL;TT zmshXTH_=X2G*BP)to53cY^U|Ttk`^r=p0#uh&8+80H90vZ5|9R549=+Zh6|y!gPL6 zd{Ko~%{y-KvDDtXG6BGf1|44rPvkylKxrmgJdQ?)1P(tb>(p#7l98*zA>$e@aS59T=Za=y;3z88mP# zhu|lL7EV9+Iu5@L{7947ghxtKz2jTIv-Kj$KD~dfLK0N7>&H#U$31O`nOEm>k`=lt z_TF61#u?jMW$cNEEp0sOZ#Jia^gV!2Zn(dnN&3IQ4$ykBsI#*O*qW@y=n4k4QuUWY zBvTA+R=6N}+lq@XtTd(SHb!vb*m(WiQZ@Yy_A<~IaoocU>au)Bw}=gkTl@mejk`J1 z#xq3O^EB}*RZl|7!cUxC+#K&VhQ*7lX;gBSa?1`=SA`tP`Qr~-K{OUGvehbE{PBk) z9jZ2pxREXl{IBWi;;q>5vRo6v?!nNjRjUQZyD zt$lq~v{0c$@KjnY)^G%=tI3p0;+%4U)nw8uPiJI29-<9ogjC`Hv^glK0_cTs8sis% zy{nZ^XA@HreM|aZRPqpoKm;FUs&ORCl9Diwn-@(j!@U2*wrw2uNah8zNW$&FH(JS= zTb7&|lA`0FZHzb>e&sd~uOR!?mhqW9mIQIq#a+m84(Ou; zj&ih94ypy8-@*Zr-%71mJ(gFpV+|tkhOFL5i^fV`gC}{8n)23--$ebU>Nk_WXi3dR zJd-bO#W+N?a%SWP{({aQbswqwsk%?p{aoFr>i$yQXYzh$;pRnHAI6v8!Gi2GXeS7a ziO4`R%L8Dfy91a81Ts{Kd9a&#O_@iOIZ~OEuVtQ6=0s&qzm|DUnW19U8)ir4>8$-h zcu5c86ELtCvCsJ(A_2?FHD=y%7BQB&dGkxIg+SV+CcBwu)8!DBlNroJi3#bP=Kj;1 z4t1C@aKtEG+g#aG#@6&F%Dr!SgGa4oIrsALZ%+mtnpk~CB!Ss^A7N4Zh=bbie&1tF zdJW(MLvse{43?*OJ45uc`+iW4#4w4&H4zJWAOpZ;Cyjv>u`)0Qd1Gvf@H8FM_PE>& z_p;^8U5dS&meIv@tP;jLK><#U!j#UvYFMQ!3HzobosecP9si;0GWv66Dmo9FQS+hW zL8HI)9(qmcye7v|N!5z*ve3b|on{8tQu;oz(IDVaQy8=Cg(0K7dV6~IOyxn7Qbm^` zzlSQ*;fq&Pre)yJaSvYfxvo4_Q#fP#N1vm=dFB9AF*0lu!k3OEfL0WK&bg5=sU=c_ zHXvf@Qh9_igueD+&4p%)goB5K!e-&>VY5{z6BBGW~+fgSG zQS&9Gs$PrKMcql9%UM>S*&Z{ptXgczNg57{ID|4FJ1IP&BkMD_M`xMn&5jgcMcts2W=@=3Vh)hidI#dFFE^5yDWa^Mz0{+V@59Hnkss_3YwXx*xXI<2m^-$iC7>X#W5xvD3zBWib9vhT{HVKnIsTUGFKS0fQ>mhcrcDB}fMa ze#V{hrm8k3iS%dal8mcmv4T5!Y0~R#`Zd6pcXB`520L|tRJf7^8}9+4$&{0K>+i=! zJQ{^6Reob261BIm*H_{EEgh|HiP-T|kUXM7NJ04~R`+H&-tLgIsW1fXL@dNQD4Q{- zRWs&wm~uQ|2Ir7A>-|pT$H63g)m`_32{p>`rQwHVZBa>TLng^Mm?*6fpFj54lee?- zFU5o@h<-vdmabjgi~yevY)_7pECp~Aa{zTFJDoZ87aey(Tk8ZFt4}8;)X~Tv11Vef zJ5_Br@4ypiUpO%%GN-x_N1`mcb%#2>dCZJU1^RXFo`Op$G5G)5@yRObaqf*VZ_(sw zX@SPJBoB);D!F34zt0csfBHpfQ@Z=HT!{w?4Wm{lS(30C9VKM|EOmx?qV_tb3#C0U z)8aI8VY4; z%qu(9U|RecTBCeW*lC{5fubuJSpFiPOUvWZbD88RyK$gnTsi^{ItrK|wJ||q>0EEr zE_a~7o<(LP>Jm4bhJivUKau2aHx*ob4<^#3Y_ivJGTX^TIB?x_Tj6l0J9YSd{Ayvh zjfLpsh#N5e?9_*HE8eKlhL2ns8PGoAD^J_}6jIcnZ+EG7@wY2!Pk>6h+50gJ?Ht-( zNX@H(>s%(E6GVO>lF~O1fVe*R7XQX2V5hBp{Z#GyfUK+B| zI65f+$dNL9sc%TwIBbhlL(Xvk*9@Wvds2FSeF?m#l*Pea#kWOX`?6;#*YQD|nGyZj zZiQ^ww~s`*-y_wr-&TffLKNKBgvY@hP1p+ALB72U%Vk(wp58K#pVU9sODY9DtexCT zTd-k$Acf@ZG_|)=Mmx+HFm4z#D3V_EFDItQ}GBf(od!WKm_TEamnR zcal~yll`P+`o4gcH_Lw%-mz1`SypcF2w4p5GuO{Mb*l!jf*j{xH&T_L1Qk7pBld){?Im<>tk$} z@mI58ci62D%o(UBoYDpDEq4_nq`(HS5uI+&-$k85w9#fWz8R4E7Ohz<^a!Y?CsSja zuSFZweZ*@a>V=C7UQ9%-Q5#30&K(}tVS9xQQ`!g}YeIh?D}DAj95B-0uKrJg;<)&B ztUigC4iAa9h2s>|>@v8PDC$rUzp=(zlIAE>=JSP8XkBOVXsErq%a6h6D*vdv-aGq- zr}bs`q1lCWzOy?&?d5K7!5p7^=foz)pN6shx7OeO#=r!-FGdE4>Hf=^-BYI11mgFbQsKQdU1SJ<2j05w4z&2C&R@nEjp$#3OSA@%XXeWG(hAJ|e zN*yc6g{1{kuhSTwphzF4VY9i2buI>CV0KUj(6IsSjebgsmZGo?mr)SB1rG^p4e7&Y zRY)43d|Z{?S%H86r(j8| z^bl40iY;u^KuAll@Ee9wzr4tnb^}XNbFhKaZ3*?(>xWWr(w!LuB|y# zAa}ek%+9&}Rz3|+y&$LSo#Gh2zW_Dt*Bv^)61^xIeNUCU)vab{_X>wyB0$F21_m8> z03{0~%^8H4e#|ix`%+GkD);`#03KvJDLHgKa&rhF6j3oj7T_85-cv~>{c=tD}%j3^a+A=Q2%Hto>)n}q)X|-WLt$1 zYz>Zs_#|Sm2RgG6zO~Qh610bmp$U2Lpie)2k#08F5Btj+{M{~!Yl_)w-WGpEzi?i2#9=qt_q2*!U zQ;{6eqD(r5$)*_d&TUdCT0-RqBu{Z2urq9*#08ARvAD!G1>c1^RvGuughM=0@6^ox zOVO|<*93Y7v$#{Wxcg8jG4}kFjoj{Sb|KE~%zT0rCn8;Bz=JW@n#e zP*1to-OpB}KzanLhXH)H6qrw8YLGv8u$xG<^laG!j20u8%_+ACi zn=Jc2Ok1eWypBVM<(|`&w;V zfJcpMB(WrN>EJ*s z>orKA>=mu#?bxrGZm=I21q%sJ-sT#?kN#xoyrMTqPMiDVmPSH90>jRQDe zmQUx*t5%g)M_~_GoOJg^;*+brc9{6l-2;*B4#ZvK5Ljn)b;=qcg=Kt!LCY$GMm-uN zVH)7WrDivhKnhpNBiZc*V>NPvV&ujr^iPl6SdZMeG;+Bfxe-QgR2{i7*M>%JAV+TB zEq`7&3?4`&3N0}dSW-)&B^HGyi!4)U@Ke!qAiI0$Go=;1Qlhj;h#izcoBy@W))`jm z8K)IHxp}abna#ebALzobY&rjj)YxiE*Jgl}FFTujHCL|9n^iX60A|+nkBAkQx$JCs zwu#5H*z-3G*FBs>{p{k%Q_UVxAd<76AUMa%Uxei zjroO+&r=J?x=T>hnLehl+Y@<^1I{UtPRgni9WXBWW|OPc@Xp-6>fGG-lOw7higA@m@D}?vf!Jlxs=-@XAZ2IO(Z=;iI%N+ zTEIKe+>5d`F!NGcZy@=)t&G%=vL)l#g#HWI6a%~M7CIJVFGD+I~qo#m6 zKL*?%y9ndg@~6tMIPq-(6Q`gp%Z=8PQ?5x&&48oH66Ly-dk7`6U@T!dhMnvwrz&`k z!}l*3bmvj)<;OR#dm+O^@%`C{5BBY+_ob)*EIoYx?8CcH?`Z<7T;>fuNr<|phMq2K z&-uY_7~BqA-+VqZr2Ct-995)uM`(nyN@QU`o$c&G+JJfA2xMZrOb*Lrd8cAlU_Nd| zfe~%nk4q#Xt$W&RGV8;IQbYCgPW#|{*iDM|5v!V6Rx6FZtrn@9C>$1%K2_;@*`a;x z=UEBfm+|=_ubCT4(I6DgiBg;^dc#F*O{gYrR}e200Lpy)&sj(s)xH>6FShL$Om~Xsh?%w1rIg;Sl?B8^TAN}PTt{yAsTGFHQVKF! zl$q$t6dyB^;J5NV|FL zI$X>j(ee+cnRvZA8cZ6GZ&iN!)E_JPt-D{zORsQt`H99IFGydw?bcGYTGRE@J%&;B zF*=&ky zCR0B^S+?1H|AZ=$5n&K5IaEw(bHzc*PBM;!!)dplgzvQ@yBw8s8NS|-YSm~y>F^3s za3LXbW&j(((8VUu0aRRqU6^8`PN)LuY#~wJ{Jto9d+Cpa>CmM=6>1jyX=ywEWoLb? zRQUEYL^Z=Q;iy#_e!U#2q7HwZy`^w$t)lTy9IyL zXhbHy6FkfG^=`b);H5ht%7JFM^e(RR$b)C=Z}Haip)dLk28II}zqfD#<;sa$8P_eY z3H3SHdR^l<&?~tQ);9d`a9d+eYQ6`ixoj-Tp%&B`H=#3bHJ4$Ea?9e`wj8bhY@wDd z0jmG|es?O%&q-qz-4*5P7Dyo41Z&5#1r|>90X4CgPd}fHqvaByBCa#-AI9=MM-$r; z#-`K_@;q-O#PO)r5qaEOM6DOEry+m_-`3UmSB634q8pf_J?8ybuVYE<__c{%N0T=>Mc4rr7H zCkC&f*;E!Pa9F2dTzvkJjhS0K9gkh7OKs>5dQE47PdZIE={TX|JC8!gYd&-y5-0!A z={=M>^@?Q*%yDQuUaLX%cZs4zVrgx~vMw9QI*yH4V5Tvip(kP+uS>uXoAyc~yh>)P zk#Y$5g&x11?S`OQvZbp%QGfkPXb5Yjn%EFubIz*?u9n;JB1|NRdFW2dksttMSVV$| zc6BolM9z)rc3!v_P|wYa^(xqWUaLlw$wt3>NffxU)qa)sw$pNS2il)X8NzYU?~Z%n zTu;Tzt(HoSwWX*sKi%Ek<(ryfJz7N8&2=rTn5?R$tD zenXIs(-rYK@p_dhyg>;d@|%JNY}}Pc^Ut{VWp1I7-09wg#40Weq;MY?;SNw`1@PhV zI{gTVN9+Hl5Y;>SqImK5i%#)ov_xKptd&1jzQ2?{zykW;3>LkBK{o10KSJZW`w|uY zw>nh#A`!Wo#jPc6{7vERL#o)}KK4ya#Mtx!ePNiIJIo2j$>&sDS>=@%VHUaXEt%6Z z{+hX!&f?O>EAccR!)_Ldy$s>F3OEGhk8NZ!19s8MWejvTX*Qha3M(Y_wn`}1hoF9; zJ*n;ytkq!h#MaS^qdsox4**vzpuIg;#@jG9cy+;N~>&n8zjL4OJ zSh{sy0XN(1I|z@pOlGF*g>kAO0X z@Zn)hF2HNdhv9LDf(nqEzV(rak#ULFrt0MwDJyVhE2PVdk-E= zyWjPmWX*Ob4v&2oXh<+j=eaxbj!&`&586*8iy9M$_k4Jhr7#waLzcZgC#G-(BS~qz z+z;KpLV=s5kUl}&?rFxtObKq}WH@!@MRWs%R2iH~z$@C!5^Qu$nQJjyQ>Vf-vJgi% zsnyNQ^QuXQneIHfNfuQC)F6Y;g?&{!VI<}p%StqW$icN9!WyL*chKVQW??~??Q)#?7C9-*XVk}%u0j6*&=kgoM z2{hYwk_BGB89K*KfZz+qjb9?6(rQA>se69b`A z5%`N!>7qc3Dgq=H=Ve^R5~S&U%=3pX4-tm;FqRcZNP#BJcwPM{@M6@S(qu5BxEi?U z(9(^jO$!t3Qy~RnH=yIXqYoHg97Mbs_oQ;>_=NIiPYx3=Yr_2FSh^@3wjav?w^`VJ zY(Dr;_L3+s@*m@S5EmugnONTxd3Y5gJ_LsXLx7vYYmeR5Gdse=+}gibl7Q%2^gT#N zJrIrO(Py`$3o{wq@1M}|`-4x_KeVG2UDhJF7;!Uwk*U$=v;qD~+y>M=QP?y^R~`z! ze>0n|2Kt7xc}@5G!^qUo^A4K{mD6ro%Q!mv+wu2*fA)jjy;6o=DDRU}=N>!=r_J*vwAO8 zmUx{8qOf_;Vo~#d)fvo;NTd(U{vfh~sw8snr7hPTg|Bm|&7hqX-ebQ3nwRbJvT#6K z<{T#b?m&YnDi2={UE|PfVJn`>Cs`aloFKxINHCAy45w?VuY96!yr#+cWj4*;b?tJ}M>D&~DaY+@eNqwfMZj`Ont7#50>ofJLA*J3jvj`rn zV=jFRJ5r8MqE4*B5dlcY*Ee*@g*uMk?fJ1)(@Fh4x{E$&>s>omn{13wmOl8FwGqeK zz9Ud|neDCc$C?W^d`X$!FU5W8TT%(ljCK@q*_SL`+OF)H}^y@+b{68lKv!?T*$%od6D$3e}IH-Ma(MF9N(g zHe1Fb)#8nWu?=|KK&zo^sgJi}J^e~a4LG3uiUSbI>6Q$0g8R81R1-E7s@qUKAX&?-D)H9ZCYe&<6T=JJ_>&0M}|>o zfew+(K-N;WEE*UA!g(2bjYBF*`xhBt$*vBdAhzCaR$9weM$Xp`#$2@uGF{Br5VR~y z3eOP)Tav9{Ud5R2flzb70!n~HYCz!3sqe36i`0<-00|u$9~Z+3(N%dgUS>=xsWe7* zeGASefCJf;-go!epjKMaN+|0=w0s+_hF2MH2rw;Ea3t6x%yykg97;wR(Z);KTFLWd zx5ztan@x0v(GFrA^#U7U<-00y*%1LZSb`mf5DKwto(G$4`I%AnIh|ckogHfr2nXol z*(}Y1f3&5|a{Gr9DzE*ICv^SYe=#W7_mD_zHmFbGu&Xdvy=n`o%U79cA>ZvKXyw$~ zs>#64`rK@Q*3g6~Bh|@~OpAGJ4+k?Tr37o8y#1<_N^mHY3WLkED8wTVorjnZmLjB_ zqG__jw&a`>n*bHoQ|^_whVk~QqN@bdmx-Aevgb5-d1kJQ{F#9rAWF4!`R8^NvE1^qW@BvBxO|MmdnlNSU|jkYup&o&D-d$&ArBjxO7KOs>D8HK}*i! zW#_iB7#zzYEqQ=7oRL@MRhNpsSQpeC? zFk`ElrG4F6Dfx$3;<%7j+>5tNas`b+OC?m#28%Z-))aC39HuUkb2MfrFvua7p3t(I z74{WNBomir&0$p|2r!rB4S1HC}#s zf%aXQ{U2Fs!a(iMS+jlaF6b}D|4Fz2H1WYKrg6H;T!-GPm`{Y5KvwCJOwl!oZgfrP zifeMkBX3tFs*6Bus&b{OauQxusuJB)PKf%dSnF$*7VFEs<7f2nt7ISyj>~(LdxUOm^KLafEmZ$_*D%tmo z_%zl$-_j;40e&13mB$1hbUQL%*U=RA{SIh_KRDoad=|QwmYq_tyB|q zJ$n-`<%FTmtTA+Dg%}YuY#Gj`-)tm~bEHXU6cWO)i97`>#>`&rheV;_PnDK%&nbm# zQPD(>r5OxMAn4Tp>tmKxDS)&LF>JajVw&1yR^gbv-nCeQIf<3>u4Y9$EHD~VH6TkW zWa^1kn9^HIkjbgLq+>;(oE3Y|bAd#k1hjBg<`_;2KBZ3^A;MmbtcN!5*QfHf+bO($ zR3z&uI2j2+g4Rtt1cF zKmb5rZu+3@6}?$5Kj^d6*X^qw6?Ts@7+7C-4IY(R%z=l?rn1W&jT(aJ6Di*uTrWS(QC7~ z7c#jo>pg=QIxUGuZ09OwEZG^cgY&_}C9rO&GM?yL2?CK9X+(+?MA@x87uTtk#AZ2` zypZbjZw{H5bzFW`%7XYs{*;mPsBiTvnFe*4swLHA3W{2E+{nF2u^50mA|;M#A$(i| z`^k~0T2!{o+qUw`2J+`PRlnHS*I8r<`VMv}q1|W2H7x-;7IkU3+i&|;-8to0x_>4z z(&xA=@roHl&N}(8Qym-CGca+|qHNb>3lHDrfHF98TTPGtcB8{Dy+_``k@&IBYMOOX z6-@=5>SCFwNT-%(bJ>+uT*dbmXgOM;!1s}ODprv=fH%9;g^A0YuNk>Q{KPjn0K{3e z`*AH;5&w(;at!|nkwoEp9nzd8y1C|gp2cDT=*0I?_)ANB#KE5-IBh9=T0XEM`wsp0 zGHftBUu(GJU-9<7c5*l*G_ar6Ws5EKXZO(cF1i=J+ikP+sC97h`$qVGIoZFs=&?@p z;^Lrf>qReaj{h#x>4Hu3GBQ3zm%OWT&FwjA12Fr z56Pqjyf%%bQwh9oJ6cXTXY0JzR>Yibcv|E%JAXAz(N-tb*dyh+0&_5-ALU$GAB zU$MB1!nTOx@H_FZG^CS&I4P&y`mXi(_%L1%$7!qm!{g?15VamhfBPZ%E_K8t`p;}Z zd@@n|^!uGiXDk2lu}DjxO3yHrNI}#NpA&=D}Y&lp{D28D&4AYF+!iCVf8V+s-~6PSGY_Y?9e{ zlTG3*-3%6+;iSJ|Iobvk<7Serq75Bm(Zq9g3EkhH&~9^a^yH{N6r(7-mHf2dZHtE| zOP*bOll6EtoR726(O1rshr&5IlFy$wh?1~5i)S}jN zRYG}9J%Lny8%e7i*lQ!i>KvPVNyHiXIl6)081QY%6Y0l@=4L76PLzHwc_dlnl6MQd zuS5o+@-&PXof#}BhN=Qg{x2;qgdIUUOUc30q=OhmVufL)pTBzp6%$Rt*Rv?iI3&3E z=)U*(sr2LHcszQq-hmsH9D+pPvPe`F5|nMw4ch{buviiKf~fqa)1%3rUHUca4*?ZA zPq|>wpuW1~noHuk$=Vos?H>z#&;KbGP!9VzgW#yrkSCFE&ZMn)9!nn&GgJ5E9L>@_ z#zUpwbhuHveT$cv`p}-7 zHG6=)z?$5P*Q*uGIf!oDOKAZP$IClE@iu`*i**A`7|;h;uA;gD5)pYx#1W(%CS6~0 z`f%z?*$L&U;{DYO_EX6j#_Y7qQ4ThnXR)g(>bJDZZ#J=j*NlYHH*R$@J0^ntsgDs;jSW4(L=vy{ zSi?@kQ#$W=TAFGcT3uNQKp3|oorPtO zIN;M)t8BtDxKkKuXUQ-hW@#t&*9OT}cSN}gi3N3w!pt+{8WJ|j59p=Gp3v?#ED?i~ z-h8L)%r|SgojV*WyaY9<_wd+q_=&wDnz|VTO>j{SE~Cu`zj54wwE1SGHf$I~l@e(A z!MF7amefW}1h;z|?UU!6b~IuRq^0-42M-*%LE-ecj!eZ8xy8rov|Cn`x_bA7tV7Cr zq;d;JCa9eywX#i~r2IT7e5&}Rt=7avv<6m_yDDy88cwI#0`M}yP0RuApcyiWC?$iu z-v2S#*|3V^7Q<(OyII;Uh0brqr$>OunWOS+DWZr)n^&-I5ghuV_=8hrXgIF11Sj0DNg|XF)koob_$A}L*T-o z*iiPI(m{j&h5S4EV#wte8IF_mBg0;@|3ac-C8}V3IAx;YbcxQ`Yz90Xx1L{MN6S5# zg^sKgla$*HCEBiNU?TH0F|rqp@b$_;0NY>NJWjuTIUH zUY6b+rk5-GXhK_PP?@oV%8aGiIiOzA0&V|X)6w$wEGEvX-yf^n>e5Vr7LwHR0*SG~ z4bB;-20mI(V;NI9aP!W2Or?7=(nR1iVus2=Z*gf2sguipU!s)n6cAk#mOx-o10A{ z24UliCYJ{Y|G+i^@>rqv4!ZrGM@;wK(8DswS{?fEaCu(EAGS^4ln_^k!>tyvWnk)8 z6F8;Nv2X`MwL+BL&Gdm@o|}r)6{B+8SEqXl-tORLp9=ITz?=90^NGNc-OOHa*KUT{ zClV;Q2GZJUZ{1yJ+!b5_G&Rf0a>U2ac3V-J+O%9^0k0*Z z!Gwn)2>@Ut#wD6=a54@Cw5HG9z6T9gBuSk?Pe?~1@!4esNuM^czDNAb%LFE?m0aeW zK<-YTtym$5EQH;F0J3E~bb^+iucx~?@*G-I8{hPHzFt)p6<6eKq^!$$DSjHJ9&sd$ zpmSGf>AF|Uwz_+S@+(nT^HQL!V`YwBabQXj>A&CzrpvonKI;B{;r1R~xBzCRYY(DD z?`R_4=i!k!5+8B}+WZIHkUum#-bI^k501o7RFLal999vyfaTs`VuEH6Iv9(67r zT^xB-N?$x@Rf5HO2aN5_!#)z4V+0`)3%4%$dox4;DiXte6flR3}K@5|ob6BA7 zF?K3(t}UXAEz?8IW8{b`yh`X34vzky`pIrm;05-=j0jr|<-!^}nLp7`U=n$yWFEv( z)pqeibzF|hn*Ai2;`(_1zR6$5F#`;+#9VU=sAx_!+Yz0F~btTS74GiVU zi0>-owzL&~KDBjIIl|dfk7Okr_fi1vFtu;{23)F)LS{k(n;P|PN|=}|28PeW|3vUE za%q`C9Hb2)JP8ya*TK=nvUyZq3d&f3*-{&A)KGr?kex1A(NDB~zmpknfqggy5#8P2 zY>W$mCA~P&9s$kFjC5D0by8o3W!3i?-&%q_ zTE^xGjY(U|v=}f^>g;LJ*}+ssdMVDN>28;g3=5DDA-|NWS;K#)bdo2noL*upAUfe^ z4F5^XBUvu7lPKF2WqPE2AgVhLzH*CV{DjC{c9GD40DuXTim&oZn5rX=Z5^bFw%x)`rP4CG6)Jqd3US&C0d>am|~@%dv2%_c4Nym0HZIkfICnuIbcdsh^~BQZ>{TQr>M?r$;P|Jl_0M7^U^Z zL{f}Uoesx9BELxr^AU@3q1I~9{ZEVGPcnJoNHNBHhC6UO{tZVJ@z^HdkDhll6pLJy zueC=+6#blp4mg^jgsnW1uxtl$cbw}oFS^@ctHcwPVR$ASZ@_}_p3oyaNSaeFIXvc*CH!b)wV z{09dR;{V|9w|Q4#5*0U5y!^Bn`>$f2LwrMTtQX@F=TVXN>re}ev9!GiKPfl`+6S08 z-fSAmgIP@yAP%}|1|yMA?_Zt4i)sol8)WwQrN;=tr0{+0HzkH5W%y146G`$5c!`wu zgG#f-;027MY?QvGW-(f$OvNRoFqERoN0&&Un)*q~DG`*kCF;IFO0)R|-{^T5h0${E zhPV8Y(~M}TDtO59`KT}hs&uQu4mkG99WINIU}0@v88~k?zhY=AzWSv%!UYeF*1o-# z`Om>Tz?iFaGH?tAsd7s!f6J`5tri-|0wsO_lKODCT%h3q*KL$>`_5?tHMn_6$Kq2> zafnM%O(4cu6XEQ4m${XpW)Mv|Sh%bOaJ*~8&~UX5suCWvLc;yyvWkUH(QcfIG!aSC zt1y0rY(Ux9NrZ0qXCOFQ!h~g+`&CGcJ5!)C^mvI`U0~@Zn~e^t#mm|&24OnmtVLi; z`8kY$QuI}oMofy!tyhLq@Wd~9sBO@!io@w&{JZuM3x!1E*V=~=r-*s1*@#<(S+OGt z+mpgg!1rf0k0D!A?v6DN+i)zV`8J%Eg={+_s>rxWfX|lE5x`c{%+1ml8O$v(?DLqB zMyY@3%0XW_y&88EMCmCq?39AOy^M*7N881%({ySM+cZp-dOTEmawEbbvgI@Ef752> zr#@e1{(1}B=|52r`-%|#bL<_ustkzaokw+rXoWWy@JNh{ zFYO3l%?+Pboqzuu^6y*uzo~eu^lvEKad`{6>eL5*QDM~T^@R2@y$IW6B? z8vG-Asigu$lpjeFE6YYek}?IVhdbrHN+l$IkyEDZWQ1%HJ7~Ap5`o`w)ElxdRNz={ zmS40`G}zQ8($oScHk;L=Il$U-9x-N6=!|L>ea6{bNW&FR@9B9KUHVxRh2) z*=CaiRw}0*FC$)1iOKJ;wyd>)^*y}(93Ar= ztJuOo00N}$e^d8==mHN+Sl>4bB*|wA~fh+tYOF^3)!GlGNHCi$@M6< zsIp7El73Hnv{x|i>nK!uweM~3+N2V_H)*mJ$0wWS`Na(Mu3E#F6s0>YrURxh+@Up) zg%QWA{)SHJ-+!_b&E4$Fc+Sq6aauZVZoN`0X8RzpPlX>mp##GnbuZHBFz?-dzdagy zYBkl(ruB3Pp~_yiOrm+V*S)w{JIx$hMfv#pUZl-Xb``4=&Zq%^OL3Xydo@MqqW0gQ zRi}Kv<=s9Vq&0clQ0Hr=m2A3ga(Z1%w;WYq;Et zrZia$p~5dXHf7X3?mi5Z+7WT^5|qagjgBBk<86M9i))c0-;IXRuJ_M$!2p*SuU*>VX< zuCVERvyIXG4%B8>bhDri6Lb`Nfk$_2?2Y;tov()JOacd|psrc@?bC}^b{OZC`jT;e zK(qS|$&A$Gr1p=~Q})Q^oV%OXo1fl&`0(oK>t}`RHoB9F(qVZ)?kqn~SMZRLUj@}u z-bgvMmVh!uiX@&tV$R#M6<7*k-D>iRudlON=GbJ`E)h?wXootlQ)?p2DICxMU~80{ zCUxg+WwpIqy(dik+ck)K3P@jC{Re83PH01yhSGf*TemM`k?*_oB$aGaftIl?#A3?m1vM|pNOG2Z+?sZ`L+ z5u(MZT;FBgUcl0!B46T_jaSPy17jmI*5xbGN&48ft+puvfVp=t@Ouf!jEvz|(VPAC>t-(sIy;;``NVEI^ zM$O_@@5!|H(te0o=0X*9$2Kya^@m9`e)k@|VJ4yOg^nXZ7I(V2LiA#~>XT(BZ_TvJ z?3VCdC*dETk@@c2{gc|0U~5H<5T-%*9RMoR>DzN|c7+Dr*h^iXYdFp<8m&}UAq^QxPhXzo|LyMe$E9(Qn@aN*d@y~P$CEs z)zo9>{eAQPS~sBXwQ?D2D1$If zUAehJxqcPNxD48!x-*O1K6|17Idr$p>rHTiub-Lx7zX zhII6uFz0@-dHGs4Mfyk(RjH*#h5^>Z(``E2yPFbfTn~ohtbC{uk*eg^w$LW9MEl(L zxuJVH-JWm3k{0;2{epT*ItkzUtRh+ah-(K}`hge&Za@lG^%9>JB-IooRoMa1lox>2za)OC+YbRTX1 zOdoAcA6e9x;WvWR66Yz89)%+ArzN4Fe#oEM6@w`xyuDHOnVpb!$UzNv`t3th8KG_M z2&E-YJci(6XFTKopgr;WT?9Mrg^h%MnQ|U<}PSx*OzKcYs zf)t6T(jaY_WNece-U3C!cVO-2h_eqLd?x`G=co6SxL#1&`)42CeR_ZP><>fp!4vYes>-^Zh1<>fgek=~#l*LYJ%cRMKzbaw3 zxtwxBoAKKzMWMHImU2Q{0=n~C1)RGhzVpO0`X4v^Jo@bLt-0e)i0VC{+mD|_7N&eB zX`R0L=?zCH)R*nvR{T~mwm57*ifYliHIW#)$0rES)ueUocun`8F^5F$B6OLPk7u(5 zJxH?Q*y(X(MUNWWRGBycTJ%?$Nc-FMwSSqy;Dvvw4_^3#M7&Uc_0Nm?1|B~ND?uQs zjnCenzCC^OOf}QoQA@YtHIVe&j^F+L^PBYLV6u#Udv^WeVXx_JDD@#fdDz=LeCR!T z$Vtzdo6pV7o8~5MZeBJwgXU(^+$@`$X!G0VMRRlA^gNiZABo0~Yy^MUxk#G_N5Sas z_3<)C#P}aQ@rp05VlSAKa!W&1nfYUiJQnKmGe@W;zKgT4pYUR4xa}JN?Y-1@a1iyX z#EE{5wgrgLf&V>KK!=q8WJ*BmbmeKZ_u=Okr_v=uX{dWB;Y@|jN)#+O>H_lJ;kx*I z>&sNy{AUOh5r0UF$g71<_ zb+6-+Be|){yGe{D^FeyH0_whM02%ufC&l?#h%xq(br)#3sCJc0724mRsVpo67S&^`rv{3 zUY`i(0rjzOSJ`Cel68-wfv;%+STV-@Q&EWzR@oeM(63}G$|1k-riCG_ZaD~)y4Hc1 z)UAnqe(p#jvo2E1>FEGGAL|$<-i?xhAQb?z%~3u3OV<3u4gxgBhf`NIFeH)8?)xqxG2Pb{^Gc*VVz`3_HKr&t1JGGfiQt+( z`a0ITKT%nq=oF%{68#LnpxWLN#lWG954*?h-bG43hu_ifNBVtCzdrr`VEtaSh_>Jm z#gz=3--rM?VDGwoHQ&H(j{ht@A3zpB^!0C#MSFX3{pi2F4gfDa7cc*V2p_sV+5qnR z;`%T9eyh_AAN=*d_b(1Fj(XA~FX~gl00XD|`+qt3>w|}nniof5=l4JUpWDs$|7{)i zn*XgH_gr;x*n6at@PXF7&%H+nM?(?!6(q;S;pT#?MI;yfp#*C8BRZ)hK*yKV3i*uR z$Hgx_KCL`PKxp}+f0e|fN`AUqGNm@oRPr|!eV&H5 zWw=IT0s)$09GC8cA%Zw>1){q66BT&Jp&k%#W0(GR9WYq5rM`IsZ0ywOd4iJ;-@G}b zTaN*=Gwh;=OoBS56r{-{>M;~YfBDtDH*faRbnoTMy~$*6xupL}D6ypvd?en97@y@5 z0iIx!M}_hia6z0b7kq|#zI;g)oJ>FkEcwrp>sopL8m1+99~Tad!!C6iD`Y2I*Zrbk2&6Gt}_JSnfixHyEXTTKtuSaf{r23r3UWx>_cCakf>AzfGQ!h=x+HyBdB$NA; z`}t{kK3G%10nBK{;N6~etJGLx1l1Xsl_OZ`fX_&;4-Ql~k7cD8hwPpg_hld}y0>D! zM=};w_SINa`=ugf(y!&vR8&75nX2qGS||V&oNcNiMx}Pdr~zv6)re8$rfoKjb9VrK ziGQ|cQQxZBB1(s|8pEk`ngmR5FjObw*D#?f zn$1P*-NR*rno~IBAZg1{F_pwuL@?0dAfs}5?nZ&M%L!8*-ZHZyKbGecXK;imivcz1 z?Ra)m+wZ&;DpZbj>W)Q(zz-i6>+EM)Qrfa^Sr#_6GCqext(CL5XWIA9?x}W&-AwdK zvU+-nx)y; z=}A7m%sZK}A5&M-PII1ie{=`3*L$>IDZPWv2SrpOBnB1J+aduu>B8H3!*10ZA}2ww zea`-r=%+^3)+pqUiCUuwPpbl-mbxJ?q$_IXM=Ih~Zr<>=e)XL~z@l^2Nm)#hipdba zr5G-)0H}crxGe{7+i9C*LLpapI^>u{k{K?vz_v-Is;HIw)ij?$4@{Lt3VUh;fHkq3 zgC~`>W`m1Ij4+g)BCa>d<-rMnGFSbbtta};(q%0HEDa@TDTHIWAg404_jkqt4>1%+#lpLzHwyRU^7P*697}4J zm?3((nv9vOEv3}VDL0ZxX?FN(Z23E#o!vQ$()(r+SEQIi1Dh+)T2Ps(*sqKAa+Ps* zw=Yqx5nxF5+f7yxqn{RAk)t=F9Z9R&$J7RX#u1UV+@reF@?ywjOk`>@=jG3Pq7i4| ze>e;n#~G71?}$GWbeqUq$W&Mblx5*7s%gE9KQW@(Ga|X@&{`MBOtP0DKTY@$hd z^OfyRM$3?TPAxBU{@dCHe>gry7oH=?h)Db4Z9HDHZ%{r<)=St32McwI?xpcqUNBra z;#te9^?c6+9@^7^iR1<9zE|L#?-fAjdnzP0eyITZ%L{8yo>zPP`8`x-kDBlq@vHY_ zT*bO*IG+;*oO6|S>0N)Y+xFx+lPFdr9+klUfGs_Wlk1CjRsA$52*oV zy**&*9z4m2qUv*WLZ&?j(K72Ka_XG|sL{;=|wM*=!=U-Frlr?V&V(&uPkXn-U!jpliT+PPVjsBnTfc z;H97v-uS@6cSlwLNY@0k7r;>)x3rj$d42f+h@7Lav5tvQ;qn1t#xvO+d5Fj~-3<%B z#U?A`3(EMF;y*k@D|I})r%j4JBqiv{V7B;>V2zd}>R;XUpPkuc&G(fts~ur$Gf05Z zJ)7TqaA3E7HRVDOJ9W7L8g@=|8?l|R6H_PAdAeTBmR$dx#efMh7nfq8qof{-wYU-! zab~ZCE9&8^VnnW#%4JQ-YkNf2>cNCVPRb#e><+<|9)hcIA|~=3Ccc$oujpAdSLb$R z_CGVzOw$2!3Eui~-vD@>coR08%Yfrc_Obda-H*~%)^|nN1OtWj(3ocuF@WUvG z!ZN<7cYDT0IvP2Wcb8lhKgZd-4wGO)Z&HV36YPRSJMq9&B5+CJjtswA!n*cuxn9(D z^eM1vwZ*WR4@v=2-(8v1?3JIR6@>z==2>d3o;c0Eoq8=#=tInC>oivrv3i~xSF@uJ zKfgFq%rzLbf^_i^0F^_zz@5u|0qRHBR{Y{wp%%Q#sH7@%03#+eZu2$OIQxnB6TO+H zTNyBwX_lf~d??j%9UvqG@xnbalB7VfaDS-bM=Zr?P0S68BzZL*uC)66&+$OtOeo2d zO9>)ULpOtD0y!r3Xc~kHmpH1d3|t*aywTi87hN;NHMRzq}%WOdJ~`>7K3@oBo>_B1Xnok_gDx%X&$4d=?PTCBpkScaG4 zId8N$O?ey&bn?|Z1q@JGW$uL7RSh@AUV4271o*&z_42bm+jLwH0Yocl2SlI3aC%kx zfRudBQU5QDZ36WQmbG4+={8taq@`b6{=}YNM6F%YlTwFVN$l1be4?An#oAd*@OQ;3yCs1qMl1Ks1b`h3Hv`ATCy{LOL!n zL(eS8*3IU1EUv@jV3`sRxLSt_HjuG)5hg#msbM+mPxG#HmjPX}j#BHlj} zdfof|t%g&ytUc@lVq#74NqGVEEAi3a#DI9W;;Mg$G`zjBE2%H~xLkC5r)VmvTr|FQ zRut>nANph#a`|J{YMxY&PePbay>0}d%4Q*Xj^eAA)x(c zlSmMNa&^NT3aRp5A%mTv;A^Z-Zo$VHAAhc7%*-)mSEdxsPr3%E`@PK78y!$q-HCma z71cBx)Ieierl`mu=(FQ&L9V0-{qAx(mE*4>0R@_tBCFXB$5bdc6=Hv3U8_4wv;2N@ zSvAZEV#N2q5cdmq!&bXi%OMhxR;xRJfJ*t^jCa^CJm>IPC2=sX5_cf2h@>!4IoXUb zH|WIJDTt%biBr!Xz^-N|;sdtgYaU+9(oG^ZnyegzTxDb;{<_%=Wb?0s63-}|L?j~2Jp{~H=Vzfc17H5U@&Dp*Xz(!?9k|=bK2^! zp3W89vT&!J_NEfpyA8GT?sX``TR6&!5%~rY?HllCEE?k7{eH-cTptD+ioMwAHC8kC z`RO$MxzH>JcRZ>#ExB)t<3QVoWagd%&3Km6&#gmaXxlHw_oAwaO?py}D zz?^2En{9PVeej@eM`fh`68+5dku+Z^@1yy*@1rFOJ7HVA@>i0bT`{|RSFG8alW*Fa zq2}1xl%q~kvnk*6`E4=QJCE(bUhdF^YCGQL1Ikf@7N2h;notRgf}lWRO+0nON$rza`)Qi@mF8`946u%sE8)L5}@>A3XA zMuJ_J#jmr7tu4!g99JtDs|$Ul?7tG4P^Fo$Gyf~kB>{m2T+Y_0TTNHQtrZ)^#AZHe z5cC;_Ks-Ul9-U#@&3Z(rln6JfqFR>ULd%7@u&6mvw;*#m77MTq>ZqQ$VnJJN45xZw zP@%Io0wCx2jjCwEw2SIFVAY4%+rFH8N>he^Pgeh_ux zbxae~=6KzH_i^vR1N=41!wYCS7B=}pDW9s^^OOvl`K$Le^%s(H-JJMD_c zRlDMWjG~EH4rMiIxn&$;(%M!AySs{q*ne)#15s(hom)=D{0EJIw#{ll#s#QBqCGvT z*GLU)X35e{mD7!_w8;D6k$bUhZhrUF9>aGB-jVq7Rnet2`)sk85`onjVOc^EBlS>% z)1+0N-Jah4tDU+JO}$(HOzrOoPG6rPe0#^6Cf;7&w`Way&o^{T2WQXOdKwwwp6pw= z#f_z;+13gb%l6rKM2b!_%a83oVW95RqucFKpLV{Z->*Tf!&G+Zb=($=aDrWd{%KXH z%<@Ewp&CoaDwszolY^}^3!{^VX75T&=}JnR@j+VKQVeY;dSXtQX6(+m*&K_@FjwOY zL(to;SV6@-4HE!SE><5tPvRL6P?oUwrCrt}tix$@#Q4VZ=2BdRT17S?+G0@K9htO5 zmZ62{_;g`|+(D!Rcu@Z6!W7vPm+(w0R~+XsGlK4=T`;Y@6`datoS8|I61Rn{xjqU< zWi_g8teJSqMp}}es23e~K#07gb&M#Db6V=r_zZau=N{s3(eb$1q=sUpE_1I~3uivK zg2H=Dvn5QlI&+$p)a$iMOWjE<)_$MOvuMTfAa{h!BQX_oNW4-+QG|Xr>W+Hhls8$4 zYoFkf<4T7>+~ZrEo+0W#KWyWEGE5Q6ec#nt;cLjb&Zt|c_eY-nx zY2N-hjX5o+lJ?ao$?vlz=(lvg(2;hRo?B*su6w|0Zh^I-5O>+JLWVI#tkY$4IHf;D zirew&K3bL%X}NMTH7FU;Z~VFPm>n{tU_e7nq^>i}ZsrbPzod)P2*&nv`gz==1vdgl z#EO2t??It%>PKHftk&+u!NoPaaaH72bxYTxa)04@b>AMQ{Pn5)`jWr5*>D}6a~<%; zB~6_Dqu;v+(czcV!{7es>8sUH-y?KWO;-Cg#=G`QC5 z&EN`NQ90M1vNKu9csQCUKf`$}i=6K$)kk8EJp}aH3u-5$C!C8{;#7PQFU31?6I}8_ zzoe~^2mE7%5Xmn?qAjr_ok_T6zWyA%gf<6+_zgC*zSl=Mt#{W`M10O5J-7jI`hl|_ z-q`-JFQt&`#tUAyma_%?=gz~%OU>cmgr@*3anB_*?I=7BMknXNNHYGfLcoo_?2P=E zv>l04BGF#;#FudHq7@@gth!%%-n$S#LdS=~^)>wosfF0Y-Wk+sq^c=F&8X@$qZZRN zi2mA*T0`0iL1hY>Jn4E4sDT0X+o>0Dp|><=z9Tu$A5vg+=}6@x`Kw`i|(k=$((!T3w@o4!M88|c>nCpyPuz( zgEDz1t8Q6pY`?iT--T$9P(^sRFT$%Jo$b8{UvP|Q6%Rg;&>=4#Ja|$1x)-P>Q499N z7tjo|4T;rOX-e_by@F}yRjBi;UxS0+3o=(#f|ksZ#`ihR)pJiOn`{#g$b(f*S};;K zK9;4S_aKK0qHbhaFy%o~HtdkKp~a!AA4W8G?|9VT-Bl6j+pe`$#q6sFU-zD6H5L?X zg;>~XeHhHHOJqjWO*)RHx*-Zj^KK4sR>0T|IMm@SOdwQDM_(r&>bh<<1u^*FZo*6Y z>`oIJMXmi(bN)wdm@|Q0W1b;v0S)Ab)#tG-C1dMATJBOev2fGMvqT2T6>Q}iu(3nJ znABSqsuMB!C;IJK*&g--NAA0Wm$DcU3CKhI9+cPH-dzSa(nhRHLy13Q{2AfTwEp}m z+0??cBY`V@8(Nt1)ZO0CZ4fcKXlHMXmxGr|IfJ+Ru;os6z>WZm51`O18U{JAwQE}+Czt zqz5ZHb>Cr;+rlH7>OWxxJHwf5OsQbjqC zr$@>X4Y^Cvlq_r}a~b$u&i934DDNkQAKS)7zZcW){v~XIhG$lvr7gSW#2?)K3|P0p zJHLd~;>+{rtU&@X)wJuOb4pL^l z?q#(4i`jZ!&uBGiPeLNGNsU;hQ^GCQioGpAEJtw z67@7k5R@^b$#TTQ@+5jgb6Greq_}KPI#@kKpl4}g6HINvJR?y=EtV4kbwY224<5{o zjWWx7*vgdHkW*^fFkY`RSM?1f+YkrgxlTdcZ&Xp1P_3nossg5Fu2N8fWGY78h)&f~ z9Zf(zsV4X|9ZhG~Q}eF+)Xt-lGARk2&Ysg`zOw1;b(~D95zPVO)Lj=&rRPs=1$b8K_Ky3- z8uBlNS$e|V5P3ptz_04M!}Fz;7@l|JD@4lq*&PG5)!wSCvn+K(otmt^YH-XdJGSz7 zJ$Z1Oz9}ubEV!qC0A$dQUAvbcs!Uq>kfwxMN46IZQ!Zwzf0n*mvKs6Qd50!%2;|t( zS2i0W(Uwv9(;_X@VGU`LTxNHvTa>>f4V5c~!aTmn%ae&`un0DI!VcYI}Diqcj za8TRdk-&lvQIXYff;#Hl7uR03dbCi8vx@%7fjr_H^ z5l_WS@l5;#d)}*XOe_Gu^A`G_PvV#0Jsx0Op3#`R3{SOc_}R(J;2Dj}?M?U$ql#&8 z6P|XT^@NP)XbG%54+rURN_Bf0uDdrq;$Q~u8+@D{9|Lz3@_l9ew{8qyHCB&Qv`nh1;fa5&wtfHRcJUd5#D!y>4i6dqEZpiQvVk* zj^b?WKNWg)u(R5VYH)2VVwar=J>6PQWzZbF2Z8Q8;dd+6 zVRzXRA7lrt5#3JFxl7G%!QEpWexR=k;u;~}T*{aC!O@#+%zFC*~8|n(^qoZACfQ?N6R>O&U4bK2e77licTNLIi6aIqO+KlY{C)6|R&CL|jvh zrVLmRYgWaRn*a`qALQ$5vue|AUI({-?@0*o+g?w6#CdRy)78E9fIW4+(@aHl@!(7)7H%%d!IC?2N=LwZ+A{rABX-jc9Lo3ya>Yq( zr-93-JsqNa@UYE@4u~tuP^I^LbrFEC_=cjk{VLT;90pr`!6QJ^rgP)CX=$5Yi;-8C zUfUtm83^(l6@-*j`k3h0HZ>*k0`xsS$t6XFa~9{QuDEtUiDalIXj<5dKJua}ufvWo z)dK8TZPbtz`+VQi77but@{IvR4Ao(bWx%by41t#tj6)C{+u=FBXPJ%81i z>dTS55SL1;@@u+-NmBK2qH#9c8$;_9+uB`{lly^Zn7F2XICnG6Eh&e~NTKknNIy9w z;w6L_{n!`++L{DSvyVc{0e(uPMk-yDZzri9LfnCSKs_tPW`>88gWULc4fb{+2yD|?eVTmD z8qElQJ6}Z^VPAEXA5tJu1`#t)>U1Xi#haUr4U-2$02_cy=c=9)R~9%M+AqHRUu>F; z7{`+paEk>FHL&aE^0EXIHrGJZ_NuyJQP;6yVwoSckgA>EIojm`?KFYh#`hyW;B67M zhPd|N0l3KeOgQvKWTe<(gLZ`-yt)dYo2p;E`l{6XfYt++xq_KXq=}O0HaBbH5h$|Z9n=-qfKG;%|3o2Hy*p~iQ%$x+3u(ROYJ_c!Yg@}E z@8e>~k^T07ld-4X+MCeK+*=Vnw>O(6S<4~b<}ILue@fjX@&ui-UJKFTON8A&Q}-9K zN~F`2dp(?{vulygk~OoU!M;o}H9I&+mUBo4E9r_xb@iJqgfR7tl=tDu5_;-AjsPxI zE{Ij~!zhIMXN~WRH)Vc z>1^e;Dmr!|P>0L;IQmRar`g{Aa5A4QIDq$-YXCEKlO-O{l2NcdIpRharbwBj{D`gZet!e-0XlNep1sHCSO7Q}3d|>0HP-ldSdj^~KhAJ6rZ-6^neH#P&z`le=;r{=Uj&E6dZl$p7w@A6sAT zzY_*4Ocd&=%;zdYe_1xT`j~T~Y%(~sG+4lX^sd_eTR>K?H+dv|i z^>mfu7!2-x3g{&DX^o(9S|6UlDHP7-Dx0C{Z57yfKTFcwb`0?l<(UvRbuPYI$dzD) z+zU%q$S15A$ua1}|BBSG#`=4($#AG;0!(iR=sd9N#REU)#fmQX0uS)p+aAa0C>Zy} zjHic?$o_tR9^24Ig8FGdP{rqgr4OVrN$Yigk|vA8 zVhx-3b{!Mb8tqk_&E^xBje%8d+XSgL^+!N#bsqRI=yy6eo7pe&-sdcok<4Yw7_8?U z*T-vo_tIMN-I|p^4Ot7iU3_+*a2OWB=WAz;b1&%FAWa{P6I${#ldoY819x{|?4ncb zqO>k{EN@%(v2D15wfj(MD+|_+Lx)=-xFLwDH|55f1}U6xb~6=Z4SG^THVkcPdkm~c z8$sKfKrf8-$xOD1u~rg%ks{xW{FQ;cu;Clwz8H$|t)(;oZwww-BfWI%i05)l@CSwF z1qN@=j0uJpt8s5tpjW{LRu4*6LsJb9ifQ#y}fv<*oW4YvW?=aD0+1O-iZ>O6$n z+SN8n4U-mpYo9K-7Hnn3v7EenH!00XH2@{^O1mUr3`Ol1DBQqYPCkEXBg!V9ytdWy=VW8K~W@$mI3hU%1{m`~i`wjszT|i5&?p?`QY1LeshBxtKZm zEKP1(zj50V%i9blYKw@}+azz@0GQ*u@WtaJ_!*Kn#V-I!7c{k!s0AOD4c*>_=E!oR zWcNLx8IJEI>z#GsQk{}-6TdX)VBn-?-~j=S|9O5d(r=rA`JM_gQ?{Zxb?b>4`9X} zV#bgwv@ZE_)Nto0Kgvt*SD=>}%}xuEm5cSe6+ z?oj0EAOt7YL_C8ELV6MB)g6b?p<)LGAu!rR$R>%K)rz6c@qtr}2g&#gMIb&ZHK=xe zQHp?yP815P8941IC=3mb0g+sXjE_)mUAc4^-R96PpzI2NO)!+8ruooNX1|UH+5@kO zdVQFkqQ&dLmnLW@O1{*Xr#D&}QddQ)!i;Zg-yocdRcN$H_!;YtM+7u?PeMXB3*U(mqq{ zDmecP^8~s+cvoTLiq&FM48CCv!iKSxTJ5=WDMxCNJP1sq|Kgi`b$DC2sXDZK;V^P+ z8r7L%3J1Me&SODRr_I5TGg0FBHd6X#nJr|~%Vl&=3fC7&45w{>yY#?;-7z4YHTw^6 z*AuEwgry85YG(Wujeo*s%DEt*u(t(!M`S|+m=-o$)L83m9+^WH&cZVO| z9-m#PVSBTrDa#Gm^TT)VkL##i)bu6XjB+=Jl|#_d%*F%vHzYDctJSg?nHD2p;YP@# zB1N6at0)^iX4tNeD$3dzzYPP(Edy%?WTQ&1!^GVo)#xEX!*|=7SjxB|A<@4R4jW84ak1lU7x5qta$4qqUfOgO7-I2tVw1pM#X$eCR19dJ2QK9^%AQYH8tfvZ5CuiDy zA_ldIrPwzGamWd~mSY4OV55?}px(-5Z)fUSe|s^g=lGZt`XEdw4}PTV5YG4r1G{5q zsJ@`V+5@+Ao*ce=c&e?@kp7o(7^9BAHKHFk80&6ehQNlWKWtPJo<`K*p$c7^!w3iT zT1VUt-S)ug*=;HV1w+eXRADMm8(teWbDSy(7B)ExfX zi+}7)=Ppe%2ERX2gMMT1#eo{^nOXZ$!xFSM)C&U>#Sa68;(wcq?W;l7Tus>cA0&rbT9l2<@1Kl@G{$hpiCQOi0O!vgY|^{iDHT4NRlf! zxPKjzOZO1`RolakTVbZV*Xx6BH6itqp-c>&rVSY^#MyFoHbGC{yjB54wl8^pQ{a2sU1fwffik zohb#!i8_>ZsDySNta6z(bf_)6-RTH9FtCoXZH+MK!*{JDnK2MZ1y>tqI7DuNxWEvD zu;EP&KOOj6WVXE`r~RUKZ#yL<8DFU1r=eBm@fK6&-m8gw3Y>L91TnTOJ9R;=_E5zD1XQc8*iOTI>EZxq)M8&GO0Nx+e1 z&f}ZpN)lF@b}kUHfyYf8R!2Zb=aKqSZl2map2Nd52yp|l->xKrIy#0i+1MwouuUu> zr~so#!xX7i2m#Z*^XJ*C&qv26$G?6)c=zt&{NmUB_w5}zgpY_B!#)l_w?C@0uEJ>n z*Qkel{Znx^q$>pP6RAvwGdjQ)aUy)_oAUFFO%7DS54a7t6_;g5RjL1MRW83;@soIB z_BDhZBGRN=7YhAPlUtb{as)5LfZAFHWnG5UCoqZ&JeAj$OG^$ROZY*5HBS*zZK_26 z&PzN9+fH?+8bDslm}eXQ&%fZ|>-aYbjTxD9&V zX*gCwIh*J@uJcN(nJuVL)!1*qLv<-Zm7RM2M40@k%G80fiG}~~TXl^IBI)B~t0WQj zd*Ds?Cf*eJVd(SG6kc8N1|}nL$o-f30vNYN>=`2QkZv=?JT@FX83FGJZ@sdRRT8*Q zYNw3`Y`YgGBtHFE-sR`>AVT=->HP$$U)*tEpnJ z%QT=AALNy{gh>9YHo`7)8=cN#9v`QvUpF5gzt(cSNWxVl)kNJiO{BK($St4Nnx!rw zZa!&QoU||w+S17B^eEqbl1xe^!Xr8MRc268hY_NTJ{VNh@hOFNWS71zg^Ll1Epgf! z+am(2lCE)my%Vpg0N0)r1Vb|81cX|^Ssv)vr2?uO`9Z_PS=z|rAY%XFv+n$RC0CLO z_j9_6528d|Wh`_HP0rZFB~CEu7N$OBgt4;wRyj;TF{efDp8yo2_@2r1W-jED@5Ly- zE3fh7s~RJ@NmPoQKlxk^qgl8Wz-nIrhAQB~GrTY9bYZ2Y^DNgS&!fE9l5TDW!!miA zL{R}+cQ=EF;2ci$Mi$V@V;P{;ca3^7pR&jW_nGNMt8lM6_Ix(a)@yIt?MC*rzL#3V z`LET#`dUgU;4#$zgVMLKP++m;J^O{b&~apN?AXOABavi>7}745?YqULCQIE?ZkQBq zoFq$j;|6ogI|$r7d6R*`2|}7QS*SJ_F7_Aa^ZcGoxjWB3lo>WVLGP$&AEB+wO6(Py zCk4&y3>|H+*#(+s>Jo*03D7RwKfAyR*?yYxdv@yH$TXvQ9U7$iD!busL@rFgg|Rz# zg)~*SPhp*rQ3af2`#~fty1ih7wOg7N_7zQ$E!iI&)~CaLSg?;eukDa_O-~abz;V?{ zV`JP!=+-Ya0Y@Gc!#s4>8W12g_}G0ubgHmCbUGPiH=lo%AJRDsLUQ}uT%`(yro!o0 zGG#w-CPJLua+R~0aC00s95tslj&{Mq(9J|TU*=4rrNU=6K`XzHB%6kAvQ+0W3*C5n zK*v2VTR$V+XJ5+Rg48h9VAQXYY4?3TK}%BAEvyQB8` zEV*U(s-?F2RW)ztI8e4jUQL^41h&@ZnT*>9>dWgqdYYG~{eT2(H=!0$MeaAY&6<=A)SY`LndfszK{uYB1HQZLIY`#Xm-;56E>j+ zr;6fXVJO&Csw}`L_n9?`IHn*s zz6-1dWf@ONb)7gIkt^tH*5lKdZK;v5dxacx;YM!kUb{;-QxROK??l=N*C+(_WO=Eh zx(i?Sbw1`GS{NWMSd`zRz1WKolM>jqzi%%_(b#dXhSzXg-CzW1Wuk^ z`Ryxr2}IYvo%z>&foL&IC4w8L#Jk+P_Lg)ps6E^Xx3kf5T*u~o*jD5Dh`d$HuZy`=sE$DRlaAW-_0t!r>k`~?0v`dqfbGs@ z;R1D)a9rPshW=LIO5g|!z@tKAPf|WrGbqK@SH7G-c`M-tm#Tu*9Z6jd>V8~V68}k0=P}@u|L{c9^PA=Y_`nr7F{h!@`47|M^ zRZ8wj;OW(cqX-JI*V%o!x6^s?5=p5Xy+B@q&x_t;%{!rcpwyO6pE{9Ir1xZAai=fUoxZBR6&ZHRtjZ&jIzdJ{ zv;%ETCDH6wbJ1ZRxJ?b(B;ACW(er8JRxjws)9=6k3Hg+u%V{s9E2v?COAVRzqE~1z|~*vhqY^%OqW>bROBg?Zn3JrG6`E&6_ir-P1<1Ri!}cTdv`*W z9BVA0-|H=80XC)-+YPCnD;jU)urnGpYPVlxjXfdB)2ap zWx?Xp$43JmYg1wm;-q6Pje&_0oT<~K!aKG@dSlig(0CQqe6pr)yZ6gF>~MQ`Pt|qp z=D2OCGOy*Rg08Cc*2T`zWqg^Bsjy4K<=Y@t9afOcy!cJZf?M$(s#-!l)O$`Qo1i5% zZpZ%biQQ_96PgZIB51V_r%}mU234i&8^TL*|NrsL`-|OkpGfnzch$>}Zy8M2Xgy!I zRSoDFW>IP1c)w=s$djdjVp+b#D4rk;Nl*EcXD-})<`NWnH$6l#)>(*#z+Ka_ zRtKz7Z=;P|h8Se_Y82O%I{nmqbFB<-8EO|ExO8s%nG++8pl7o6>)ovZRIs8vgf}bR zmmr4N*TwCicqKtE8|t@fv>4)RtU+e}@p1R*6Y!uvOS0T7FROAwxiq|mD;k~xUP3(a z5<>Y<6>m1&s1r2Yx%tiDRMNQoNa1pt(Z;x{&3C&?F{F#8zuwAIM0Bq=Dc(~$Yi|?4 z?iwolf3#g`ciTpm{mx$@;rK8>Sdyj5OgaHnW?QlyEiFsl)RUH!KG?W4Nf0EcP&CQ^ zKJUJ&0tzJUnf@>*IU<%qq44U}Tkk6uZ#J)Q<43k1rgf;6yhXuvpa6Y6T9XKQ(zg@q zu{QR$QjcK;CL@ad@=AVeZ_kxN@@?O?zfv1-_E_E6?vrVUKpI)~ z+xV0BHH4^zI8(5lwFGx`e>$M;LoIex=!{e3)JQNHs7an8OALQWE2K%ff!ipbou)P9C`?(MY)ha&$u=uT zBty?<#DoR-J2)1|ZNY4FIvE1cbgN*%?-3HwK4-pg3n+#i7>!iP*D}!HhTFM;NGF&W za|s$ZerM=j&4LTnBC+r+3PJOhy0jwgfiA{TB|hY4W{1kbo%I=~sytu%W|BtQm`}-= z&*{{$stktrVXOBjtsGQh19tf3OZ0()0qbiGLq$?SWkpByf_i;}%B789g|NFi0yj|$ z&<{~rcA`TKK9w&(F~Q!8!oH<0q>aj8kn^Ok#)nmk18^KVw=y7P$sUQGEDK>eCkno- zp~nf}!ULidhSUX$Cm7s!iMt+kv|=E-wj?r>*{1ROfBV*r-+78mx=F{G zb8o50kH;9;+aO27%9o1#aFiBB*o765ldKBkL9+=HOP`c>R@o4UQ8$<6dCPgooR)4* zW<5=`8pU)wWAoo;y>jUhC9l&_&VGXDZ3Vz#YvizyFg~( z$pP7h1C0>kGYiX153VhU*Pw(IBpzEmva5a~tYJmX)KJsBl{^gt5loPHR{SA=_+&7=0QL7ss=&KaQ7JDAh_2EvrOOu50mt}S{ zWT#d~#6xhCNn3_)omj>xrL%iG$7)Nlayshr6>(*g@BB`;Trh=}W%jeyxKT>>G7ahl zLpaw_FySBg`Dr|!hDhS=nrbD^NQ^6a2w@$lpUr%FND@Pfs|3}KrlibVj8rYf@x%cEvK%wDvE1nkCYa7#PHLS@HeAcJ^vDqTo=Xe>)LSCEoA5qj z!FF<3@K;`Bv+-R&ox8A{W$N0Ei_-;Oi}pJoaLABmL=8aO>h9+;Sx85Kkk-!ErrO8& z#6g7UnF4y-Ag*HLT7D-xVkAbp*2m&2Vr7#y2!CjLp$J9JmvX# zN>+b+$AX7xw%#SoV4)p}{bO1h)+mZ}XVNEBC}tzfd4V;yas9`L5o*mvDyBF%5J2D- zD&2EgrhCxWMZv%nRlvfTZ*|_~RFHbl97w9dSh~3`Lm0I~Bm9oGS6cGa+qhL+o$oCv?}EA$BH|K~KyOnmi`8`Ym)9C{Fqf=6kS^VSrF_VjgVA?V)ke zKEXy%3&4R3;=4osy3On2iqW{PgAa7-XQ+O00U0}$02;SPrT<6$DU!&FSiN;E^nAKn zsU|jv`!9%k{y^3`(=}pG^#(lFI~-?UrbCtcugwwMnj8KpztiI?47%;m(ky0-V(l{3 z5|(Q*_foa0t~sL7)efM=6Tk-DAi5NVJ2-$jzL&;>YJ6R~2o5+01-8_ForYyk?J8~e z@(&kD{i6Ngd+mpp54v1Y?~~yu>RfX@MJQ>SZe{g8*Q1xmlb?G}V(;j9?=sBxj(`2j(caOK&;RfJ zcKzA$b*?v&?#>>amOhK`6OvJ$KqeyxK$md(mP> z9_+rO{e19^87-~2MjR>+IF#lp%(yZFA0PzJgzs=!Zc;DHWtX-m(<&B^GL@r~9{Uqd znI=F=sxDG)3u)(`M_t0WRIw#K+}V_9L@TajnCmroFG)M;bjLi$0f|pI54LvK&RLGE z8)juK$j&+P0$hM0R)*M2$2z_$cPQkX#@Tku9>Yg5f$NsKe|m5RkleBIUdg6WVB2n8 z{Aj2N4ojSU%~DXx+)Dnlj!!uLZ>#?I^>^*Yi$>dqLAWm)ki(PqL`sy!O_2KV{0XWI zUH=WYF*!jd z)x6N;ssy&(bSQgrpr);itVHyA{j4+IQESRP(i6)9c)jl0`0psaW3WVLyc^YB-wGwg zRw(JC4EYJS=I;cY$gG!DRQ$BA8d@RKEsvna>|#e>fWS>e=Hcej?v!itd2n-cvi zQu*?ahvLT)%J-mav66@RKe-h0p=h~>KYA}-k_3P0+IQTx|KPj#e*|>wwS%_zl8gcB z&K+m>;lbC()(T+HkcUCf8?RNrsHDi=Kv7V|5iDSGfcR?LeS<=S>Uz24#a3lC;6a@B zQ}KEoLLrwtsfS0LEgYeGlhxDi%B9A{`e8^H z*8YC?P+w^_yNBeY4`@-8*Cbl;Rnk-4!+hEwPf0Gx>X7se-C+GFJ9NawJY8L064C`7 zWtQa;iIBqzM2WYeF}GCPuF2|86u4j#jx zV*g@34RhByDe^FTb@Gyb98aSKT;6?Tt(1I6-te$OJ+L5R0g`nO3MNMtZK);J@CjT` z=9p$`DaY-bP@&ayaD(P~qk0p{zH5s1oK{prS*Ykh*rbt(dC-w1;6lYSxGKW!D9FW#=rB6z1S9(B z;(qifWBNmyd!*?zA$LJPJvx~L88ypgai2YkSPt&#DS&JBL5y5G)1ggXeR%jHLoiQh zgh?(+A`M9>Q3Eljq0LHU{7v?`R~B%zuklgVUHUqIpDyUtR%_gBrtQ>6L?GhlBF|UJ ze78fC$cZ#_dLz#UvQ!lsMLbcr>xvIZ9U0nXM1ZV<`=}3xaehOKkQ^sk2}A^b08yB% zo%rZXxE(n(CF>XNP;j%H=Z(j7&kuFf2oZx(6r-UNiOAvB#WnW&5IN9t9s2g^NCBAl zY2MJW^(mxM_ebR>AL!h_aYv%lc01pqF+I`QO;8?J^^{K%b)fVKF&Dz@lhvxTTIDqN zw&6W#clom{_einqc8*%}uv*gl$3Ov+e_irSS0+S@Y6QTY05}|}2a#1^AYjU2juIfb z5!BU}Z96Zu&9|^zP+c{Y0TLUCVTe2^xTHe|$+7;uB`SpWj{Ovo*-7Z$A{bRi8+pQc z>u`UibTRByEB>C7<5R#0&kTdjPl{k}B+raUA}&Ffq0H{*%6$L0TK#e&>FmJx2q~nMxD+MS;0#*%1pz)JVDB?$0#b11(q=a#%%a+)uM` zI$OQX5>M@68epHKMa$vq4(WlJ%xQqR>FJ1;l}_{hm)COAjC{O&=k1?8Gq?@P1%vzH zqs)24#F<-0BaJ>ri;)-uEihydATEG2KJiSB=xV^TGZfhLk>-I^NLCP z@eZGA|0d3vT3FTkIaehlV_n9qoc({18l+oO@&Fv}HaM0tw6Tj@eURWLCwk*D_9;yd z4y--of{kipT~~Z`l4=Y^vKhU6?b;Z@v+qhFB;~zjDsNp>2dI&kDVVB-B{(jO!d17h zye)M+H&VqQqGAq`o#|X1_BAlBz_~f1Yo+4^7L{Ky4Og`2&R45*Bo+!bM%jS$LOI0} zXe{lkK7#}Y_P%?OdUr(cj`X{u9q*3*op(ogcO-VadQxsWP*G}D(SltyQWzTK%~HQC zaN!l`{NYG0WVecBc*6iG)4&5}N3XMEl3QpCo}v!>SuZ6IEKS}hxOrE0%g0h3VxYCo z@1?Km@CF+RIrY#eKos709(Ndu3fe^P*zZ~#9?y6u1KzSxF`wR_0B)$*+=xLtY!w-h zv#NAa1x-WA!E>kOqU)wU0FBoYqAYRqVymQ_JdqhW(jkK8qt%ENVxZZmCZ-beQ38m zoIcEd#P5rj!7a%#*5{DW`xmR#1xeehW|M!YPg8+aMmgFa%AGbW?s`8q<)ekC<3^ zL8mmg+%$4==E1EG8kX*Gq~pUNB4*_V>ef)^s(mnlQA$f>8V=iM;YM842s^bYB5!<1 z<|xv1h~W(nNp1GK8CiUGppS<$#HO3o+Q`P4YU>ERR$Qq~PJ(q{x4+$yoma6uzCEEq zp)q1aI39Ry?8Xps51y|mRx;29+E)wRzP6NHvE(D0>AMQeBp|Z~au5mF0gByy6OfAuPk! zpVIfm!5QqAQZxk(Vk#-mh%3(I;yzUS2BCVdaL?d)#-X_=W0DN3)rZO(RyXP{aSWwY z_5Zryn7$hQef^_QX_xf<_i$92kxHihC*_=_E zn*OP7Qd5#=B&m!=bM7sN?z!mGLMKVurNu$wgci4PV#+N&t4m*|Y$x3BY*W z1I#=P#+#H#HEhP)MVwoI%;f*$W8UCn-k6VBXEMW{sHNYqkOx~ikH)pQk(o6grRXfg z72P0Vdtr^Ryelr~rlCl6H3b-o7VYO@R28UKx8K1Fd5b?6Xh=qyb`N(oerAtNGqc1d zFXCK$6?fv3OvOiWj#7{!tg2AX!;>PUzi@fZ$(cP5AK4H2B4KhA@H52kd`!+n@P(%E znJlzed=bBs?7NdsP(_PZ=Vkm=%RIVzM~^YU$)1i& zT+xu9vA^#fSmsKe)0@6hXY60?*LrC-&ug(;era`qrs;-vxW%h}a9UrH=e6U3H}_v3 z)!T4HZ!!a~mcB>yKN6t8Y)Eps!NWGU3&w<{q3VMs@|UeibCQ} zK5xyFsU$rck5o199*@?ipM*xnC04-7Kf^YjhNQm?s_0G}@&0O`*Faa?(YvoARA@2TVAhb||7WcKJT6&8Q>sXl-M_ed6T*!0!$oF>p zc6IDNW3T1voSOcfrdpgE$JbkS`QfxW8pomp z(Bk}<2y%rY$~)y1X}tPfiCq5&^+L8Bdb5_BvQ(edE}VB8G=+%#K21vsu_Pr{yOguk zwZ>Tu@|@A2D9dxO zg81Z#_wGlpJy)opk1SjKkEGJwkErnx^mkm$__CJSf2I)HB<@IRKa_vA*?5PWyCQY+ZS67nDfnLmwY&(8 z?R07X%Lt+UFK(b}uOw!45(g1)>K#4G7m;YPJ8wgM+e}9aMUzReWCPAupq;}I*QU%pi{4CuJo?(_JGQFQa3gS~KpH{2xt4T$B z)I02xo;O*w63)_rz#nlIoW5{^&tnzjj|3|qKU1YW?9ELOk)0TpSb!rIq`WrNCmC^2TIsCvbeo z>=xQ!1NavOh$pb&s)%t-ft&09S^~XH70tpObfb{Bj1%F#zWONL>bPAULOZBE0X@GQ z1a**VAeg!&>Zbrn@ovQ=KX5_1`k<|UTl@{WS8M{^sYtK`%->Nv!gm0~08UB5pRW}& zLsnIp6BX}K2_EcwMhrUVZSaVr!g7O?g@UCNVrhv2U%Nyeo>7N^09sdt$ z*V@&%v24HlSAZ^yBgNu1nR`~2G>VsDn2DKTAi!jj3(hRPEOY$Cj{ySy@3(eUcdKQa z7Bkwkzid~#^X;qtkry0=eHTWGLmD76Zz0n%btD0MSM1NM%By_w zQJgt`1{^S4Fjj22wn3WqYvYDH)_OvJtfNusqs$z^&&_@DEm)y?3MF{eLg@K;5qV%- z@%?{Sor?8}uoIg@U2|}&r;Rj_eKLcUanO+SH|lKXAUFWN0PDw&iVtIf<&ugHII+ga zVBVkIii%vR6XYj`NC6FPS;_-+yhcrhC|OXXWhRQ(ldgI|GrIz_tSTX5W0N-vIvX3y zbk>Yoa9_{Plr}trBg7sN>~v6DZ`Zvw9bUIli}s1`bjU16*iZhHLX z@yFw%E1cE1x>m25=6%lXpqBu{kuG-uAo*81fmU!)HY*j?0&*zo*-A&6SSsh5gr$&` zA-AhTpJX&J)I4}2dc7VA7Z5W3V3?Ta^_bKYqub4d<`h4)!E|ebiN!rkz~jAeYJ7Os8YJQ z_h#JSyW{I#J1`K*0<792m)vMRKX-=gHX`3-kRbVk@)KJKfKYc*v z1<0J0nO}M-5>+4JOSS$Y6+|_i=2ZZ;Wd@8!>C#OYZ84MFFJ0hRaQcwsW1dO!_HYF}8OE0HT)Yu(S5=|o};Y;K^Jy8=+@j(^~I%>s@BCZT6Gf$L37@lsP_6n%P%CyCT zQd$^MpQF9shXp>tb#v$c+IzZQhc-C-&JKMDc285%J(G!8GRd@p<90x2mMAY94hP}! zzQ2-CgU=1U1&rWJt~W+0Q)nNY{jmGSC_cKf%oNlwue)sziniO~oEC~Otw-*@;aY0w zje4Qg6c*deS!O}z{TRsbo6EZAQsv?)VC6UW0FG;9xMr`mXW7hhFE8!q;CE{rb8`d< z55GTw+-koWTTEF+Y?P=mze`K-dm&aYA`MiHJ!+zYEw%LXy)5BN3WKiKNT{`f4)sxG zN>R9WAe^m}rfnD5OKi@oE|9O*?}Nc;7(6voK>b&n$KL=&>mI$= zEggTi_Z_tmtOE?4@qA`bmm|zWa=h>#rjw3%dqU3>IKN(AyxX}t`*8e;20kwS#Lrh3 zcdW~B#1sb!tedFQh!)`MwqHbQ7M5?&s$Q3%}pr z|52YcvS3IpfC{3SmCMv1pLIuB;4t^pN=M3fmy`U}4xO*o2##2*T9v$f3H6>j!DHiB zBB`L6Lcj8pYw&GI4L!u?kmX)QvwVtacFU~5vm8V8fp$(TPTI>l6BU8c$X%&a)r7#y z=O;!}x|mr!Ur~EEgzPA=2#uJ!%q?+`=>po4V~)!nruxmIjE?0MqK~nwkF}RaWu(H& zp>9(en6d-RQ)XUT`5s>Qj;p)s%;6{#@ebBjh}aK|xl?ZdDBl7%$Gi8pv+mI(=hzZ) zA0qPRJ2zjGyqY&!&8C}Us-T;@vu+M^2}#%f93yeKdykY4s8-9P2VVI=;2O;4kNaV!PB{$5E%EvF?`V08Ps4#eUaY z6xxVT>G~>%y-vrjM91YHB5&sZX0hg|l)$mB0@80%25Rcc zy(fUCWM)xQO5=zLGGf0Z#mV`kMUQS|Sfl8rc3#M}69m&D3P(xiVSCzPxpim<37(jS2yis_P&T4{}>0 zIyGRrD?sd_gs~_^fIr}{-SqM+7qvA%_77xAol*a0XL+!&M7$Fp+)^hy@>_m0I-v<* zJ^^FqIG)6VDeNT6B`WoPIFZjh^(ogTxJ_yCp`n?}5y@4G_EViY?~kt>hE>Q6L#sK$ zrJmbq*xMhGyrypQ8tNdpa&w)e4uX3Y){ii!wxT1pq%{#)vn9-Ot?lhkZi}mEWVBXU zW4{%Cq^ylQw}pL03#tlQ-dpS)(1H^{K$m>_xCo=|oPBVVsY-XO3Ff92#=J|Yg}sqiwjjmn%=2iSgoekV@9ALS>Gm}PyLo9apv)JXtq zF!{DF&oqB$Fsi7&5t&oiH{Npkf(_f{mKO!l9(aeH5X@Gh;dl-D7a$&JJuO3uS{nnw z7o@Io@$~$eI2c!vyE8L=v-k6z;-3ueET+bV5Y*Pz>04gBE1cPcd{Z-cKI8s+F zKzNp4nfYJJu7oBwj-h@lV;1jP5iKSbeUzOJW(>uwB}FxAa{_b zx+0=EvVPM6COP_noJ|e>H=l2*3s8e>T@8GmUemH75aAJ;Y|j57x+B&=x#`JEjyG#} z`;e>XAYd4#j<%VUL5soAj8dQRQqNTIv%KcB1WTHFTYI-O25OsMc7Hwn_!D(8E&dp6 zOn$%dWw*(u-(MxpmFt||rjyZh)S2;JS6Am^cv&f=QbzS!SZX5djRC779=cH2lyCTX zMLl3`9!iRnmk3SFmv}@se#mM0=9L7N0SVr>Yr<^#9O&3?-bcf6hGn$0s@fJB*dLeT zkVvR1b^u{qQHPnLBZoI`|BAoGDg#}ZP7rpyv&(UOKoC<@#F8YT(e8+8yYNCeW$A^_ z^W9{W8BMyfafHejQUt4=1AiJh^I>l|x*s|~9=lQ=xOif&7|9PLxN4XNR>M<$R;mx!mZ zkB2FVt}|m6O~`vFk*cWK&-P>!zm#izxh&Zgu6i@BnoQKH4XbvdQkWB!=gz^3A}05H zcGx1Tdz_%hMg`!JA4tGG+}=LijT1P(S@O}n1XL9H@C=kpLImWVdEtjhv6`_Fgf$d- zgW5Q7I=5`4eTdzjVlYidef|XW0b=*y)hh%u&nWZjK$Xt%4>&sI%cODcHx;0H#u;BG zkE7S=MZM?eM2PR$NFkFYwn7>!_ZT@}ojM}SNZ7i1-7D#@zyBwRuGf!M_Xi}foK1s) zkEJt$5llf^Kq6&AMnDMrder>*@vwRHfrkIeP~LUwkDmXyBn8~V4ca%)0{vQ|D)Vh2 zveJ`4f?_y;9PW;&Pw z?y!j>gGcNgfOZO&N)9>FMZd2#1M*bBmXpZQtFJ@5_ru1ak*0$35YjqeLtI|%VE@Mp z7+9#-QkeHbG#2d_56!+uXrLD1nCfu2vT0R_f+<5E4=XZUG}Xk7Dq<~q4CbGyQb{Lq zxIiloGj|~ROZFPve{<)t9hGFD6QHwLn7xCLd?!Nk_O=|jC$L4o z79HByr+tX8E{y3b+(-6hx}Cm?2dGs3Fplm4My+#&_Pqe5o~$nwcSI*7U7bYQgAlT~ z$_hp8Np!%yslUB_@W$kk&T^kZOb|?-qs$+h)NSrdAdIr-pZMd-H%8B_LW@uQ!k~(| zXX@-_z=9&0!a-AvTZM!S>eGg<#6QwFDZC&Thxap(0r;}rD7*sWmV2%8#ZxAkLc8QC zX6?Xs6dSU$jT7MJda>jRRzxFa37P=|ZR|2LwSO=q>|N+s&{C|lh3eGrS`i35zm~x@ zccDIA2WU4M7}IF#j~AM$NOiDo2@L71A3x<_SWuQV!=)TwWqe~>&bnL?@+xZJQp~$s zmG4X;nF}}juEx|3pt8+pEfvtw@3ZH99#2flFiMSD;Vv2)wStVYx3t7=Z*TU(E**Bg zMmN|G?<_45|I6TQyk(+Bwz6noo12GrxA$3I5c5e~J=g@%LQcdLb1VZW*u z+9XS1@ip2W76O+uMMJ}bu@IEQ(<00?(O_-eBnGM-y9}s)^M;#WSCiX%+vZ4Nh??#% zHLIJv^e#(fX%0in>S6`1e34>MGs9;jpAL8CQ^)`G`SkGk;w$iT&8y4c>Fx1R>$Lgt z>*euz;Osj7`-`*B=W^yd{(1B2>iFW*WpMp8xE)fhg9D!q-c8~(*!KtV10Q`4o67lw zL`4Tjm-7yehIzqhJA9}Ti%Z~hzW4j;0DHaVf?t_}Uzvh$=KK5c>vhGu`jUQ#x8^Br z|LF6@#qp=3-@ab{b_#s@!hhSmI=*V19+z!#diLq+r@%Rhhx2&yxbZGeCVX%jPtxv2 zb3D22)A8fR$$Xg8zy9M!b6(K2wsA@8RgQ^h<1C$x@cW7O7@Y7nPx-*Hqh{4Ee>%Ik zDj$bO;VF2AegYnV@=-X7MUg12QqbGxZ-H|;A7*sV>4<->NVLN5mpmKF(N%Xo;e&UR zTl`#-h%%vrN=aNJT^!0nK_~>b<4)XGxubb{eo7s~X`WI~IX`vU{vgk8=YzmGJw4sX z{Ef$rZZH@G)9FTxFyLJh>K(ecm+d{uC8T#~X5iq?&N2P@8>jka$M27ZgVGW zi#U5u>G2Ym&^wM_$#{y|Adw)8Wwd))k@oChVh_d;onQ--h`aCBmfpxjiP_U|IzoCy z64{GAA2~0*YxJ2W861$<@CW7+&DNiVuJK~0tU)TwLqYY zSOi)A`J#11yC-Tf+56SI)N^C~@wBnp&YLEBQC$=9-Yyu_iN48qhA{W(Q2K}||c)xK8RH#|-i2pI5 zZB}+RlE8vV#8v?p)sUYi4;ReQP+Pi8>Ol1a8)>ajO#m}M%)bhR2LFryh2A7)Y@DOC z!2E!a`$1_bRs0?Cz{n+vK@CrG0xt5a4+R8f-`iquwB2yEffc{Vp~7Q3rT z5Y&IW4`fy!z-v4|t19_Bo4{tMijz1{D;J=Or_*6?J0m)vMV62|<6iZF`9XJlZmQ3=$l4a6%BvPw^lvj4ba zrfWK8E&p$OX39M?<(`=@^~|L1mbW%ytDhqKU-VF0^UITpV(f3Ym3|bjA<}NXFu`0U z>qC53W}LqmHrA*~xJH@R=m0NVm{CCINjxRQ>6jK|hCP5}k7Z|xWRKF48BCYiF(8_E*;-5^p^Y7s>+Ca%_%M6e?lGmqSmOg_qD7Y7JoakD09QI4r!T@6TwnY zN}Q#%uDWF;ABm)JDewx%@9xT%gV%(LX5#mv%!+ydE_9{$t;G$u7Y!;b`-4&tph@07 z!y5j|qw(IOC3=tJFy|&>#vf_WOlcwLVLS|Q?TQ9cmnklDx96i{B4KG*xdVS7W3R7p zh^1lWc@W?BXCp7zyA3-`%$W9X(_S^q6*I=|rn5=^L;gq`eFjCveEbSx)qVGtMv0{` zYLZwb(N4WkM=naVUl<233ZW%puaYS0&=2@A%gD8^$7HmM`dn6=M7#=Apb8&a7dvtV z_WE+p&SHUzXK}?~V*@O_{N;U9ID0cu@U+=Kz|!~dszIV{z6)~SU*f*UaY4MWuYIZj zeaJcbwmeT>)L}R0)u5rS$A-g|Z<70k$7c1V$fVSF%I>wPdl7iTj4H$bq78D_PN%c; z&mUgD+W%pvT8zUSi_9`@7dHSwKT5Q^FgqyP5lt$lT>I`=s5J4=s%?E#duvO{ z3E^;iJC3Xup>H&-*Zr5?``>x9T4lmmeMh^g5M_Sp1JQdI3M~;@HO0KEEypFgs%xu; zzSxn?Xr{*8si@-fWL_K(5#%iUPiE^E;p^j>sQ?CFFs1l#Fgy&GKa&B!402#>+t1PNVXJC?bA^|XKVt^ zL+nX|=t#x$^S4lzKRED2o13}FJtfoo!Wc_xE8UL)Dr;~ONg1A2;sYA)!4kl4M;~MR z8X#bzN>3V&6C{S);)&`RMhCY&dNw6Xy zQ4YSc#_`yDN?@RsipCXB1GnmJ&?`6A&eyLrCg@wd&u6oI^7X4D?pG%LawWY#8ujMm z)%=panxEAkDnac!`b-j+r=5OghrPA%`-8HI0v+K9w6u!PIZWZ2qK1yz7?&O#+uJoe zvKFlx{d_XRfu(X^Do2klT^4g2(84HTThtngPKYIu%cPYEzlitcEftoH2Bsvcvw%~U z^3d|gc^0dUFi&h?$3XUdLc;m9U_K+I`P#KKU)a^|hX*AWcfo#9Q4c=s8oruoL@Nq~ zqZB1THPD6k2@)bu@kZS+VOs)%rOt<-us;{;0~qff8NcsrZGD3gH?as0IkNaF_Lb(( zZ-|_^z;crCnS?r$V|dFY98mk4w^*&ORa4zk%wA$C1TC*K$LcNA^J-SB9;Icg3b294 z1IZaZ(FEZ4|5>}%=C+Mw`<=f+#QMsB3CWV<&1M0?EL*XX*ooyE+sS$MC5jy9uprKc3BB~k@>1T;p5E1ELLc~JTu&B|HeU^p`q9OZB zXt>ZcT-0dDKTE?oV-@@rG@NT1&NU6{>(;hfD1iAgVvM}@0H&SMP+KZ$OA^TDZpl!^ zz1l!4J91x9ZesTMJfJop?#jHN%;~t%b9ZIFpv;*$X~>5%&+sZJsrDsWG$?J2PJV|1 z!7|iLSa|*|_U9ZE)B+@HoFK%lRVa_L;fp*tX1Bst=xd`<&!L)-5FFO_LYs=VUkoZqNKbZ|8XT1~c^S>1-21C-C?xr=BYHy=7{CwS<>0;Cm7+j%%h1`i$28&<*Ed*}@wj%3pKAp$nY zJpo!{pmKuNchZ<19J1C&<@`T5!``VL5 zL~A@nB#vupNt-H4tgiys^a^K;9uC~ekZXMHku-447-w62XG`y)I_mkF7YWM47s$tX z9I)nS8|85nI$=*JV@}lPsB3(uf|)XGGN>^6U*rzFhK-R4;#AFeM$e%`sJ9)BZkm)O z?8%&Z=koC!)D_U*mDc#v6Y^gTCviCSMz=I&g8rdj{|r({M3A>XlRN-;?0}@k#vBfG z;Olw;cMFkeGiSfAq{`+aS#q0@`%3S~ZOU&yYzVT&w7P8R4C`*yQd5{>vB3ey`do%J z;-=(ezt?EaX3e@9w~M-Vb02KO-{M;RyWZQ~?Z51Z*+#?TB?oN-QmBk%Iug2u@s2EF zr&!XI!GxlzvP0irkS z|GwXJ>)yC|*08VZ=Mq~l1#FDCW|sf8+|YGGYMXGHe3_LDWs|gqYKZ<57*x)E9XzOt zZk0P+Lti{mx`IhKiLTHaLJg?=d=guSrtKws*cxUg_*HV02^iBT1%T;XdOI@k47T|g z{#<~B@`bTgjfzU!jg`XLMi`jgN9q0K(Z)~Y<%T?kr<6oJ`1q2{`ef`1pekR^jo=m} z+q_4i1fwbf?9!{+;VKkKhHm$bfHp`y98XHEQZ8!7wq-e7#&Ue_Q|dwjsg&v9 z)_1rFr_1Y!CyB8i#ISY>;B!{L)${A|o7mAIaCDUpGu;5h{AI?AN*mE>Qmk2EMYv+O z0yM!Sa$_Kwxt7VO=7meY*FV(ZUMH})77nr`A2fIm>#dvx^^TgO9w^TXWTsY&bi9<| zrGxGQGgTnWmi(e?BaK0HEQyYrBevG6wRvG0I8~l=&6cH4*1dG3C%!A9+BAA3F@wX> zaOmUd24R+f)*=nvR@xH);X^S2QayANk8>~y0Jh~IIEIU|svy&D*#I`k)mk(0d!K3q zmBPtIf*|wkKKIwH86`?1n&#ecE~b;2O{z-ktqYE?Cj4+;A%gU-0Z>0l5VdhKGeHg}=05M#f$x0gcss_4&}#CN`J zjd!jKL%cy}Yl>;l153ItT7ip=t+#X$ZgLC?IaLA_h?7rxda{!0<4<`qn<|H#_T%~T z_s(Ywk0s2S?<({7j;B=QU61S+yFZ(g=#T=%blC3l)LKXEHHxQo&9YOp3WVsxFZaLv z602j!i?MPtV@Do1Wt_u|GS8x(_Ajj&=6^K8)*;T?;Ie7>JB&xMp@=}uIH!DnS$C{y z$BM!MoE`4^P9Ux>ry1>0qwON<5={+lq11zGnnlyZHTF3;T$Sc8;&5Ks!tkjoAV%c9 z73@vhYpwF@MiL)ozzeIE@IIbarhP^Z{ySrt1+8cuT|Q7TBH{FQsBLM$0RQh*ew7WN1e%#S_-CgPhu04);vN6D=F)?jC zuw|M*K9bF_?cF_Y|J=B0OtdDMY@O|QKeT#+6&=24?C#8780!fYhjDEhOPliUS&GVq z&H4{p)o&&=8ZZu8P1q_zpC`pzU-U*Dg;eWnF;$#T!sTCqZJGWfUh}qC1c?u`=wRW~ zLP_jeesdnsc7GA{``I>3b^MC{8xCVAk09M-v$^&i)mIK8#up3QOtQ!GuH+4Y(W`}G zLW2j!IH2Tt5!2+6>R%MnDv1l431l;2A}JzReu$Z=rPT!ik6P<2a61hG(WGnFk|oGm zZdGd@1Hh2;+=C zS_~dr8^=sr`<%%A|C2?_+saPhc41ZV>Y2c?W{UVFujAXhyZvrjS9^Pwx7k>t-{dkt zNgieVC37xjM`^wox)<=I1v+|0;})_i{0T*X=hAESG)d4RfVt{U+ePLej17D$LW@lp zb(H=InvfH=M9{ZxX(gxzof&e%FKxAb|QL>c4e3Kpwry8@WE65&HSHl5WSTa4d( z?Z5D^Xk$I~f50?&?!Wbq!x>^&b1q&hu$<-T%ox$BNf>6!+ZSL|P?1xy>dpj&dHphC7lB zT}(4|^m>fmasikQbemIlF>p)FKUzvLjHvJOi{UZW7nnOs&)djAr|#~y6N!bLuHVFO zI989iDoNCPfh8oRF(!(Sxaij?nva))f*uqQ@Jo);UJ!?I>iK_v1~ZN4(bK&v|A*0Y zOcwlLS@hpVZ$RM9xi@rZ2iRdq1-fO7R)But+H&d*IK_WEh@23(D?h~*URvTrc|b|a zDw9b)N-I77pwMr<^*kM`DuWE?iSA|RW7@z>q`+1$ea*i5ec3m9Kiq6i0YOUxV|Vwo zzI%#QGBnTC%XXg7J4Kq<)@7XK)2QOv=)Tx&)M!IDULigrGpG&fm|rbuKqF2r)%gd6 zwGd_5Mer!DzZ$OeXlB)d8p>K-b_4UY8z>?a=Wb6+v`CY$S0-|{*daL)+t^ZtkoP(S zg9A>ZcDul8=XSAT{)!7JF>;2cVoh8YMcuZ|}*>CbYZOQIVu};1#`(Nf0tFwZa zhecv^Ei*0`k;S)kc;8>M62Hf*aChikutkD<1GJ<$gsI6vdZs{~WmiQ9W|~O9dV7EE z$_e}qZQs20+G4K^E+V4WGF@it2x8t7t%j>n+1;!tR)!3*0<+&rAG%Gal4g17)VhXpvp>=mOc#poi^7lh9nqLDVAUUs$vO@mAdMI5S~w$EGaeuH z_9d{Nmo4jcexaEND*+N8M&($A1XB@6jluIFLO=O)@JCrxG&*XtNyBnKy;`C9nC_?u zE6Ih=wOkNZNAs`}fcn{}8uJc>3p?V-Obx09M&d&9x|xFPtB4Z6B7S&IOc5Ul3LKGM z0jg@dYGGMf_+~3=^oHVXb$|x`DLOlYAKl&D#h+;INrV{W_ck}+_lfTcQE;to(@=zw zsCU6=9G0@lXQs)^qnV0fkRgD9X!5ew?fXA&HeVe@KYAQk>ISDz>^J2*Fry&hpkGM1MQx|y zTrJjdkq6G8Fp5q~v-(m2HX@Q%AuRWmDjc8uhSfK8u?LlfcAYtsl=~++Oo>?JDZVtW@HfeNWJ)nd~ z^ym1_{-Z`RrHy_G9`6r#!|@~IE%9SMd3kg0I=?uBWZ*n-SS*+&mI0)lLwc=n#?Y$Q zT<}RX2*?vFE&|QouUXiy2M*QVCEUK<;T2+i*7WZ9<*&!5Z{ENB*ZX%-A&_H}Xe7F1 z?8jQAzA~XLyGL*5>Bb4495+umtg>EETh3ZTxTCB%I;ll@XnKlvK;ySumPF-x9juYC zdy*(tBCuV1ijeHbppkZ5Dy%6n4!d=d0>A1g;LeZ1>D(?tTMquIGAsdjYnr@a=o2f( z3u8>oP>isUjmwDBTZ{FD5?N1_vcAAhR)(n=1C-xx!^x()Amvvxys}dOq(CuGO;KrqvTlqd@C{Q+u^w)cPAZ?V$`~SFOWA0Xn0# z7Aa-02_=)*g)#)bMNMmGA=s*;MwlKgq)zLyRXl5NJ25tB@ae-_BUX1}5 z3gKsqc&#M7ix7D@SL2PH3icNUT&=6n*c2Lf7J6tGs@u@&rNufYrl_K>uT?MHn&!YT z)<)`XX0OL)uK03>cZRsCb;K~fqX!U_#^ygB*p8eEYO(FS*}2B{`*)uq>oa1@_;sD; zU*}am;ehhijM9-&!Fv8swsw^KQ^szAh;jSVb<6kE zM#e)(51!G)b;_&I#t{^1N_i8SB{fr52wCcyQflvZO7jT``=*=;6UmM9+&18`d>gFD z8^jsYhPOgI`Ai=G4KsT+GIlvNIK;%y_@O6xPyi&$ulkc3)g;U5>cA%;_Pf?PgxCo4 zQJlCA7xQbp{?!?z-nfeaWN2pE2}FMS)UTJ~TX(-QslCG4@BKt6@>?#fHKzI=ihF>K7Bf!4Et|Ay_f8CX$W@5I_QUe`jj&?boNnf8&mBe# zQR6-K=kyA|lMw)fL6kXax?e#|Utn#5^}i7vFL2;7_gIfV6V-d0PrV zjGzw`cX_kfm}_Qm86tSIdHRfq$VuFc*QLM}4xHt}+_B#%@Zcw#%|DKM6VCY(a&^;E z7zQ0zmku99{xc$3M^Ga=??l8%RI7B+1Uk>A*NBJ)#Swkp8Ss%-q9gm^xXXt-!M|hT zo9OG=awV`-g3>pgvJNU=xBEI+_;LaSnU8fi_|b1J$3Z<1UDld&cR{FJeBLHqYzeyT zoP+OaOi_avT19h>7`m|0LgTjb6WWBiwu#GKZLdjvFgGT2K7+CX|$I)C1{nb`1!?)OjBnoK9x+XzDbwTLMV(1p5T52Z*s3x_Z z)t617tj=mry&CvuZ4JGNYo>l7z~Vo`vmC)Or^(f2E&)<}s8OSj63pbTEQ+DVeIPRX z3NeuGHH^BsH1cuq;4vV!fX)dRLcC5tzQ9llK1$@ip}H_H{&F@nZ_>QPo4{ETKUTiK z)jq)5w2vC{(1&>*Xrv!s;Rgl+eeh!!`k+Y+dI0}3_m&dkt;BbPvSEnECYH5krYX=E zqv)Gd-(het9wVpX$^w?C&%Zc!f41zbW-PuNF&0kj-ve zXm|eoLWGTTvjGAk$>?j&iEw^u#0ZT)D>-*~h+3gmStLvbu#*{w#0CmYWBQxpe+#%s zS__%A{7&2`2dtBvZyE61|3=(Y(>ZMsOL|e>FF)lgxZlv*B1IlK`>#Y{%T<=IUeK3b zi~B3=i{gb>xz*4od52~T5guje$Nu-0GA2={1VXZ-287lFLn%*ZHU8Uz%nlP1@ih^m zN*|UM(0Ay4LtY$gL*q`Ql>d>FESFUtFB}osARTrxuK$YniOY^F5ih+73j!Zm8jiHj z%UGw8ZLZW#w6xc}sKqQ|Jz%12*i2=Ffhe+wWf2Sa7dVNlI;qMXZj;D$08B-E%J=9% z!KY~W%OgCE8&549zpc_Jw5n!Iur&QtslLD))zm7AnBO(Lk=JlIi4 zzB)K~{`Bz#Vc)H2VSk#yJLovLkB-_WzfWy=8Ljmq`Mp0~pG+4dSrbj0&3m&TOX%7n zI(U|ZGGJevtkrSuBuWNg5dcm&V~0g4U-7}}#GDjBSt&j7CMp(`J37nG_QwM+clU;# z5G#pmq-AA~hFCRvLL;$PjlLK^%Lj+RZ*>kFm>_xqNmvu%Fu!G4?%)W{qd1$`-93E9 zMt3DO>gDoedPAarF3pJGT{e*z2S20`OgRdT8f+&bSavSM5>6Mcj!zixOUQJe4NxR% zx`$(qfylLjW3M}K6HA>nplT23JU1g4*jKoF7%;oaPk1?pAwq?|&|KcMqgtXgP?8ai#wg zwG7TZc?L{x9jT&B0lIK1XYmKh3Dvwl%0n+1pg%+EIQo(9zw}Nvuz;6))Og#OT#SbC zzC@yr_XSwJ7tYiY5Y}@6o}P2jg|VzO;VAT!_$g^1aXTT6bY2Dkr=LW7iJ%?$n}fvE zAf_6i0+g1r>Mv;`Q}0=)EmSMZH`&N@X_Rg@2d(LzBfxh);xKRn;$v7#d~ig0^Jjah zms0}^2wniXJz?J_8n%Z|^!I*(M`K){{nyDocq2k*m|CCo3k0!^hYsV_Q(9o0B(hO4 z9_2O~>UT|US_j=_D6Ck{d73h}!hRkQ@&G!Qs|zr!XcL?b1!Z5aK7 zvw9e=^4kRm2TAlE7E5{I@h`L>-ag%Wd;dq;m4CNwE9>9=SJ!UG z-o7SIZfxK7R914pMN6zLlI$e4^uNFP1`9!oj??o_>xcwE5X3SV%zQJG(8XnLfBhsp zw^w1(ba{Ee#573opa0j&@3i*2fyy?;khOcbG4nF(3>YMxZO{Q^U>9Uw84-ZiElv1( zTnCyGG()3bRZ!(D{=HDbzAQ*C=${aR+#vdpntVGCkj(yjZloIrUJ(tV75-KHvM|o1 z18fETI{4y>K^FyO%%(G-PJL}7T_BaQt{iOAJwgoPR|Nn}y;eL(1~|}dd=fR&c$+Pemk8OkPq3 zU$e(GwA?Hz*6$i!Es{p^1ej`~$zOTyt}1^a4^{tbrAb+D8<9rkL?KJhs|>)zgt)W!-#|4bMr0SqRHBwuCLmh6d7W zc|hsK7a4ru)oeX8$jo!ZFz8D)XQaY`&{Hscv&SW(4*S)VAdBlsf;|{I(IKojXb zmozL-o_ZFi!G`y?s?)4du*}>LF(wHOUuyynWOTtxKgS-+c5d__u?C;g@N*ipGF*R7 z!%w&lv{Y9kBTFyS7^~$R#!++MLsWZsao+lp3N}Fk{7;2Hzg@EdR744o57LZLb_Mj? ze?gBSBOlI*(812*V0ShjE|zqPg|e1zX=M#_eLbA$yEcqQWS&1&O#s>Ua3Ti{0_9^EfR(M#swMnL( z>#8nEA$HvFD?^2Z4qugQWG~wIZg$e*P{JpU-`&zFjZ|Ln zUqhkE3h4u8_y*HaUf>kMl)H<>rCb9U2)34XRWT066}G z(l;sx=I+RjFkyquq6kavS_yK66CQcxgUdZhq5&hr)H5TZ&XaHH8CD&Wu9 z+pB-;4qRPsc7Zj`@@6z|KTYQ>NF49&5o?tZwLYH@$Fu>R1HQIJ$FW@+HfGTwx@#PB zfc$TUY$A3I`^cWgVfFU*iXc@sEykiD$M;2$K=?J zCNy&Hm#7&*S;NKi@oWjh(Q*s|I5@8)DDcc8#sI&OaQ!`rV1MNo@xlgqzdZC09tQMp zr#~Eb9|pUJbOPD(7qQ0LBKVIeaMl)8IHX>(*VqUUZXVTp!f6~jVxdlzk|Tlyp;JR< zI4kX`&0lDjw?q}xSv0UQ-fi<$fcJUPFFwyau^C?#5))7<7cPGwb4b=iUQ}%3v9L^YHaG-G)sPVoAt>w z%O-!dS*FxRv$jmTj^j`NtX)o=1ue!(V;Vxf@W3QG)Mw3# z_QtaB#DPMG1Gwd=R7_|@WT60-0eIWt`Pb70N~A&v)v&k*I_D%hWRsVJ`^d|_1Kd%GEW zo^3}KVB{Gkw$Vrx%I|n9U9A|g!;l;X??4RJB0dxeaonOpX{pg+KDO~q!bJiqY3{Zw z&K+wrhMgltPnq@0i_hOb;xnc`B^Gc%(b0~b_0~9xuZXQ`bJP#Zip{h!4az+QBQ??k zvWQ`6Lt8>l4=p$Zk-rdQT%el$HuA3g87f3mW=2YqRd~NXJrW{+T;|)C_lbswS$X85 zdn&$Oj{xmV)lNSGZ>ao14A^TUN0^~cn5ML-ZVH4L!V0O3$=L5{^2naE(b;w>DL=Qi zQcrbXHJFp(kFq^h_4HxBp6|7-f~Tm}A8qx~`w-h*DGM;SkNYgEx>#m{Fhx;O%NsIPZ`?~L$v?fYmSbvE5#crCiDiVzVu%=ddqlB zpwDVxObe-O1Yt(XE5KVY-Kq*|O-^MH)Gyke--Q39wjjDW(0COr{JwPWFjg;PHx7&L z%u}DeQydHz{2L^d+lHvHHk=d};1E*3Xu(U$flkD31Fl*1v)lE=izZ(gT*~HAZHh~) z%QzUS#pUARVO0psX&RN4qFvD&8UK9pw(?RCYZFogVMw9iv#=qyCO&0>fCub5twkp> zc`1G8mV@e)m4Wp&*NxxczSmxcl{jIFv-?ZB-AT*)ng17JZ9U1rs=v3Jm7Az7m3I7{ zKMy`$7xOP?#fV`{zSo)b_p`;U%V&UwY|!|9TSigjl+7HgzP&vMnh0F}x}g~{Ey_Q$ zScR%Q?2FwjzD3aE&}$z&I(YQcetJC|&Mn&AP?kG?WQ)ZFrBNY*!25I z^-OK%;W!IuRc=BD629zXtB%fr+7jOJuEDELrV#BH%iX@T&^r7L$mu=pAW)v%%La!D`KM*v2+GWVHQ-l@pweXngO^|d$WMbn$9 zTi#9~E1<=9d6=B|NqBsDc-cN#(V0X1md1z2tG(|MDiIxuD=L0W zFZmR9iOPu)-zGb9UdUBCUuDzL3IW9P)nL9Fj{7S<1+Ksxtq_B6MVzV}2fnGH^X{aJ zL(a)ze<*Sp-%c658+O{_!SRBp`1bgEv>eXpO`f^(P96yF_)z7ac<`oB$Q%!O$4i)L zoOsCBmt6rcXy=j5Jf{2pe0n`o#q6E)#ByGFUblX>q)T8_6ZP0J?!68St&LxOy#V zVL7hwXsGh)F5#%0gIjna$XIX$EVEHyL@YXiNEN%sp9fqCi)RvIuFe(mm&3vF^aQzi z_WBhhwpbKXnHL=^b2(zWvogZnAU0I_$yjQ9?Tt?wp4&Cd3mMeBv zY$NDF*IUDjTElWWI*!xi30Dl}D%6+Ua;-=M97||b+>yZh!Z(G0jv@r70?e+m4d+|F zT8nwAz^-Wxl=+n_tc(c;c`%(4zP$e^nvW^@0RjlW-| zLJ_!UxOh6-1H{2TsRQq zV6FQO*!8g5Z)V>pVeRl;LhLki?cph%>=q068HM(hu{657XHfK~%J_txg_NSUJo@P* ze#;n~)VfYUIe@+0dwNcI8nO$(j=Ww@!9?V+<1Fhgt5b!O>#+3yo3GbG)<3Y+k9^k6 zowID)v^z`uR7ZM9tLT1UZitYiS{t(2Pq}evWf(I)4C?@=Tx0=EHyDrnw`DPAxycQH zVMR9V4T~ItdMkM~y;yBRR(j1!QJKxNc!9W@PR~3FgfYIQ{z=)X{%oUD>?_M5b3cUW zAuZmDQw?i?hDl(7MzW*0&~u{dU1idfml27Qv{Q7UaKvw%udR(e_-P-}2x6if#HpOK z^|0(brUX>SB1#WNsZ>NIqd}UKdr$=AH^6vV$p{R9T(3h0W%0r)y3d}!eE#$t6X%Um z=cfmTM3#c22(T|jzZNy!j3J}2mbIbXiXI_ z$e!`R@$VE&xt#Zn8E`+Ip|HokYba3+bi#mb%-~INwW<_hKLQ*ZvQkhW*pd(UD_WU1 zZo|1zGWCg8@<~oL&>K&$7X_T>aAd|DNHV013V8#T%xcB{R0fIIvgB~GSgmZr7(1#l zwR@wrV6KsdCD=c+LuNx;5^2vv+By*KX@RuKoK4!|BLJyft#)>N+G^SytlCtB?ZY-K zDKGs32(ddFxVLv_7F3_@{|Tx_B#OXh+PzL(X=0!a<7I<_HA07j3Sq=<%f1a&KeM;` zatuuB4GqT{qU}x44=`_FAo6o|pTr1Pyd3j%wbG7sGYEqQyv)~HPP$O>>IQ)@EmD(V z-Lpb*ViUHeyxXUtwv?AH(G! zY?vGXow7pxM*8yF#8}SnKW+5H=Tj~FHW%`zThO)Hl`=`ul51Om>|wn%kttc9i%s85@2ky0 zS(}42-`L%tk*0?;bKp_8)09rA{r*Vb)>m5whecT5Bq&$s+5iQ z#)v3>sTaZ9&yrVHrZ;kbhM2>4Cud&^2)@>5?EmfEe(jj=YIJQkTs*5gx7!edTyZiuj_r3q!?HwgJw2 z4BA=Ol%OvAnK|rdrWhn^0SBC6G&pv-P@~@^++S7E4?aqWZdvjq*02hM7P|Xlt7mny>K`Yk$KLt=G%Zy&ZTI~88J6UNi+l>q2YK$2cq7m!L_Wb{< zd%9V-!3s3=?;S0RxRyR@vE8Y?U4=K$x_89~6<-MlnV~u|W~HjvSx+V}$nJs;H>7grEt90krtk&NGJOC}t1I_>V>9_PXz zhMyNuI$pf}?J2OH6HI!kbnQOEZxXAGJN<-m?cqbr#b&H(VZx0ErTwVWTmeJrT|~9s z+(Ik>Cn72pmz2UDK6E*{L<+Sq{3YWQ32;{x%&2&*SSHdjcMZ3!u#LTgIL07EZr}-o z*jMAWq`H8$F?^k*X`}d~u-dh%3T1fS4mGf9wfX>YbD)fBOq)dIkvQX#F!EBCF;&(I zTf`~RXjVpbg7ss)CaG#s^!|*xZn&7^bNgQ1?)?Qr2erI4OPrn5)&{SM^#t154N+z+ zaOv4W4={UlQA32wMlT}GFye7~5B1rO95#}LZ7UA_8rZfMbBA+YD!=qr|*L5J& zsQ0j`5~IN23LdI@sO;grWe;uH@9!=9y)FCh-m>quY~k2LC(`7;_aR8x%u_qxP=3cL z|4r39wSPn9w)FA6g^z97lY7gal=a@WGD~ln&;(SVCKKSze_F(N?Xz?;+G-r+Xlx zFfpH?x=9Dv;I9qK4_E3%{RXrDa!#vNZw-6KUd#Z_vrW3KJYYcEEW5td5pc3W%lT>} z>2uN^1tO0J8p@Onx}w0H?1~#Z*mG50liqA8J41q9U8`G1Hs>OWU6K>UIHCydq?XC+mfG!KC z+6kFdgXCgg$?c3c88e=1@kxGbarJ5lDrpd_@iPGwZ&yyes@Za5c_v?3lr71g1BF>U z4ej!dJhtfZekQ^$QM8<-O~to8g<=PpGgnJ{HL+JW_UiiRNFMPWYUhR5RI$|U_#5qj zjj+D|OW(gY74F))o`0Ubo)vRh0gse5?wcoC`l3omAPnCsC(#D6XT(O?mr)i2W{`M` zZLb9>i`Q~G+S~5vdmf3>pd3{JI~7vTwT!#z5q#Ovfoo1%1>R;Kg!kx@oK-*zHejCT z2_5rBwd?Do+dxLXM!K1H{~v4D+TFHsw7>ILP^cmU=1P{Fr00YTs>rfz%Z=?+Zj;m$ z+kgOg32(9}N}@^jx4-ku>@F4;%1+Mfb1VW2V6U^YGkbX+(zRh$VpB;8c`A)C$u`1h z5+<#Rh76;`n_cvGPMhFNk^R`wg6=DTw@QmD9`o@Wt#!@JF_`_MLXRgSvH6G3@_(T61Wfy@?)&nufSSvBgFuYK-V6sP8mF0Iqw} z3a=r;`2uCfscJcp1y6`W;)sJVJFCHG#NzW~Im<&humT|?rZYP0ilre;zseKpPAn>J zgafHlsoq?z;iOU^dl(#H?tNiqdy+?=69#ROovKXtlg3fNOVt*$iNXTcIO=6k@a2)2 z3e=;KxRzynlZ3i86Fo-qS+Q?Sh1cj|RLEO4 z->9S!>SH3k>)GU#N`RxdK2KwuQ}^pBY|tP}suBJxZTCCtbziE+9(%sLv&{JZnhf=Aq`PG7VssMoJYloYy1j#hH1_A9!x(Yae#7;l%2i9edr19Hq09 z-8f6iSJ%Tybe$vQQP;nw4Sjd@|u@hadB0})evs( zHdZmU@)@A|QB+0+kF^!ZxD>N|fMZLlO;_=$0nVjyTljA2L+5pD#K;x<9K8o~z)cxw zD_-mk({XYV$5b$Wv8;;T{Pgbq`}1c%o@Ep85hyn+&0oU-1Uq%UoZdwDR9AWcp$qBG zN7+D?UAdO8wJycM`6r~c!2D_#@^k$l$WmXVv($4(#Ik=z@?Y|x96Zs(?<*2mWq?3g zEX?bGR9aeP)_^gE3YVWmey|(QrzaH3%mNnE!$R?Hlt{(a2`3{GY(<-ALEbwygwWcB z(><)LgR}rfRgzEIBU2r!E?3kxa8@FWKgu!Y157tl4|y?dR@xhm9(64Y1}PUq#A;Nh z!{9thdR-B(gC6U|%=;s3%(y#_`KOuikDE%?NwLoS!t9lZ3YLAq`SZ` zWuXL=>>;*519mHMKg6D7y_ZKE*&y3QjNM2!C)*tqhriqhkE`A@n#=V%)AOdW&_<-q zl~ZKDa7ZCCIe0j69vzIstc?Xb6x1($;`-tjxpe^n82smqLwp}ONR5Zx=*~+F%3MyQO+PD0M46^JLF!u;{4%oZW zNN>zv?xUzGm6(;EBws0^2OSmZsH@b?fvJV4&`% z`g68*JPea@F^i5Ka-%EeErR3vTe;7yOcNeRG}j-Yv=U_HYUXmQ-cJVd(LSn=As#(%!>4$x7npqn4$#B`Z;q z3~~H>OGdu;us+A@ZoHiKa3*B?;Sed8d+xfv$qxG`$^>YdCRQ4!Mkd4-g^pDQWxMrm>GW(=Bha(#*M;0 zHkI9iHLSm0`y-(1g$p&}kA1g<@+jH_fcfs@#ulPqS>-)!x06F$lzWt__8Y=fg<^JOwn-H&trh`_t^w)n{16R(le zBa&;;T5eyenxTv{?elOoZT?6#ZEQ8w^7SNs2AE03yj`yok+6IXG;_V6vVvEmblFUV zL>8;);FtMx)xG(Abu&1Ocqx(VrD#;Lh`f|m+MWOuvO`kLnUYgZ)*abi{W6Vv%4s~~ zzn^4V-aN*pyj~QpaFDm@VOy)GPT#+8dNGi}{&hitOvUT+4>vuB7~E5@>FwX=-bVd9 z{I$ro7*X^uV=xu}a$q*v3Y#6U$VQc$g*&ZRxo~_pM)2$EU>1Nkap)a*>J61{N&lK3 ze9v$~cJ@I8k%FJPx4=0YxR2HT5xwFD9ZAq+!if&200!WA@&ObR88Z~}J|%HT z;p_vVVM;G^8jf@^M z!mS~eq?%LaM5b>#>*;JFo%Q^z-W%QnA5!SpIjX%qy0{0>(JOO@n1iC-VC|S*Xkppj z|NK%&V^h8In1g7G1wvV}Mi#rrsWkbvDWR=cU~0*&8Ra)D$Bc>L_XIdJf51&pt~Y^t zp>b=jyM=RMeDK_WF6|ib$0Ym+Cd2+dc$8Z&fa+0(k6V$?FuR8LdvthMK*x;4bc_r_ zV!wETT42Bd$i5RvCgo&*+nZm<)I9W;p3?=FzUuU~&0)kjj33P5ci$BSrPGo{*K0h`L z%%PIU?qYDa{Otzg^N|599|<4(Hm(R&1Fg3xyq=+gnC$H>aDHi`fTRkIHVPid(5S%1 z0K&p7iUp!LBOzRNO~0i5AMSrg;||?ESpyUM^6oD?&4c4cxcAi$JD2;HPdajx=@&%Yj`ly% z`#XQx{c7*glp@0MWOGAU4yS#|Q6>m>C>8fdQOQXTchp?S=bN3_?TTuJ0hD%*oXr8oN zuYXMqthdDh%ER_s{@EH@L0DrnO3rcAbjY?M&O+s<;My}&6Qo4mf=1gu{CCPKhJ3&R zia*C!1f{MxT9YdlkkdUr-$Xv#WH@DVpK?_=`R3PL*zuGhzCD!|z`g$#0cfk zvD;Hf%iX0SO+$(TH;_@^BY`6 z5*Y)o(C6LReMLz%j0F=?ECeEq%oN+V%eLw*-8!)|(Q=YrlP#!r;(Xk%I)l*f>1UVI9LhA!R+RDJy&%wv1IpQsP zXhGFem!3;cRqaktlVZ(DfR(dQ_7@+#jo#{=ep*88aV1B8>TL~b;!CTfZ4xlqNQ45A%gNvO9Vsh>j`;wWhXARhX@T5drajWa8c-R@Oz|Y zLD+Q1Ta%6Lz3l~SVox7%(5H9l3cWDe*tzZ;ekfU@sJT;X&jPt+Nd{GTs6OguFuZ5f zGjp6|1Lh=?ceXHjk(_*NM;*$*4r6p3`?%fPo9^$cDcHN%V$IjLD%Lla*XYHgp!no4 z|K*XIHX39JGqz={1`L~|#wAiI{wgq-728;_DrLe74cT}lVFyoEPR1`HHxa9LvsBD3 zR!7p4LP{1Q!_a!EqWB)EVn!~Qfy~JW1shABh<3-zc zedYbtKC>q&n|OS3sSBO#TYi77K~FS}*dK&r8=-ete&T?*6GM!hlWQ7pP(UKovPd>XzjBun3dX3Ai&gW&4}#V{ma zCx-S)Giab;(0rIa)5KSd-+|>Sc|xO!@*XtEFtWX{JV1;d7y-1+a0T`9aB-1E4BW*> zE@pg-)6{}rMo;y0w{(xV=k=0ECNXWs;bUSx7MoX zzIEny4c`{kpk^q`wMfq}V_Gl4bd#R_q5!&yZ!%42-pW;LF-jviG%B7@&o1W}G1~C~ zHYCt1!19|hL}xh=NC-oBWZoqAX4-#7H$kSSO;CO`VQZJWwLn?#(q9DsTe->Zq5N8j08bUhOx5A%>(xiun_CZ zg4p$flsw1EU&^NttNk5p%QdH6)Ejjd}M5 z;YzSNV~}?WDjbl1&C&#e&XU74A**#_G3Lk_<<%)QqTtbiu@C9NNI$@d-AQ=X^#=zW zKNe|OXk9YjU^UUyp}^b5chW#$YC+3jtpOr;qgCiBXmw<6As)=G-^b{Vxw!_dLd#PQ zRTD3#Cr(>_Q)tV|mUmRZufSEEU6q%rcz2`t?*r8Ilp|TM2M6=(m?~~SG7d%+C_Oc% zThKVnxhSPhL2;=ob*)|-UP{WBTB?P5=Jtps)%7xKo26cpyn$=ww#|)xP+Q>Kg>H_8 z+Z;>N9LsQ_7V`QAZh^5DSPPuYFS5-t{wTY;@o-ek4eLi_x5)B+BU&`f)U~> zY&7nGAj0DUX^IsyCt-)r!F(ZS&I;z?RTzeM89k%)ht~Chk&!TJMd53A6_&nxhU0w} z^N?3PsN=CqO8(jc=PW5}6X-;;Ah^Nud^qsLCGVgl3=Vh>!yO1YTYG!Yuuu=W?#jq6 z$i_}F^m*t~^saL9?JW0p)N7W1gF#rOnYPLCJ_Ok@=C%BlW)y9|NMvO0*__ZQslngn zL}>spNn~mvqixn@zCHZ%m0E#2cYL_>>F`Dtx7Dpg>~_D*Mn3zR-ndJgO($xrnU`%m zeX`m-G5E%AU7ZyDn%v&lJ{5a0y&)~u*T&!-@7wK(h3E!C2ljzoOE$)OOb&Fj zy&qdLJ`dk}IetFgF*TPB_(3PEJ`;m<(Q6C!vMkJXut^z%! zzhxH1Sy?l6F_4Wn!o+Af$~~_YgQjB4n+~IcE3#3d@hLc6_b1u*%PHlP+h^y{wIj8_D1a(~IZ*heEEGdzAOftG? zKms$OFJOP?jp3C3WFh`>lkO=;@rZuA%iP1@m{+UGGFBgHXqeMja7T)LwL46jZ=;Q- zc$KN)#P^0w@?4vc{tCzBjMI2OZa?j`{4xE0jWzTsIY2aCe=MDczr*m+zUzc!1>Uq5 z;J>-i^s-@Mn}`YxN|&a8RhXyMFT9NyigsSPoI}zyrb;sPLItOS?q1$u*V?6Tuc^dJ z>P+7UVAw7db(nBr`vEERF{6ZXiSMMuyn#k$okf06F^)@?2Blp|XvT5a!~ED3)w@y8 z)u}pH_v)p(RiD%gy|ev2Q)l0Xk$M?^T(7U>5IJAZTer*zLF2@^aI!>2OpkMb(d>FV z;dzD=y0>>LQx)f690hwB4JpsVTf1R7e4#J1Fr?8B4_p232d%zv#GHp%|Ek>{_nWuv zKJ}0=YM)bK!k0cu(GS#FC6mP!_IggJ>Sx$i7_>N@6^ZzC z)8eFejyASpmq97hs8}D_KF9nfS)red6&h=^r;o|yem+m+K)ikV-;1+1?|wde&ctf! zlVsAdqR^8LxyGFaFcrU&YB#msl3H(l3Q+)*Vjlpw)cNF6GSzl(EEti{HS6?))oG|N zThnmdN@n$GIL$%%%eHMAtERbPkhQ1FPO`ic@);Di6WvN-gXo0R0Q#eO{ZlbRa$@il zr_c)bEhvgpC<>C;Q==jJxMYrt5C`%37MNrqj=4YIKf?WFm?Yyg-0{t;>$QH1 zBbp_NJcF;tvG{$^F1K_|tk)y|G~a^l=nK3c(a7+GCJpw1;=E;ODk3rog{LiD888&7 zVj!%aXz2OB+ODlRaU2W3^D7A3HEnMcl2py?c3HT+2@Tn;%uW~6-IcDT*(zg0U~J4S zKtjTQ-*b^<*$}$tVXIQXmL*Hp<=ph?oG1T5F`%dKX_EVfCtKzFG@5RTu|K+ZA$bI| z%oR1Srp-LH9tNHV9esr8a%DqW6x&ewvyJ#Cyz6iyOtoI2J%<{xB~k8x+{g8x;776S zY}Sm5KhOi-4O%#H=~+~dKn;im*@z^Wd?WHju_M926<6O?0xgd6swVj*xw9B@ z*K}(qwbQtVfJJp5)OxP6#=xgXylYb=ym@p1O;#p4c#@3kxm@NH=O&kl>q=OY>ckd# zUC~yI9AGIJkE0grwNf{Z%6#u^HR`|lOgXqKf@43hJHZU|9f;;V|Hv~8P$Z8$R|j{j z#twyQEgyt&MKks*|IZJCQS|MbVv`}^2in-;K7X|`4xeU08a|tsA+BM%n8Ds1-#0J^hyG8HHIq2hykCjD8ALknD^_{e8KLE8GjTcuWo(B@%L_kU$OfR&j_Nt=mHLeUwSQ8ld2=o>27!%gK#2M=tW>wgu|%MQ%ocC2 zM8_>yQjTS3%u<;{vDu2w3{Ozp2X$U#GNFx<{&bbazQNN#>Np!e>?uqrF=Xf+EWzy< z9%SF9SuH#i{kEp!{_^J9TcCGnVL!Q$sSOGsm;i;L7Tp=2oEY5MQ(WF=8|Te*x|2&_1(-j|?%mHEpPc9JfcFNnN~ zo~w4K=PpbLkPYb#V`z1Ys4FH>0u)1`QI7&;%SnmT+T#(GbxtbttaQ?FKXaa!CNjr| zr)F$}TYu|0xBi13F1OA*HPEKL_gEFF&OZFC&;hz*xVua@t<(z8^W|2}3zb2@1SMfQ zUj(KC5SHh;tfgNmZlM!{mI|+S>w4D2;0c8p{9Bi4&grRKsaf#8DPaOYgP_&;YFo6* z8*G$L!!pShX%@iyj&!g29XOPGnMu?n>opLdrLum~I(Uu0ipX#-W;BUN#at|) zLtq6mv%(%vk#a9^Pu$miFBgpmxqx-dVNtKUi=LSKmq0Fz3yOJTDd$MO;620v=4Oms zd+8ywNm%rec$zIHu0mGX4ZWV(pC*fx1NcFN%#Rtu31&yG$ed3-27Z+Clz*gLpc`rz zwTVCE3PGoVrJ1;?zslV&(*a>jBO0cY54D9VCCF;2Mfgt8Q@CQ~sC-v!oUp!Cn5_3D zi6uq2J|FS9Hi!BCWN%ZGV3y)emhvjpDoDY)}#C)WEnGImJ zA@W11`C^NhdfcSDa`v2wXyFkN7l$8{(vBG_k2!6Y+Te#(U*op53c&54xM3EJi!f>d8l z_(IGTwqbM}sL6ZD&xWki*{hrmzh)OGfNJ+ak_zeFb_QCFP{=aW4u~PS1WOyDUABOJ z)sGD@zYJyNy zWXEZT{$2f#&T6oD0v)%FKJ(xU0_Vzahoa?Bf=~$0k7`3wjF&VkKj~Ea-Zn6O46Sqf zLPz4I66#tM&PMSQD*?opo4%vAR8ZTeze`Rf_KC*=E(QH@Dtj|hX=oj|@;`~Mp=MzI zmH1EOd<}usJ6~OjMpa<1ckoce5e1fZoek)((~XFy5rv#|!E=KRsREp%30-qvhV%TFs1","<=",">=","&&","||","&","|","!","ESCAPE","lex","ch","lastCh","tokens","is","readString","peek","readNumber","isIdent","readIdent","isWhitespace","ch2","ch3","fn2","fn3","throwError","chars","was","isExpOperator","start","end","colStr","peekCh","ident","lastDot","peekIndex","methodName","quote","rawString","hex","rep","ZERO","statements","primary","expect","filterChain","consume","arrayDeclaration","functionCall","objectIndex","fieldAccess","msg","peekToken","e1","e2","e3","e4","t","unaryFn","right","ternaryFn","left","middle","binaryFn","statement","argsFn","fnInvoke","assignment","ternary","logicalOR","logicalAND","equality","relational","additive","multiplicative","unary","field","indexFn","contextGetter","fnPtr","elementFns","allConstant","elementFn","keyValues","ampmGetter","getHours","AMPMS","timeZoneGetter","zone","getTimezoneOffset","paddedZone","xlinkHref","propName","normalized","ngBooleanAttrWatchAction","formDirectiveFactory","isNgForm","formElement","action","preventDefaultListener","parentFormCtrl","alias","URL_REGEXP","EMAIL_REGEXP","NUMBER_REGEXP","inputType","numberInputType","numberBadFlags","minValidator","maxValidator","urlInputType","urlValidator","emailInputType","emailValidator","radioInputType","checked","checkboxInputType","trueValue","ngTrueValue","falseValue","ngFalseValue","ctrl.$isEmpty","NgModelController","$modelValue","NaN","$viewChangeListeners","ngModelGet","ngModel","ngModelSet","this.$isEmpty","inheritedData","this.$setValidity","this.$setPristine","this.$setViewValue","ngModelWatch","formatters","ctrls","modelCtrl","formCtrl","ngChange","required","ngList","viewValue","CONSTANT_VALUE_REGEXP","tpl","tplAttr","ngValue","ngValueConstantLink","ngValueLink","valueWatchAction","templateElement","ngBind","ngBindWatchAction","ngBindTemplate","tElement","ngBindHtml","getStringValue","ngBindHtmlWatchAction","getTrustedHtml","forceAsyncEvents","ngEventHandler","$transclude","previousElements","ngIf","ngIfWatchAction","$anchorScroll","srcExp","ngInclude","onloadExp","onload","autoScrollExp","autoscroll","previousElement","currentElement","cleanupLastIncludeContent","parseAsResourceUrl","ngIncludeWatchAction","afterAnimation","thisChangeId","newScope","$compile","ngInit","BRACE","numberExp","whenExp","whens","whensExpFns","isWhen","attributeName","ngPluralizeWatch","ngPluralizeWatchAction","ngRepeatMinErr","ngRepeat","trackByExpGetter","trackByIdExpFn","trackByIdArrayFn","trackByIdObjFn","valueIdentifier","keyIdentifier","hashFnLocals","lhs","rhs","trackByExp","lastBlockMap","ngRepeatAction","collection","previousNode","nextNode","nextBlockMap","arrayLength","trackByIdFn","collectionKeys","nextBlockOrder","trackById","$first","$last","$middle","$odd","$even","ngShow","ngShowWatchAction","ngHide","ngHideWatchAction","ngStyle","ngStyleWatchAction","newStyles","oldStyles","ngSwitchController","cases","selectedTranscludes","selectedElements","selectedScopes","ngSwitch","ngSwitchWatchAction","change","selectedTransclude","selectedScope","caseElement","anchor","ngSwitchWhen","$attrs","ngOptionsMinErr","NG_OPTIONS_REGEXP","nullModelCtrl","optionsMap","ngModelCtrl","unknownOption","databound","init","self.init","ngModelCtrl_","nullOption_","unknownOption_","addOption","self.addOption","removeOption","self.removeOption","hasOption","renderUnknownOption","self.renderUnknownOption","unknownVal","self.hasOption","setupAsSingle","selectElement","selectCtrl","ngModelCtrl.$render","emptyOption","setupAsMultiple","lastView","items","selectMultipleWatch","setupAsOptions","render","optionGroups","optionGroupNames","optionGroupName","optionGroup","existingParent","existingOptions","existingOption","modelValue","valuesFn","keyName","groupIndex","selectedSet","trackFn","trackIndex","valueName","lastElement","groupByFn","modelCast","label","displayFn","nullOption","groupLength","optionGroupsCache","optGroupTemplate","optionTemplate","optionsExp","track","optionElement","toDisplay","ngOptions","ngModelCtrl.$isEmpty","nullSelectCtrl","selectCtrlName","interpolateWatchAction","$$csp"] +"names":["window","document","undefined","minErr","isArrayLike","obj","isWindow","length","Object","nodeType","NODE_TYPE_ELEMENT","isString","isArray","forEach","iterator","context","key","isFunction","hasOwnProperty","call","isPrimitive","forEachSorted","keys","sort","i","reverseParams","iteratorFn","value","nextUid","uid","setHashKey","h","$$hashKey","extend","dst","ii","arguments","j","jj","int","str","parseInt","inherit","parent","extra","create","noop","identity","$","valueFn","isUndefined","isDefined","isObject","isNumber","isDate","toString","isRegExp","isScope","$evalAsync","$watch","isBoolean","isElement","node","nodeName","prop","attr","find","makeMap","items","split","nodeName_","element","lowercase","arrayRemove","array","index","indexOf","splice","copy","source","destination","stackSource","stackDest","ngMinErr","push","result","Date","getTime","RegExp","match","lastIndex","emptyObject","getPrototypeOf","shallowCopy","src","charAt","equals","o1","o2","t1","t2","keySet","concat","array1","array2","slice","bind","self","fn","curryArgs","startIndex","apply","toJsonReplacer","val","toJson","pretty","JSON","stringify","fromJson","json","parse","startingTag","jqLite","clone","empty","e","elemHtml","append","html","NODE_TYPE_TEXT","replace","tryDecodeURIComponent","decodeURIComponent","parseKeyValue","keyValue","key_value","toKeyValue","parts","arrayValue","encodeUriQuery","join","encodeUriSegment","pctEncodeSpaces","encodeURIComponent","getNgAttribute","ngAttr","ngAttrPrefixes","angularInit","bootstrap","appElement","module","config","prefix","name","hasAttribute","getAttribute","candidate","querySelector","strictDi","modules","defaultConfig","doBootstrap","injector","tag","unshift","$provide","debugInfoEnabled","$compileProvider","createInjector","invoke","bootstrapApply","scope","compile","$apply","data","NG_ENABLE_DEBUG_INFO","NG_DEFER_BOOTSTRAP","test","angular","resumeBootstrap","angular.resumeBootstrap","extraModules","resumeDeferredBootstrap","reloadWithDebugInfo","location","reload","getTestability","rootElement","get","snake_case","separator","SNAKE_CASE_REGEXP","letter","pos","toLowerCase","bindJQuery","originalCleanData","bindJQueryFired","jQuery","on","JQLitePrototype","isolateScope","controller","inheritedData","cleanData","jQuery.cleanData","elems","events","skipDestroyOnNextJQueryCleanData","elem","_data","$destroy","triggerHandler","JQLite","assertArg","arg","reason","assertArgFn","acceptArrayAnnotation","constructor","assertNotHasOwnProperty","getter","path","bindFnToScope","lastInstance","len","getBlockNodes","nodes","endNode","blockNodes","nextSibling","createMap","setupModuleLoader","ensure","factory","$injectorMinErr","$$minErr","requires","configFn","invokeLater","provider","method","insertMethod","queue","invokeQueue","moduleInstance","configBlocks","runBlocks","_invokeQueue","_configBlocks","_runBlocks","service","constant","animation","filter","directive","run","block","publishExternalAPI","version","uppercase","counter","csp","angularModule","$LocaleProvider","ngModule","$$sanitizeUri","$$SanitizeUriProvider","$CompileProvider","a","htmlAnchorDirective","input","inputDirective","textarea","form","formDirective","script","scriptDirective","select","selectDirective","style","styleDirective","option","optionDirective","ngBind","ngBindDirective","ngBindHtml","ngBindHtmlDirective","ngBindTemplate","ngBindTemplateDirective","ngClass","ngClassDirective","ngClassEven","ngClassEvenDirective","ngClassOdd","ngClassOddDirective","ngCloak","ngCloakDirective","ngController","ngControllerDirective","ngForm","ngFormDirective","ngHide","ngHideDirective","ngIf","ngIfDirective","ngInclude","ngIncludeDirective","ngInit","ngInitDirective","ngNonBindable","ngNonBindableDirective","ngPluralize","ngPluralizeDirective","ngRepeat","ngRepeatDirective","ngShow","ngShowDirective","ngStyle","ngStyleDirective","ngSwitch","ngSwitchDirective","ngSwitchWhen","ngSwitchWhenDirective","ngSwitchDefault","ngSwitchDefaultDirective","ngOptions","ngOptionsDirective","ngTransclude","ngTranscludeDirective","ngModel","ngModelDirective","ngList","ngListDirective","ngChange","ngChangeDirective","pattern","patternDirective","ngPattern","required","requiredDirective","ngRequired","minlength","minlengthDirective","ngMinlength","maxlength","maxlengthDirective","ngMaxlength","ngValue","ngValueDirective","ngModelOptions","ngModelOptionsDirective","ngIncludeFillContentDirective","ngAttributeAliasDirectives","ngEventDirectives","$anchorScroll","$AnchorScrollProvider","$animate","$AnimateProvider","$browser","$BrowserProvider","$cacheFactory","$CacheFactoryProvider","$controller","$ControllerProvider","$document","$DocumentProvider","$exceptionHandler","$ExceptionHandlerProvider","$filter","$FilterProvider","$interpolate","$InterpolateProvider","$interval","$IntervalProvider","$http","$HttpProvider","$httpBackend","$HttpBackendProvider","$location","$LocationProvider","$log","$LogProvider","$parse","$ParseProvider","$rootScope","$RootScopeProvider","$q","$QProvider","$$q","$$QProvider","$sce","$SceProvider","$sceDelegate","$SceDelegateProvider","$sniffer","$SnifferProvider","$templateCache","$TemplateCacheProvider","$templateRequest","$TemplateRequestProvider","$$testability","$$TestabilityProvider","$timeout","$TimeoutProvider","$window","$WindowProvider","$$rAF","$$RAFProvider","$$asyncCallback","$$AsyncCallbackProvider","$$jqLite","$$jqLiteProvider","camelCase","SPECIAL_CHARS_REGEXP","_","offset","toUpperCase","MOZ_HACK_REGEXP","jqLiteAcceptsData","NODE_TYPE_DOCUMENT","jqLiteBuildFragment","tmp","fragment","createDocumentFragment","HTML_REGEXP","appendChild","createElement","TAG_NAME_REGEXP","exec","wrap","wrapMap","_default","innerHTML","XHTML_TAG_REGEXP","lastChild","childNodes","firstChild","textContent","createTextNode","argIsString","trim","jqLiteMinErr","parsed","SINGLE_TAG_REGEXP","jqLiteAddNodes","jqLiteClone","cloneNode","jqLiteDealoc","onlyDescendants","jqLiteRemoveData","querySelectorAll","descendants","l","jqLiteOff","type","unsupported","expandoStore","jqLiteExpandoStore","handle","listenerFns","removeEventListener","expandoId","ng339","jqCache","createIfNecessary","jqId","jqLiteData","isSimpleSetter","isSimpleGetter","massGetter","jqLiteHasClass","selector","jqLiteRemoveClass","cssClasses","setAttribute","cssClass","jqLiteAddClass","existingClasses","root","elements","jqLiteController","jqLiteInheritedData","documentElement","names","parentNode","NODE_TYPE_DOCUMENT_FRAGMENT","host","jqLiteEmpty","removeChild","jqLiteRemove","keepData","jqLiteDocumentLoaded","action","win","readyState","setTimeout","getBooleanAttrName","booleanAttr","BOOLEAN_ATTR","BOOLEAN_ELEMENTS","getAliasedAttrName","ALIASED_ATTR","createEventHandler","eventHandler","event","isDefaultPrevented","event.isDefaultPrevented","defaultPrevented","eventFns","eventFnsLength","immediatePropagationStopped","originalStopImmediatePropagation","stopImmediatePropagation","event.stopImmediatePropagation","stopPropagation","isImmediatePropagationStopped","event.isImmediatePropagationStopped","$get","this.$get","hasClass","classes","addClass","removeClass","hashKey","nextUidFn","objType","HashMap","isolatedUid","this.nextUid","put","anonFn","args","fnText","STRIP_COMMENTS","FN_ARGS","modulesToLoad","supportObject","delegate","provider_","providerInjector","instantiate","providerCache","providerSuffix","enforceReturnValue","enforcedReturnValue","instanceInjector","factoryFn","enforce","loadModules","moduleFn","runInvokeQueue","invokeArgs","loadedModules","message","stack","createInternalInjector","cache","getService","serviceName","caller","INSTANTIATING","err","shift","locals","$inject","$$annotate","Type","instance","prototype","returnedValue","annotate","has","$injector","instanceCache","decorator","decorFn","origProvider","orig$get","origProvider.$get","origInstance","$delegate","autoScrollingEnabled","disableAutoScrolling","this.disableAutoScrolling","getFirstAnchor","list","Array","some","scrollTo","scrollIntoView","scroll","yOffset","getComputedStyle","position","getBoundingClientRect","bottom","elemTop","top","scrollBy","hash","elm","getElementById","getElementsByName","autoScrollWatch","autoScrollWatchAction","newVal","oldVal","supported","Browser","completeOutstandingRequest","outstandingRequestCount","outstandingRequestCallbacks","pop","error","startPoller","interval","check","pollFns","pollFn","pollTimeout","cacheStateAndFireUrlChange","cacheState","fireUrlChange","history","state","cachedState","lastCachedState","lastBrowserUrl","url","lastHistoryState","urlChangeListeners","listener","safeDecodeURIComponent","rawDocument","clearTimeout","pendingDeferIds","isMock","$$completeOutstandingRequest","$$incOutstandingRequestCount","self.$$incOutstandingRequestCount","notifyWhenNoOutstandingRequests","self.notifyWhenNoOutstandingRequests","callback","addPollFn","self.addPollFn","href","baseElement","reloadLocation","self.url","sameState","sameBase","stripHash","substr","self.state","urlChangeInit","onUrlChange","self.onUrlChange","$$checkUrlChange","baseHref","self.baseHref","lastCookies","lastCookieString","cookiePath","cookies","self.cookies","cookieLength","cookie","warn","cookieArray","substring","defer","self.defer","delay","timeoutId","cancel","self.defer.cancel","deferId","cacheFactory","cacheId","options","refresh","entry","freshEnd","staleEnd","n","link","p","nextEntry","prevEntry","caches","size","stats","id","capacity","Number","MAX_VALUE","lruHash","lruEntry","remove","removeAll","destroy","info","cacheFactory.info","cacheFactory.get","$$sanitizeUriProvider","parseIsolateBindings","directiveName","LOCAL_REGEXP","bindings","definition","scopeName","$compileMinErr","mode","collection","optional","attrName","hasDirectives","COMMENT_DIRECTIVE_REGEXP","CLASS_DIRECTIVE_REGEXP","ALL_OR_NOTHING_ATTRS","REQUIRE_PREFIX_REGEXP","EVENT_HANDLER_ATTR_REGEXP","this.directive","registerDirective","directiveFactory","Suffix","directives","priority","require","restrict","$$isolateBindings","aHrefSanitizationWhitelist","this.aHrefSanitizationWhitelist","regexp","imgSrcSanitizationWhitelist","this.imgSrcSanitizationWhitelist","this.debugInfoEnabled","enabled","safeAddClass","$element","className","$compileNodes","transcludeFn","maxPriority","ignoreDirective","previousCompileContext","nodeValue","compositeLinkFn","compileNodes","$$addScopeClass","namespace","publicLinkFn","cloneConnectFn","parentBoundTranscludeFn","transcludeControllers","futureParentElement","$$boundTransclude","$linkNode","wrapTemplate","controllerName","$$addScopeInfo","nodeList","$rootElement","childLinkFn","childScope","childBoundTranscludeFn","stableNodeList","nodeLinkFnFound","linkFns","idx","nodeLinkFn","$new","transcludeOnThisElement","createBoundTranscludeFn","transclude","elementTranscludeOnThisElement","templateOnThisElement","attrs","linkFnFound","Attributes","collectDirectives","applyDirectivesToNode","$$element","terminal","previousBoundTranscludeFn","elementTransclusion","boundTranscludeFn","transcludedScope","cloneFn","controllers","containingScope","$$transcluded","attrsMap","$attr","addDirective","directiveNormalize","isNgAttr","nAttrs","attributes","attrStartName","attrEndName","ngAttrName","NG_ATTR_BINDING","PREFIX_REGEXP","directiveNName","directiveIsMultiElement","nName","addAttrInterpolateDirective","animVal","addTextInterpolateDirective","NODE_TYPE_COMMENT","byPriority","groupScan","attrStart","attrEnd","depth","groupElementsLinkFnWrapper","linkFn","compileNode","templateAttrs","jqCollection","originalReplaceDirective","preLinkFns","postLinkFns","addLinkFns","pre","post","newIsolateScopeDirective","$$isolateScope","cloneAndAnnotateFn","getControllers","elementControllers","retrievalMethod","$searchElement","linkNode","controllersBoundTransclude","cloneAttachFn","hasElementTranscludeDirective","scopeToChild","controllerDirectives","$scope","$attrs","$transclude","controllerInstance","controllerAs","templateDirective","$$originalDirective","isolateScopeController","isolateBindingContext","identifier","bindToController","lastValue","parentGet","parentSet","compare","$observe","$$observers","$$scope","literal","b","assign","parentValueWatch","parentValue","$stateful","unwatch","$watchCollection","$on","invokeLinkFn","template","templateUrl","terminalPriority","newScopeDirective","nonTlbTranscludeDirective","hasTranscludeDirective","hasTemplate","$compileNode","$template","childTranscludeFn","$$start","$$end","directiveValue","assertNoDuplicate","$$tlb","createComment","replaceWith","replaceDirective","contents","denormalizeTemplate","removeComments","templateNamespace","newTemplateAttrs","templateDirectives","unprocessedDirectives","markDirectivesAsIsolate","mergeTemplateAttributes","compileTemplateUrl","Math","max","tDirectives","startAttrName","endAttrName","multiElement","srcAttr","dstAttr","$set","tAttrs","linkQueue","afterTemplateNodeLinkFn","afterTemplateChildLinkFn","beforeTemplateCompileNode","origAsyncDirective","derivedSyncDirective","then","content","tempTemplateAttrs","beforeTemplateLinkNode","linkRootElement","$$destroyed","oldClasses","delayedNodeLinkFn","ignoreChildLinkFn","diff","what","previousDirective","text","interpolateFn","textInterpolateCompileFn","templateNode","templateNodeParent","hasCompileParent","$$addBindingClass","textInterpolateLinkFn","$$addBindingInfo","expressions","interpolateFnWatchAction","wrapper","getTrustedContext","attrNormalizedName","HTML","RESOURCE_URL","allOrNothing","trustedContext","attrInterpolatePreLinkFn","newValue","$$inter","oldValue","$updateClass","elementsToRemove","newNode","firstElementToRemove","removeCount","j2","replaceChild","expando","k","kk","annotation","attributesToCopy","$normalize","$addClass","classVal","$removeClass","newClasses","toAdd","tokenDifference","toRemove","writeAttr","booleanKey","aliasedKey","observer","trimmedSrcset","srcPattern","rawUris","nbrUrisWith2parts","floor","innerIdx","lastTuple","removeAttr","listeners","startSymbol","endSymbol","binding","isolated","noTemplate","dataName","str1","str2","values","tokens1","tokens2","token","jqNodes","globals","CNTRL_REG","register","this.register","allowGlobals","this.allowGlobals","addIdentifier","expression","later","ident","$controllerMinErr","controllerPrototype","exception","cause","defaultHttpResponseTransform","headers","tempData","JSON_PROTECTION_PREFIX","contentType","jsonStart","JSON_START","JSON_ENDS","parseHeaders","line","headersGetter","headersObj","transformData","status","fns","defaults","transformResponse","transformRequest","d","common","CONTENT_TYPE_APPLICATION_JSON","patch","xsrfCookieName","xsrfHeaderName","useApplyAsync","this.useApplyAsync","interceptorFactories","interceptors","requestConfig","response","resp","reject","executeHeaderFns","headerContent","processedHeaders","headerFn","header","mergeHeaders","defHeaders","reqHeaders","defHeaderName","reqHeaderName","lowercaseDefHeaderName","chain","serverRequest","reqData","withCredentials","sendReq","promise","when","reversedInterceptors","interceptor","request","requestError","responseError","thenFn","rejectFn","success","promise.success","promise.error","done","headersString","statusText","resolveHttpPromise","resolvePromise","$applyAsync","$$phase","deferred","resolve","resolvePromiseWithResult","removePendingReq","pendingRequests","cachedResp","buildUrl","params","defaultCache","xsrfValue","urlIsSameOrigin","timeout","responseType","v","toISOString","interceptorFactory","createShortMethods","createShortMethodsWithData","createXhr","XMLHttpRequest","createHttpBackend","callbacks","$browserDefer","jsonpReq","callbackId","async","body","called","addEventListener","timeoutRequest","jsonpDone","xhr","abort","completeRequest","open","setRequestHeader","onload","xhr.onload","responseText","urlResolve","protocol","getAllResponseHeaders","onerror","onabort","send","this.startSymbol","this.endSymbol","escape","ch","mustHaveExpression","unescapeText","escapedStartRegexp","escapedEndRegexp","parseStringifyInterceptor","getTrusted","valueOf","newErr","$interpolateMinErr","endIndex","parseFns","textLength","expressionPositions","startSymbolLength","exp","endSymbolLength","compute","interpolationFn","$$watchDelegate","objectEquality","$watchGroup","interpolateFnWatcher","oldValues","currValue","$interpolate.startSymbol","$interpolate.endSymbol","count","invokeApply","setInterval","clearInterval","iteration","skipApply","$$intervalId","tick","notify","intervals","interval.cancel","NUMBER_FORMATS","DECIMAL_SEP","GROUP_SEP","PATTERNS","minInt","minFrac","maxFrac","posPre","posSuf","negPre","negSuf","gSize","lgSize","CURRENCY_SYM","DATETIME_FORMATS","MONTH","SHORTMONTH","DAY","SHORTDAY","AMPMS","medium","fullDate","longDate","mediumDate","shortDate","mediumTime","shortTime","ERANAMES","ERAS","pluralCat","num","encodePath","segments","parseAbsoluteUrl","absoluteUrl","locationObj","parsedUrl","$$protocol","$$host","hostname","$$port","port","DEFAULT_PORTS","parseAppUrl","relativeUrl","prefixed","$$path","pathname","$$search","search","$$hash","beginsWith","begin","whole","trimEmptyHash","LocationHtml5Url","appBase","appBaseNoFile","basePrefix","$$html5","$$parse","this.$$parse","pathUrl","$locationMinErr","$$compose","this.$$compose","$$url","$$absUrl","$$parseLinkUrl","this.$$parseLinkUrl","relHref","appUrl","prevAppUrl","rewrittenUrl","LocationHashbangUrl","hashPrefix","withoutBaseUrl","withoutHashUrl","windowsFilePathExp","base","firstPathSegmentMatch","LocationHashbangInHtml5Url","locationGetter","property","locationGetterSetter","preprocess","html5Mode","requireBase","rewriteLinks","this.hashPrefix","this.html5Mode","setBrowserUrlWithFallback","oldUrl","oldState","$$state","afterLocationChange","$broadcast","absUrl","LocationMode","initialUrl","lastIndexOf","IGNORE_URI_REGEXP","ctrlKey","metaKey","shiftKey","which","button","target","absHref","preventDefault","initializing","newUrl","newState","$digest","$locationWatch","currentReplace","$$replace","urlOrStateChanged","debug","debugEnabled","this.debugEnabled","flag","formatError","Error","sourceURL","consoleLog","console","logFn","log","hasApply","arg1","arg2","ensureSafeMemberName","fullExpression","$parseMinErr","getStringValue","ensureSafeObject","children","isConstant","setter","setValue","fullExp","propertyObj","isPossiblyDangerousMemberName","cspSafeGetterFn","key0","key1","key2","key3","key4","expensiveChecks","eso","o","eso0","eso1","eso2","eso3","eso4","cspSafeGetter","pathVal","getterFnWithEnsureSafeObject","s","getterFn","getterFnCache","getterFnCacheExpensive","getterFnCacheDefault","pathKeys","pathKeysLength","code","needsEnsureSafeObject","lookupJs","evaledFnGetter","Function","sharedGetter","fn.assign","getValueOf","objectValueOf","cacheDefault","cacheExpensive","wrapSharedExpression","wrapped","collectExpressionInputs","inputs","expressionInputDirtyCheck","oldValueOfValue","inputsWatchDelegate","parsedExpression","inputExpressions","$$inputs","lastResult","oldInputValue","expressionInputWatch","newInputValue","oldInputValueOfValues","expressionInputsWatch","changed","oneTimeWatchDelegate","oneTimeWatch","oneTimeListener","old","$$postDigest","oneTimeLiteralWatchDelegate","isAllDefined","allDefined","constantWatchDelegate","constantWatch","constantListener","addInterceptor","interceptorFn","watchDelegate","regularInterceptedExpression","oneTimeInterceptedExpression","$parseOptions","$parseOptionsExpensive","oneTime","cacheKey","parseOptions","lexer","Lexer","parser","Parser","qFactory","nextTick","exceptionHandler","callOnce","resolveFn","Promise","simpleBind","scheduleProcessQueue","processScheduled","pending","Deferred","$qMinErr","TypeError","onFulfilled","onRejected","progressBack","catch","finally","handleCallback","$$reject","$$resolve","progress","makePromise","resolved","isResolved","callbackOutput","errback","$Q","Q","resolver","all","promises","results","flush","taskQueue","task","taskCount","queueFn","asyncFn","cancelLastRAF","rafFn","cancelQueueFn","requestAnimationFrame","webkitRequestAnimationFrame","cancelAnimationFrame","webkitCancelAnimationFrame","webkitCancelRequestAnimationFrame","rafSupported","timer","createChildScopeClass","ChildScope","$$watchers","$$nextSibling","$$childHead","$$childTail","$$listeners","$$listenerCount","$id","$$ChildScope","TTL","$rootScopeMinErr","lastDirtyWatch","applyAsyncId","digestTtl","this.digestTtl","destroyChildScope","$event","currentScope","Scope","$parent","$$prevSibling","$root","beginPhase","phase","decrementListenerCount","current","initWatchVal","flushApplyAsync","applyAsyncQueue","scheduleApplyAsync","isolate","child","watchExp","watcher","last","eq","deregisterWatch","watchExpressions","watchGroupAction","changeReactionScheduled","firstRun","newValues","deregisterFns","shouldCall","deregisterWatchGroup","expr","unwatchFn","watchGroupSubAction","$watchCollectionInterceptor","_value","bothNaN","newItem","oldItem","internalArray","oldLength","changeDetected","newLength","internalObject","veryOldValue","trackVeryOldValue","changeDetector","initRun","$watchCollectionAction","watch","watchers","dirty","ttl","watchLog","logIdx","asyncTask","asyncQueue","$eval","isNaN","msg","next","postDigestQueue","eventName","this.$watchGroup","$applyAsyncExpression","namedListeners","indexOfListener","$emit","targetScope","listenerArgs","$$asyncQueue","$$postDigestQueue","$$applyAsyncQueue","sanitizeUri","uri","isImage","regex","normalizedVal","adjustMatcher","matcher","$sceMinErr","escapeForRegexp","adjustMatchers","matchers","adjustedMatchers","SCE_CONTEXTS","resourceUrlWhitelist","resourceUrlBlacklist","this.resourceUrlWhitelist","this.resourceUrlBlacklist","matchUrl","generateHolderType","Base","holderType","trustedValue","$$unwrapTrustedValue","this.$$unwrapTrustedValue","holderType.prototype.valueOf","holderType.prototype.toString","htmlSanitizer","trustedValueHolderBase","byType","CSS","URL","JS","trustAs","Constructor","maybeTrusted","allowed","this.enabled","msie","sce","isEnabled","sce.isEnabled","sce.getTrusted","parseAs","sce.parseAs","enumValue","lName","eventSupport","android","userAgent","navigator","boxee","vendorPrefix","vendorRegex","bodyStyle","transitions","animations","webkitTransition","webkitAnimation","pushState","hasEvent","divElm","handleRequestFn","tpl","ignoreRequestError","totalPendingRequests","getTrustedResourceUrl","transformer","httpOptions","handleError","testability","testability.findBindings","opt_exactMatch","getElementsByClassName","matches","dataBinding","bindingName","testability.findModels","prefixes","attributeEquals","testability.getLocation","testability.setLocation","testability.whenStable","deferreds","$$timeoutId","timeout.cancel","urlParsingNode","requestUrl","originUrl","filters","suffix","currencyFilter","dateFilter","filterFilter","jsonFilter","limitToFilter","lowercaseFilter","numberFilter","orderByFilter","uppercaseFilter","comparator","matchAgainstAnyProp","expressionType","predicateFn","createPredicateFn","shouldMatchPrimitives","actual","expected","item","deepCompare","dontMatchWholeObject","actualType","expectedType","expectedVal","matchAnyProperty","actualVal","$locale","formats","amount","currencySymbol","fractionSize","formatNumber","number","groupSep","decimalSep","isFinite","isNegative","abs","numStr","formatedText","hasExponent","toFixed","parseFloat","fractionLen","min","round","fraction","lgroup","group","padNumber","digits","neg","dateGetter","date","dateStrGetter","shortForm","getFirstThursdayOfYear","year","dayOfWeekOnFirst","getDay","weekGetter","firstThurs","getFullYear","thisThurs","getMonth","getDate","eraGetter","jsonStringToDate","string","R_ISO8601_STR","tzHour","tzMin","dateSetter","setUTCFullYear","setFullYear","timeSetter","setUTCHours","setHours","m","ms","format","timezone","NUMBER_STRING","DATE_FORMATS_SPLIT","setMinutes","getMinutes","getTimezoneOffset","DATE_FORMATS","object","spacing","limit","Infinity","sortPredicate","reverseOrder","reverseComparator","comp","descending","objectToString","v1","v2","map","predicate","ngDirective","FormController","controls","parentForm","$$parentForm","nullFormCtrl","$error","$$success","$pending","$name","$dirty","$pristine","$valid","$invalid","$submitted","$addControl","$rollbackViewValue","form.$rollbackViewValue","control","$commitViewValue","form.$commitViewValue","form.$addControl","$$renameControl","form.$$renameControl","newName","oldName","$removeControl","form.$removeControl","$setValidity","addSetValidityMethod","ctrl","set","unset","$setDirty","form.$setDirty","PRISTINE_CLASS","DIRTY_CLASS","$setPristine","form.$setPristine","setClass","SUBMITTED_CLASS","$setUntouched","form.$setUntouched","$setSubmitted","form.$setSubmitted","stringBasedInputType","$formatters","$isEmpty","baseInputType","composing","ev","ngTrim","$viewValue","$$hasNativeValidators","$setViewValue","deferListener","origValue","keyCode","$render","ctrl.$render","createDateParser","mapping","iso","ISO_DATE_REGEXP","yyyy","MM","dd","HH","getHours","mm","ss","getSeconds","sss","getMilliseconds","part","NaN","createDateInputType","parseDate","dynamicDateInputType","isValidDate","parseObservedDateValue","badInputChecker","$options","previousDate","$$parserName","$parsers","parsedDate","ngModelMinErr","timezoneOffset","ngMin","minVal","$validators","ctrl.$validators.min","$validate","ngMax","maxVal","ctrl.$validators.max","validity","VALIDITY_STATE_PROPERTY","badInput","typeMismatch","parseConstantExpr","fallback","parseFn","classDirective","arrayDifference","arrayClasses","digestClassCounts","classCounts","classesToUpdate","ngClassWatchAction","$index","old$index","mod","cachedToggleClass","switchValue","classCache","toggleValidationCss","validationErrorKey","isValid","VALID_CLASS","INVALID_CLASS","setValidity","isObjectEmpty","PENDING_CLASS","combinedState","REGEX_STRING_REGEXP","documentMode","isActive_","active","full","major","minor","dot","codeName","JQLite._data","MOUSE_EVENT_MAP","mouseleave","mouseenter","optgroup","tbody","tfoot","colgroup","caption","thead","th","td","ready","trigger","fired","removeData","removeAttribute","css","NODE_TYPE_ATTRIBUTE","lowercasedName","specified","getNamedItem","ret","getText","$dv","multiple","selected","nodeCount","jqLiteOn","types","related","relatedTarget","contains","off","one","onFn","replaceNode","insertBefore","contentDocument","prepend","wrapNode","detach","after","newElement","toggleClass","condition","classCondition","nextElementSibling","getElementsByTagName","extraParameters","dummyEvent","handlerArgs","eventFnsCopy","arg3","unbind","FN_ARG_SPLIT","FN_ARG","argDecl","underscore","$animateMinErr","$$selectors","classNameFilter","this.classNameFilter","$$classNameFilter","runAnimationPostDigest","cancelFn","$$cancelFn","defer.promise.$$cancelFn","ngAnimatePostDigest","ngAnimateNotifyComplete","resolveElementClasses","hasClasses","cachedClassManipulation","op","asyncPromise","currentDefer","applyStyles","styles","from","to","animate","enter","leave","move","$$addClassImmediately","$$removeClassImmediately","add","createdCache","STORAGE_KEY","$$setClassImmediately","APPLICATION_JSON","PATH_MATCH","locationPrototype","paramValue","Location","Location.prototype.state","CALL","APPLY","BIND","CONSTANTS","null","true","false","constantGetter","OPERATORS","+","-","*","/","%","===","!==","==","!=","<",">","<=",">=","&&","||","!","ESCAPE","lex","tokens","readString","peek","readNumber","isIdent","readIdent","is","isWhitespace","ch2","ch3","op2","op3","op1","operator","throwError","chars","isExpOperator","start","end","colStr","peekCh","quote","rawString","hex","String","fromCharCode","rep","ZERO","statements","primary","expect","filterChain","consume","arrayDeclaration","functionCall","objectIndex","fieldAccess","peekToken","e1","e2","e3","e4","peekAhead","t","unaryFn","right","$parseUnaryFn","binaryFn","left","isBranching","$parseBinaryFn","$parseConstant","$parseStatements","inputFn","argsFn","$parseFilter","every","assignment","ternary","$parseAssignment","logicalOR","middle","$parseTernary","logicalAND","equality","relational","additive","multiplicative","unary","$parseFieldAccess","indexFn","$parseObjectIndex","fnGetter","contextGetter","expressionText","$parseFunctionCall","elementFns","$parseArrayLiteral","valueFns","$parseObjectLiteral","yy","y","MMMM","MMM","M","H","hh","EEEE","EEE","ampmGetter","Z","timeZoneGetter","zone","paddedZone","ww","w","G","GG","GGG","GGGG","longEraGetter","xlinkHref","propName","normalized","ngBooleanAttrWatchAction","htmlAttr","ngAttrAliasWatchAction","nullFormRenameControl","formDirectiveFactory","isNgForm","ngFormCompile","formElement","nameAttr","ngFormPreLink","handleFormSubmission","parentFormCtrl","URL_REGEXP","EMAIL_REGEXP","NUMBER_REGEXP","DATE_REGEXP","DATETIMELOCAL_REGEXP","WEEK_REGEXP","MONTH_REGEXP","TIME_REGEXP","inputType","textInputType","weekParser","isoWeek","existingDate","week","minutes","hours","seconds","milliseconds","addDays","numberInputType","urlInputType","ctrl.$validators.url","modelValue","viewValue","emailInputType","email","ctrl.$validators.email","radioInputType","checked","checkboxInputType","trueValue","ngTrueValue","falseValue","ngFalseValue","ctrl.$isEmpty","ctrls","CONSTANT_VALUE_REGEXP","tplAttr","ngValueConstantLink","ngValueLink","valueWatchAction","$compile","ngBindCompile","templateElement","ngBindLink","ngBindWatchAction","ngBindTemplateCompile","ngBindTemplateLink","ngBindHtmlCompile","tElement","ngBindHtmlGetter","ngBindHtmlWatch","ngBindHtmlLink","ngBindHtmlWatchAction","getTrustedHtml","$viewChangeListeners","forceAsyncEvents","ngEventHandler","previousElements","ngIfWatchAction","newScope","srcExp","onloadExp","autoScrollExp","autoscroll","changeCounter","previousElement","currentElement","cleanupLastIncludeContent","ngIncludeWatchAction","afterAnimation","thisChangeId","namespaceAdaptedClone","trimValues","NgModelController","$modelValue","$$rawModelValue","$asyncValidators","$untouched","$touched","parsedNgModel","parsedNgModelAssign","ngModelGet","ngModelSet","pendingDebounce","parserValid","$$setOptions","this.$$setOptions","getterSetter","invokeModelGetter","invokeModelSetter","$$$p","this.$isEmpty","currentValidationRunId","this.$setPristine","this.$setDirty","this.$setUntouched","UNTOUCHED_CLASS","TOUCHED_CLASS","$setTouched","this.$setTouched","this.$rollbackViewValue","$$lastCommittedViewValue","this.$validate","prevValid","prevModelValue","allowInvalid","$$runValidators","allValid","$$writeModelToScope","this.$$runValidators","doneCallback","processSyncValidators","syncValidatorsValid","validator","processAsyncValidators","validatorPromises","validationDone","localValidationRunId","processParseErrors","errorKey","this.$commitViewValue","$$parseAndValidate","this.$$parseAndValidate","this.$$writeModelToScope","this.$setViewValue","updateOnDefault","$$debounceViewValueCommit","this.$$debounceViewValueCommit","debounceDelay","debounce","ngModelWatch","formatters","ngModelCompile","ngModelPreLink","modelCtrl","formCtrl","ngModelPostLink","updateOn","DEFAULT_REGEXP","that","BRACE","IS_WHEN","updateElementText","newText","numberExp","whenExp","whens","whensExpFns","braceReplacement","watchRemover","lastCount","attributeName","tmpMatch","whenKey","ngPluralizeWatchAction","countIsNaN","ngRepeatMinErr","updateScope","valueIdentifier","keyIdentifier","arrayLength","$first","$last","$middle","$odd","$even","ngRepeatCompile","ngRepeatEndComment","lhs","rhs","aliasAs","trackByExp","trackByExpGetter","trackByIdExpFn","trackByIdArrayFn","trackByIdObjFn","hashFnLocals","ngRepeatLink","lastBlockMap","ngRepeatAction","previousNode","nextNode","nextBlockMap","collectionLength","trackById","collectionKeys","nextBlockOrder","trackByIdFn","itemKey","blockKey","ngRepeatTransclude","ngShowWatchAction","NG_HIDE_CLASS","tempClasses","NG_HIDE_IN_PROGRESS_CLASS","ngHideWatchAction","ngStyleWatchAction","newStyles","oldStyles","ngSwitchController","cases","selectedTranscludes","selectedElements","previousLeaveAnimations","selectedScopes","spliceFactory","ngSwitchWatchAction","selectedTransclude","caseElement","selectedScope","anchor","ngOptionsMinErr","NG_OPTIONS_REGEXP","nullModelCtrl","optionsMap","ngModelCtrl","unknownOption","databound","init","self.init","ngModelCtrl_","nullOption_","unknownOption_","addOption","self.addOption","removeOption","self.removeOption","hasOption","renderUnknownOption","self.renderUnknownOption","unknownVal","self.hasOption","setupAsSingle","selectElement","selectCtrl","ngModelCtrl.$render","emptyOption","setupAsMultiple","lastView","selectMultipleWatch","setupAsOptions","callExpression","exprFn","valueName","keyName","createIsSelectedFn","selectedSet","trackFn","trackIndex","isSelected","compareValueFn","selectAsFn","scheduleRendering","renderScheduled","render","updateLabelMap","labelMap","label","added","optionGroups","optionGroupNames","optionGroupName","optionGroup","existingParent","existingOptions","existingOption","valuesFn","anySelected","optionId","trackKeysCache","groupByFn","displayFn","nullOption","groupIndex","groupLength","optionGroupsCache","optGroupTemplate","lastElement","optionTemplate","optionsExp","selectAs","track","selectionChanged","selectedKey","viewValueFn","getLabels","toDisplay","ngModelCtrl.$isEmpty","nullSelectCtrl","selectCtrlName","interpolateWatchAction","ctrl.$validators.required","patternExp","ctrl.$validators.pattern","intVal","ctrl.$validators.maxlength","ctrl.$validators.minlength","$$csp","head"] } diff --git a/vendor/assets/components/angular/bower.json b/vendor/assets/components/angular/bower.json index 4689d923d..5bde08c09 100644 --- a/vendor/assets/components/angular/bower.json +++ b/vendor/assets/components/angular/bower.json @@ -1,6 +1,6 @@ { "name": "angular", - "version": "1.2.28", + "version": "1.3.20", "main": "./angular.js", "ignore": [], "dependencies": { diff --git a/vendor/assets/components/angular/index.js b/vendor/assets/components/angular/index.js new file mode 100644 index 000000000..5c1aafcca --- /dev/null +++ b/vendor/assets/components/angular/index.js @@ -0,0 +1,2 @@ +require('./angular'); +module.exports = angular; diff --git a/vendor/assets/components/angular/package.json b/vendor/assets/components/angular/package.json index 40cf22288..563bd609f 100644 --- a/vendor/assets/components/angular/package.json +++ b/vendor/assets/components/angular/package.json @@ -1,8 +1,8 @@ { "name": "angular", - "version": "1.2.28", + "version": "1.3.20", "description": "HTML enhanced for web apps", - "main": "angular.js", + "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/vendor/assets/components/bootstrap-switch/.bower.json b/vendor/assets/components/bootstrap-switch/.bower.json new file mode 100644 index 000000000..851278464 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/.bower.json @@ -0,0 +1,39 @@ +{ + "name": "bootstrap-switch", + "description": "Turn checkboxes and radio buttons in toggle switches.", + "version": "3.3.2", + "main": [ + "./dist/js/bootstrap-switch.js", + "./dist/css/bootstrap3/bootstrap-switch.css" + ], + "ignore": [ + "docs", + "test", + "CNAME", + "coffeelint.json", + "composer.json", + "CONTRIBUTING.md", + "gulpfile.coffee", + "gulpfile.js", + "index.html", + "package.json" + ], + "dependencies": { + "bootstrap": ">=2.3.2", + "jquery": ">=1.9.0" + }, + "devDependencies": { + "jquery": "~2.1", + "bootstrap": "~3.3" + }, + "homepage": "https://github.com/nostalgiaz/bootstrap-switch", + "_release": "3.3.2", + "_resolution": { + "type": "version", + "tag": "v3.3.2", + "commit": "ec69b5dd914dacb19cf1fa8e1be7eea6bcc7b083" + }, + "_source": "git://github.com/nostalgiaz/bootstrap-switch.git", + "_target": "3.3.2", + "_originalSource": "bootstrap-switch" +} \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/.bowerrc b/vendor/assets/components/bootstrap-switch/.bowerrc new file mode 100644 index 000000000..cfacd73c9 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "components" +} diff --git a/vendor/assets/components/bootstrap-switch/.gitignore b/vendor/assets/components/bootstrap-switch/.gitignore new file mode 100644 index 000000000..d67b976b6 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/.gitignore @@ -0,0 +1,5 @@ +.grunt +npm-debug.log +node_modules +components +.DS_Store diff --git a/vendor/assets/components/bootstrap-switch/CHANGELOG.md b/vendor/assets/components/bootstrap-switch/CHANGELOG.md new file mode 100644 index 000000000..3094ef388 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/CHANGELOG.md @@ -0,0 +1,71 @@ +# Changelog + +## 3.3.2 + +- Fix for Flicker on initialisation [#425](https://github.com/nostalgiaz/bootstrap-switch/issues/425), [#422](https://github.com/nostalgiaz/bootstrap-switch/issues/422) +- Prevent horizontal misalignment inside modal in page with odd width [#414](https://github.com/nostalgiaz/bootstrap-switch/issues/414) + +## 3.3.1 + +- Revert of switchChange event triggered only on falsy skip [#411](https://github.com/nostalgiaz/bootstrap-switch/issues/411) + +## 3.3.0 + +- Fixed setting of correct state on drag from indeterminate state [#403](https://github.com/nostalgiaz/bootstrap-switch/issues/403) +- Fixed broken state changing on hidden switch [#392, [#383](https://github.com/nostalgiaz/bootstrap-switch/issues/383) +- Missing animation on first state change triggered by side click [#390](https://github.com/nostalgiaz/bootstrap-switch/issues/390) +- SwitchChange event always triggered after change event [#389](https://github.com/nostalgiaz/bootstrap-switch/issues/389) +- Skip check for transitionend event on init [#381](https://github.com/nostalgiaz/bootstrap-switch/issues/381) +- Added stopPropagation on element mousedown [#369](https://github.com/nostalgiaz/bootstrap-switch/issues/369) +- Fixed wrong descrition in documentation [#351](https://github.com/nostalgiaz/bootstrap-switch/issues/351) + +## 3.2.2 + +- Fixed wrong rendering of switch on initialisation if element is hidden [#376](https://github.com/nostalgiaz/bootstrap-switch/issues/376) + +## 3.2.1 + +- Hotfix for broken initialisation logic if $.support.transition is not set [#375](https://github.com/nostalgiaz/bootstrap-switch/issues/375) + +## 3.2.0 + +- Added option and method handleWidth to set a specific width of the side handled [#341](https://github.com/nostalgiaz/bootstrap-switch/issues/341) +- Added option and method labelWidth to set a specific width of the center label [#341](https://github.com/nostalgiaz/bootstrap-switch/issues/341) +- Fixed broken toggling of side handles when switch is wrapped in a external label [#359](https://github.com/nostalgiaz/bootstrap-switch/issues/359) +- Minor refactoring all along the source code + +## 3.1.0 + +- Added inverse option to swap the position of the left and right elements [#207](https://github.com/nostalgiaz/bootstrap-switch/issues/207) +- Fixed misalignment on Safari [#223](https://github.com/nostalgiaz/bootstrap-switch/issues/223) +- Added options toggleAnimate method +- Enhanced documentation with new examples + +## 3.0.2 + +- Added radioAllOff option. allow a group of radio inputs to be all off [#322](https://github.com/nostalgiaz/bootstrap-switch/issues/322) +- Made HTML options overridable by JavaScript initalization options [#319](https://github.com/nostalgiaz/bootstrap-switch/issues/319) +- .form-control does not interfere anymore with the switch appearance [#318](https://github.com/nostalgiaz/bootstrap-switch/issues/318) +- Fixed triggering of two events in case of jQuery id selector [#317](https://github.com/nostalgiaz/bootstrap-switch/issues/317) +- Fixed internal switching loop when toggling with spacebar [#316](https://github.com/nostalgiaz/bootstrap-switch/issues/316) +- Fixed switch label toggling not working with radio inputs [#312](https://github.com/nostalgiaz/bootstrap-switch/issues/312) + +## 3.0.1 + +- Added support for intermediate state [#218](https://github.com/nostalgiaz/bootstrap-switch/issues/218) +- Added change event triggered on label click [#299](https://github.com/nostalgiaz/bootstrap-switch/issues/299) +- Added onInit and onSwitchChange event as methods + +## 3.0.0 + +- API redesign for a more intuitive use +- Entire code source rewriting focused on cleanliness and performance +- Initialization options can be passed as JavaScript object or written as data-* +- Plugin constructor publicly available from $.fn.bootstrapSwitch.Constructor +- Plugin instance publicly available calling .data('bootstrap-switch') +- Global overridable defaults options +- Improved flexibility with baseClass and wrapperClass options +- New onInit event +- Event namespacing +- Full Bootstrap 3 support +- A lot of fixed bug, as usual diff --git a/vendor/assets/components/bootstrap-switch/LICENSE b/vendor/assets/components/bootstrap-switch/LICENSE new file mode 100644 index 000000000..d9a10c0d8 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/vendor/assets/components/bootstrap-switch/README.md b/vendor/assets/components/bootstrap-switch/README.md new file mode 100644 index 000000000..8bbd0cda4 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/README.md @@ -0,0 +1,75 @@ +# Bootstrap Switch +[![Dependency Status](https://david-dm.org/nostalgiaz/bootstrap-switch.svg?style=flat)](https://david-dm.org/nostalgiaz/bootstrap-switch) +[![devDependency Status](https://david-dm.org/nostalgiaz/bootstrap-switch/dev-status.svg?style=flat)](https://david-dm.org/nostalgiaz/bootstrap-switch#info=devDependencies) +[![NPM Version](http://img.shields.io/npm/v/bootstrap-switch.svg?style=flat)](https://www.npmjs.org/) + +Turn checkboxes and radio buttons in toggle switches. + +## Contribute + +Hi, Emanuele here. I am currently the sole contributor of Bootstrap Switch and have been mantaining it for quite a considerable amount of time. +The development pace is strongly affected by the personal lack of time and a missing core team behind the project. +It would be nice to have someone available for clearing the list of open issues and occasionally implementing new functionalities. +If interest, you can drop me a line or pick a bug, kill it and open a Pull Request against `develop` branch. +Many thanks. + +## Demo and Documentation + +- [Examples](http://www.bootstrap-switch.org/examples.html) +- [Options](http://www.bootstrap-switch.org/options.html) +- [Methods](http://www.bootstrap-switch.org/methods.html) +- [Events](http://www.bootstrap-switch.org/events.html) + +## Getting started + +Include the dependencies: jQuery, Bootstrap and Bootstrap Switch CSS + Javascript: + +``` html +[...] + + + + +[...] +``` + +Add your checkbox: + +```html + +``` + +Initialize Bootstrap Switch on it: + +```javascript +$("[name='my-checkbox']").bootstrapSwitch(); +``` + +Enjoy. + +## Less + +If you want to use your bootstrap variables, include `bootstrap-switch.less` in your compilation stack. You can even choose among Bootstrap versions 2.3.2 or 3.*.* compatible source. + +## AngularJs + +Two custom directives are available: +- [angular-bootstrap-switch](https://github.com/frapontillo/angular-bootstrap-switch) +- [angular-toggle-switch](https://github.com/JumpLink/angular-toggle-switch) + +## KnockoutJs + +A Knockout binding handler is available [here](https://github.com/pauloortins/knockout-bootstrap-switch) + +## NuGet + +A NuGet package is available [here](https://github.com/blachniet/bootstrap-switch-nuget) + +## Supported browsers + +IE9+ and all the other modern browsers. + +## License + +Licensed under the Apache License, Version 2.0 +http://www.apache.org/licenses/LICENSE-2.0 diff --git a/vendor/assets/components/bootstrap-switch/bower.json b/vendor/assets/components/bootstrap-switch/bower.json new file mode 100644 index 000000000..5504c1d2d --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/bower.json @@ -0,0 +1,29 @@ +{ + "name": "bootstrap-switch", + "description": "Turn checkboxes and radio buttons in toggle switches.", + "version": "3.3.2", + "main": [ + "./dist/js/bootstrap-switch.js", + "./dist/css/bootstrap3/bootstrap-switch.css" + ], + "ignore": [ + "docs", + "test", + "CNAME", + "coffeelint.json", + "composer.json", + "CONTRIBUTING.md", + "gulpfile.coffee", + "gulpfile.js", + "index.html", + "package.json" + ], + "dependencies": { + "bootstrap": ">=2.3.2", + "jquery": ">=1.9.0" + }, + "devDependencies": { + "jquery": "~2.1", + "bootstrap": "~3.3" + } +} diff --git a/vendor/assets/components/bootstrap-switch/dist/css/bootstrap2/bootstrap-switch.css b/vendor/assets/components/bootstrap-switch/dist/css/bootstrap2/bootstrap-switch.css new file mode 100644 index 000000000..952d37ba3 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/dist/css/bootstrap2/bootstrap-switch.css @@ -0,0 +1,519 @@ +/* ======================================================================== + * bootstrap-switch - v3.3.2 + * http://www.bootstrap-switch.org + * ======================================================================== + * Copyright 2012-2013 Mattia Larentis + * + * ======================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== + */ + +.clearfix { + *zoom: 1; +} +.clearfix:before, +.clearfix:after { + display: table; + content: ""; + line-height: 0; +} +.clearfix:after { + clear: both; +} +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.input-block-level { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.bootstrap-switch { + display: inline-block; + direction: ltr; + cursor: pointer; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + border: 1px solid; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + position: relative; + text-align: left; + overflow: hidden; + line-height: 8px; + z-index: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + vertical-align: middle; + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -moz-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.bootstrap-switch .bootstrap-switch-container { + display: inline-block; + top: 0; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} +.bootstrap-switch .bootstrap-switch-handle-on, +.bootstrap-switch .bootstrap-switch-handle-off, +.bootstrap-switch .bootstrap-switch-label { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer; + display: inline-block !important; + height: 100%; + padding-top: 4px; + padding-bottom: 4px; + padding-left: 8px; + padding-right: 8px; + font-size: 14px; + line-height: 20px; +} +.bootstrap-switch .bootstrap-switch-handle-on, +.bootstrap-switch .bootstrap-switch-handle-off { + text-align: center; + z-index: 1; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #005fcc; + background-image: -moz-linear-gradient(top, #0044cc, #0088cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0044cc), to(#0088cc)); + background-image: -webkit-linear-gradient(top, #0044cc, #0088cc); + background-image: -o-linear-gradient(top, #0044cc, #0088cc); + background-image: linear-gradient(to bottom, #0044cc, #0088cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0044cc', endColorstr='#ff0088cc', GradientType=0); + border-color: #0088cc #0088cc #005580; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #0088cc; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary:hover, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary:hover, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary:focus, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary:focus, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary.active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary.disabled, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary.disabled, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary[disabled], +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary[disabled] { + color: #ffffff; + background-color: #0088cc; + *background-color: #0077b3; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary.active { + background-color: #006699 \9; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #41a7c5; + background-image: -moz-linear-gradient(top, #2f96b4, #5bc0de); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#2f96b4), to(#5bc0de)); + background-image: -webkit-linear-gradient(top, #2f96b4, #5bc0de); + background-image: -o-linear-gradient(top, #2f96b4, #5bc0de); + background-image: linear-gradient(to bottom, #2f96b4, #5bc0de); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2f96b4', endColorstr='#ff5bc0de', GradientType=0); + border-color: #5bc0de #5bc0de #28a1c5; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #5bc0de; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info:hover, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info:hover, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info:focus, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info:focus, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info.active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info.disabled, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info.disabled, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info[disabled], +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info[disabled] { + color: #ffffff; + background-color: #5bc0de; + *background-color: #46b8da; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info.active { + background-color: #31b0d5 \9; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #58b058; + background-image: -moz-linear-gradient(top, #51a351, #62c462); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#51a351), to(#62c462)); + background-image: -webkit-linear-gradient(top, #51a351, #62c462); + background-image: -o-linear-gradient(top, #51a351, #62c462); + background-image: linear-gradient(to bottom, #51a351, #62c462); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff51a351', endColorstr='#ff62c462', GradientType=0); + border-color: #62c462 #62c462 #3b9e3b; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #62c462; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success:hover, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success:hover, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success:focus, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success:focus, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success.active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success.disabled, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success.disabled, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success[disabled], +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success[disabled] { + color: #ffffff; + background-color: #62c462; + *background-color: #4fbd4f; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success.active { + background-color: #42b142 \9; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #f9a123; + background-image: -moz-linear-gradient(top, #f89406, #fbb450); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f89406), to(#fbb450)); + background-image: -webkit-linear-gradient(top, #f89406, #fbb450); + background-image: -o-linear-gradient(top, #f89406, #fbb450); + background-image: linear-gradient(to bottom, #f89406, #fbb450); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff89406', endColorstr='#fffbb450', GradientType=0); + border-color: #fbb450 #fbb450 #f89406; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #fbb450; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning:hover, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning:hover, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning:focus, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning:focus, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning.active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning.disabled, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning.disabled, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning[disabled], +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning[disabled] { + color: #ffffff; + background-color: #fbb450; + *background-color: #faa937; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning.active { + background-color: #fa9f1e \9; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #d14641; + background-image: -moz-linear-gradient(top, #bd362f, #ee5f5b); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#bd362f), to(#ee5f5b)); + background-image: -webkit-linear-gradient(top, #bd362f, #ee5f5b); + background-image: -o-linear-gradient(top, #bd362f, #ee5f5b); + background-image: linear-gradient(to bottom, #bd362f, #ee5f5b); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffbd362f', endColorstr='#ffee5f5b', GradientType=0); + border-color: #ee5f5b #ee5f5b #e51d18; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #ee5f5b; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger:hover, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger:hover, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger:focus, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger:focus, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger.active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger.disabled, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger.disabled, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger[disabled], +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger[disabled] { + color: #ffffff; + background-color: #ee5f5b; + *background-color: #ec4844; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger.active { + background-color: #e9322d \9; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default { + color: #333333; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + background-color: #f0f0f0; + background-image: -moz-linear-gradient(top, #e6e6e6, #ffffff); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#e6e6e6), to(#ffffff)); + background-image: -webkit-linear-gradient(top, #e6e6e6, #ffffff); + background-image: -o-linear-gradient(top, #e6e6e6, #ffffff); + background-image: linear-gradient(to bottom, #e6e6e6, #ffffff); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6', endColorstr='#ffffffff', GradientType=0); + border-color: #ffffff #ffffff #d9d9d9; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #ffffff; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default:hover, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default:hover, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default:focus, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default:focus, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default.active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default.disabled, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default.disabled, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default[disabled], +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default[disabled] { + color: #333333; + background-color: #ffffff; + *background-color: #f2f2f2; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default:active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default:active, +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default.active, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default.active { + background-color: #e6e6e6 \9; +} +.bootstrap-switch .bootstrap-switch-label { + text-align: center; + margin-top: -1px; + margin-bottom: -1px; + z-index: 100; + border-left: 1px solid #cccccc; + border-right: 1px solid #cccccc; + color: #333333; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #f5f5f5; + background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); + background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); + border-color: #e6e6e6 #e6e6e6 #bfbfbf; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #e6e6e6; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.bootstrap-switch .bootstrap-switch-label:hover, +.bootstrap-switch .bootstrap-switch-label:focus, +.bootstrap-switch .bootstrap-switch-label:active, +.bootstrap-switch .bootstrap-switch-label.active, +.bootstrap-switch .bootstrap-switch-label.disabled, +.bootstrap-switch .bootstrap-switch-label[disabled] { + color: #333333; + background-color: #e6e6e6; + *background-color: #d9d9d9; +} +.bootstrap-switch .bootstrap-switch-label:active, +.bootstrap-switch .bootstrap-switch-label.active { + background-color: #cccccc \9; +} +.bootstrap-switch .bootstrap-switch-handle-on { + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; +} +.bootstrap-switch .bootstrap-switch-handle-off { + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; + border-bottom-right-radius: 4px; +} +.bootstrap-switch input[type='radio'], +.bootstrap-switch input[type='checkbox'] { + position: absolute !important; + top: 0; + left: 0; + opacity: 0; + filter: alpha(opacity=0); + z-index: -1; +} +.bootstrap-switch input[type='radio'].form-control, +.bootstrap-switch input[type='checkbox'].form-control { + height: auto; +} +.bootstrap-switch.bootstrap-switch-mini { + min-width: 71px; +} +.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label { + padding: 3px 6px; + font-size: 10px; + line-height: 9px; +} +.bootstrap-switch.bootstrap-switch-small { + min-width: 79px; +} +.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label { + padding: 3px 6px; + font-size: 12px; + line-height: 18px; +} +.bootstrap-switch.bootstrap-switch-large { + min-width: 120px; +} +.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label { + padding: 9px 12px; + font-size: 16px; + line-height: normal; +} +.bootstrap-switch.bootstrap-switch-disabled, +.bootstrap-switch.bootstrap-switch-readonly, +.bootstrap-switch.bootstrap-switch-indeterminate { + cursor: default !important; +} +.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label { + opacity: 0.5; + filter: alpha(opacity=50); + cursor: default !important; +} +.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container { + -webkit-transition: margin-left 0.5s; + -moz-transition: margin-left 0.5s; + -o-transition: margin-left 0.5s; + transition: margin-left 0.5s; +} +.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on { + -webkit-border-top-left-radius: 0; + -moz-border-radius-topleft: 0; + border-top-left-radius: 0; + -webkit-border-bottom-left-radius: 0; + -moz-border-radius-bottomleft: 0; + border-bottom-left-radius: 0; + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; + border-bottom-right-radius: 4px; +} +.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off { + -webkit-border-top-right-radius: 0; + -moz-border-radius-topright: 0; + border-top-right-radius: 0; + -webkit-border-bottom-right-radius: 0; + -moz-border-radius-bottomright: 0; + border-bottom-right-radius: 0; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; +} +.bootstrap-switch.bootstrap-switch-focused { + border-color: rgba(82, 168, 236, 0.8); + outline: 0; + outline: thin dotted \9; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82, 168, 236, .6); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82, 168, 236, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82, 168, 236, .6); +} +.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label { + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; + border-bottom-right-radius: 4px; +} +.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label { + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; +} diff --git a/vendor/assets/components/bootstrap-switch/dist/css/bootstrap2/bootstrap-switch.min.css b/vendor/assets/components/bootstrap-switch/dist/css/bootstrap2/bootstrap-switch.min.css new file mode 100644 index 000000000..df552ab40 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/dist/css/bootstrap2/bootstrap-switch.min.css @@ -0,0 +1,22 @@ +/* ======================================================================== + * bootstrap-switch - v3.3.2 + * http://www.bootstrap-switch.org + * ======================================================================== + * Copyright 2012-2013 Mattia Larentis + * + * ======================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== + */ + +.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-switch{display:inline-block;direction:ltr;cursor:pointer;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;border:1px solid;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);position:relative;text-align:left;overflow:hidden;line-height:8px;z-index:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;vertical-align:middle;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-moz-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.bootstrap-switch .bootstrap-switch-container{display:inline-block;top:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:inline-block!important;height:100%;padding-top:4px;padding-bottom:4px;padding-left:8px;padding-right:8px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-handle-off{text-align:center;z-index:1}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#005fcc;background-image:-moz-linear-gradient(top,#04c,#08c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#04c),to(#08c));background-image:-webkit-linear-gradient(top,#04c,#08c);background-image:-o-linear-gradient(top,#04c,#08c);background-image:linear-gradient(to bottom,#04c,#08c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0044cc', endColorstr='#ff0088cc', GradientType=0);border-color:#08c #08c #005580;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary:hover,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary:hover,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary:focus,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary:focus,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary.active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary.disabled,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary.disabled,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary[disabled],.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary[disabled]{color:#fff;background-color:#08c;}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary.active{background-color:#069 \9}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#41a7c5;background-image:-moz-linear-gradient(top,#2f96b4,#5bc0de);background-image:-webkit-gradient(linear,0 0,0 100%,from(#2f96b4),to(#5bc0de));background-image:-webkit-linear-gradient(top,#2f96b4,#5bc0de);background-image:-o-linear-gradient(top,#2f96b4,#5bc0de);background-image:linear-gradient(to bottom,#2f96b4,#5bc0de);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2f96b4', endColorstr='#ff5bc0de', GradientType=0);border-color:#5bc0de #5bc0de #28a1c5;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info:hover,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info:hover,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info:focus,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info:focus,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info.active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info.disabled,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info.disabled,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info[disabled],.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info[disabled]{color:#fff;background-color:#5bc0de;}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info.active{background-color:#31b0d5 \9}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#58b058;background-image:-moz-linear-gradient(top,#51a351,#62c462);background-image:-webkit-gradient(linear,0 0,0 100%,from(#51a351),to(#62c462));background-image:-webkit-linear-gradient(top,#51a351,#62c462);background-image:-o-linear-gradient(top,#51a351,#62c462);background-image:linear-gradient(to bottom,#51a351,#62c462);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff51a351', endColorstr='#ff62c462', GradientType=0);border-color:#62c462 #62c462 #3b9e3b;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success:hover,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success:hover,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success:focus,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success:focus,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success.active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success.disabled,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success.disabled,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success[disabled],.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success[disabled]{color:#fff;background-color:#62c462;}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success.active{background-color:#42b142 \9}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#f9a123;background-image:-moz-linear-gradient(top,#f89406,#fbb450);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f89406),to(#fbb450));background-image:-webkit-linear-gradient(top,#f89406,#fbb450);background-image:-o-linear-gradient(top,#f89406,#fbb450);background-image:linear-gradient(to bottom,#f89406,#fbb450);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff89406', endColorstr='#fffbb450', GradientType=0);border-color:#fbb450 #fbb450 #f89406;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning:hover,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning:hover,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning:focus,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning:focus,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning.active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning.disabled,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning.disabled,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning[disabled],.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning[disabled]{color:#fff;background-color:#fbb450;}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning.active{background-color:#fa9f1e \9}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#d14641;background-image:-moz-linear-gradient(top,#bd362f,#ee5f5b);background-image:-webkit-gradient(linear,0 0,0 100%,from(#bd362f),to(#ee5f5b));background-image:-webkit-linear-gradient(top,#bd362f,#ee5f5b);background-image:-o-linear-gradient(top,#bd362f,#ee5f5b);background-image:linear-gradient(to bottom,#bd362f,#ee5f5b);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffbd362f', endColorstr='#ffee5f5b', GradientType=0);border-color:#ee5f5b #ee5f5b #e51d18;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger:hover,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger:hover,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger:focus,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger:focus,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger.active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger.disabled,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger.disabled,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger[disabled],.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger[disabled]{color:#fff;background-color:#ee5f5b;}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger.active{background-color:#e9322d \9}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default{color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);background-color:#f0f0f0;background-image:-moz-linear-gradient(top,#e6e6e6,#fff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#fff));background-image:-webkit-linear-gradient(top,#e6e6e6,#fff);background-image:-o-linear-gradient(top,#e6e6e6,#fff);background-image:linear-gradient(to bottom,#e6e6e6,#fff);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6', endColorstr='#ffffffff', GradientType=0);border-color:#fff #fff #d9d9d9;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default:hover,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default:hover,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default:focus,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default:focus,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default.active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default.disabled,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default.disabled,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default[disabled],.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default[disabled]{color:#333;background-color:#fff;}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default:active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default:active,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default.active,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default.active{background-color:#e6e6e6 \9}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;border-left:1px solid #ccc;border-right:1px solid #ccc;color:#333;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.bootstrap-switch .bootstrap-switch-label:hover,.bootstrap-switch .bootstrap-switch-label:focus,.bootstrap-switch .bootstrap-switch-label:active,.bootstrap-switch .bootstrap-switch-label.active,.bootstrap-switch .bootstrap-switch-label.disabled,.bootstrap-switch .bootstrap-switch-label[disabled]{color:#333;background-color:#e6e6e6;}.bootstrap-switch .bootstrap-switch-label:active,.bootstrap-switch .bootstrap-switch-label.active{background-color:#ccc \9}.bootstrap-switch .bootstrap-switch-handle-on{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}.bootstrap-switch .bootstrap-switch-handle-off{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}.bootstrap-switch input[type=radio],.bootstrap-switch input[type=checkbox]{position:absolute!important;top:0;left:0;opacity:0;filter:alpha(opacity=0);z-index:-1}.bootstrap-switch input[type=radio].form-control,.bootstrap-switch input[type=checkbox].form-control{height:auto}.bootstrap-switch.bootstrap-switch-mini{min-width:71px}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding:3px 6px;font-size:10px;line-height:9px}.bootstrap-switch.bootstrap-switch-small{min-width:79px}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding:3px 6px;font-size:12px;line-height:18px}.bootstrap-switch.bootstrap-switch-large{min-width:120px}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding:9px 12px;font-size:16px;line-height:normal}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-readonly,.bootstrap-switch.bootstrap-switch-indeterminate{cursor:default!important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label{opacity:.5;filter:alpha(opacity=50);cursor:default!important}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left .5s;-moz-transition:margin-left .5s;-o-transition:margin-left .5s;transition:margin-left .5s}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{-webkit-border-top-left-radius:0;-moz-border-radius-topleft:0;border-top-left-radius:0;-webkit-border-bottom-left-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0;-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{-webkit-border-top-right-radius:0;-moz-border-radius-topright:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomright:0;border-bottom-right-radius:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px}.bootstrap-switch.bootstrap-switch-focused{border-color:rgba(82,168,236,.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6)}.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px}.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px} \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css b/vendor/assets/components/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css new file mode 100644 index 000000000..cea4acb8e --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css @@ -0,0 +1,196 @@ +/* ======================================================================== + * bootstrap-switch - v3.3.2 + * http://www.bootstrap-switch.org + * ======================================================================== + * Copyright 2012-2013 Mattia Larentis + * + * ======================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== + */ + +.bootstrap-switch { + display: inline-block; + direction: ltr; + cursor: pointer; + border-radius: 4px; + border: 1px solid; + border-color: #cccccc; + position: relative; + text-align: left; + overflow: hidden; + line-height: 8px; + z-index: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + vertical-align: middle; + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.bootstrap-switch .bootstrap-switch-container { + display: inline-block; + top: 0; + border-radius: 4px; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} +.bootstrap-switch .bootstrap-switch-handle-on, +.bootstrap-switch .bootstrap-switch-handle-off, +.bootstrap-switch .bootstrap-switch-label { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer; + display: inline-block !important; + height: 100%; + padding: 6px 12px; + font-size: 14px; + line-height: 20px; +} +.bootstrap-switch .bootstrap-switch-handle-on, +.bootstrap-switch .bootstrap-switch-handle-off { + text-align: center; + z-index: 1; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary { + color: #fff; + background: #428bca; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info { + color: #fff; + background: #5bc0de; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success { + color: #fff; + background: #5cb85c; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning { + background: #f0ad4e; + color: #fff; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger { + color: #fff; + background: #d9534f; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default { + color: #000; + background: #eeeeee; +} +.bootstrap-switch .bootstrap-switch-label { + text-align: center; + margin-top: -1px; + margin-bottom: -1px; + z-index: 100; + color: #333333; + background: #ffffff; +} +.bootstrap-switch .bootstrap-switch-handle-on { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} +.bootstrap-switch .bootstrap-switch-handle-off { + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.bootstrap-switch input[type='radio'], +.bootstrap-switch input[type='checkbox'] { + position: absolute !important; + top: 0; + left: 0; + opacity: 0; + filter: alpha(opacity=0); + z-index: -1; +} +.bootstrap-switch input[type='radio'].form-control, +.bootstrap-switch input[type='checkbox'].form-control { + height: auto; +} +.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; +} +.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label { + padding: 6px 16px; + font-size: 18px; + line-height: 1.33; +} +.bootstrap-switch.bootstrap-switch-disabled, +.bootstrap-switch.bootstrap-switch-readonly, +.bootstrap-switch.bootstrap-switch-indeterminate { + cursor: default !important; +} +.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label { + opacity: 0.5; + filter: alpha(opacity=50); + cursor: default !important; +} +.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container { + -webkit-transition: margin-left 0.5s; + transition: margin-left 0.5s; +} +.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} +.bootstrap-switch.bootstrap-switch-focused { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); +} +.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label { + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} diff --git a/vendor/assets/components/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.min.css b/vendor/assets/components/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.min.css new file mode 100644 index 000000000..6eb3d4d9e --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.min.css @@ -0,0 +1,22 @@ +/* ======================================================================== + * bootstrap-switch - v3.3.2 + * http://www.bootstrap-switch.org + * ======================================================================== + * Copyright 2012-2013 Mattia Larentis + * + * ======================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== + */ + +.bootstrap-switch{display:inline-block;direction:ltr;cursor:pointer;border-radius:4px;border:1px solid;border-color:#ccc;position:relative;text-align:left;overflow:hidden;line-height:8px;z-index:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.bootstrap-switch .bootstrap-switch-container{display:inline-block;top:0;border-radius:4px;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0)}.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:inline-block !important;height:100%;padding:6px 12px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-handle-off{text-align:center;z-index:1}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary{color:#fff;background:#428bca}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info{color:#fff;background:#5bc0de}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success{color:#fff;background:#5cb85c}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning{background:#f0ad4e;color:#fff}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger{color:#fff;background:#d9534f}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default{color:#000;background:#eee}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;color:#333;background:#fff}.bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch input[type='radio'],.bootstrap-switch input[type='checkbox']{position:absolute !important;top:0;left:0;opacity:0;filter:alpha(opacity=0);z-index:-1}.bootstrap-switch input[type='radio'].form-control,.bootstrap-switch input[type='checkbox'].form-control{height:auto}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding:1px 5px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding:5px 10px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding:6px 16px;font-size:18px;line-height:1.33}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-readonly,.bootstrap-switch.bootstrap-switch-indeterminate{cursor:default !important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label{opacity:.5;filter:alpha(opacity=50);cursor:default !important}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left .5s;transition:margin-left .5s}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{border-bottom-left-radius:0;border-top-left-radius:0;border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{border-bottom-right-radius:0;border-top-right-radius:0;border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch.bootstrap-switch-focused{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label{border-bottom-left-radius:3px;border-top-left-radius:3px} \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/dist/js/bootstrap-switch.js b/vendor/assets/components/bootstrap-switch/dist/js/bootstrap-switch.js new file mode 100644 index 000000000..496704818 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/dist/js/bootstrap-switch.js @@ -0,0 +1,710 @@ +/* ======================================================================== + * bootstrap-switch - v3.3.2 + * http://www.bootstrap-switch.org + * ======================================================================== + * Copyright 2012-2013 Mattia Larentis + * + * ======================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== + */ + +(function() { + var __slice = [].slice; + + (function($, window) { + "use strict"; + var BootstrapSwitch; + BootstrapSwitch = (function() { + function BootstrapSwitch(element, options) { + if (options == null) { + options = {}; + } + this.$element = $(element); + this.options = $.extend({}, $.fn.bootstrapSwitch.defaults, { + state: this.$element.is(":checked"), + size: this.$element.data("size"), + animate: this.$element.data("animate"), + disabled: this.$element.is(":disabled"), + readonly: this.$element.is("[readonly]"), + indeterminate: this.$element.data("indeterminate"), + inverse: this.$element.data("inverse"), + radioAllOff: this.$element.data("radio-all-off"), + onColor: this.$element.data("on-color"), + offColor: this.$element.data("off-color"), + onText: this.$element.data("on-text"), + offText: this.$element.data("off-text"), + labelText: this.$element.data("label-text"), + handleWidth: this.$element.data("handle-width"), + labelWidth: this.$element.data("label-width"), + baseClass: this.$element.data("base-class"), + wrapperClass: this.$element.data("wrapper-class") + }, options); + this.$wrapper = $("
    ", { + "class": (function(_this) { + return function() { + var classes; + classes = ["" + _this.options.baseClass].concat(_this._getClasses(_this.options.wrapperClass)); + classes.push(_this.options.state ? "" + _this.options.baseClass + "-on" : "" + _this.options.baseClass + "-off"); + if (_this.options.size != null) { + classes.push("" + _this.options.baseClass + "-" + _this.options.size); + } + if (_this.options.disabled) { + classes.push("" + _this.options.baseClass + "-disabled"); + } + if (_this.options.readonly) { + classes.push("" + _this.options.baseClass + "-readonly"); + } + if (_this.options.indeterminate) { + classes.push("" + _this.options.baseClass + "-indeterminate"); + } + if (_this.options.inverse) { + classes.push("" + _this.options.baseClass + "-inverse"); + } + if (_this.$element.attr("id")) { + classes.push("" + _this.options.baseClass + "-id-" + (_this.$element.attr("id"))); + } + return classes.join(" "); + }; + })(this)() + }); + this.$container = $("
    ", { + "class": "" + this.options.baseClass + "-container" + }); + this.$on = $("", { + html: this.options.onText, + "class": "" + this.options.baseClass + "-handle-on " + this.options.baseClass + "-" + this.options.onColor + }); + this.$off = $("", { + html: this.options.offText, + "class": "" + this.options.baseClass + "-handle-off " + this.options.baseClass + "-" + this.options.offColor + }); + this.$label = $("", { + html: this.options.labelText, + "class": "" + this.options.baseClass + "-label" + }); + this.$element.on("init.bootstrapSwitch", (function(_this) { + return function() { + return _this.options.onInit.apply(element, arguments); + }; + })(this)); + this.$element.on("switchChange.bootstrapSwitch", (function(_this) { + return function() { + return _this.options.onSwitchChange.apply(element, arguments); + }; + })(this)); + this.$container = this.$element.wrap(this.$container).parent(); + this.$wrapper = this.$container.wrap(this.$wrapper).parent(); + this.$element.before(this.options.inverse ? this.$off : this.$on).before(this.$label).before(this.options.inverse ? this.$on : this.$off); + if (this.options.indeterminate) { + this.$element.prop("indeterminate", true); + } + this._init(); + this._elementHandlers(); + this._handleHandlers(); + this._labelHandlers(); + this._formHandler(); + this._externalLabelHandler(); + this.$element.trigger("init.bootstrapSwitch"); + } + + BootstrapSwitch.prototype._constructor = BootstrapSwitch; + + BootstrapSwitch.prototype.state = function(value, skip) { + if (typeof value === "undefined") { + return this.options.state; + } + if (this.options.disabled || this.options.readonly) { + return this.$element; + } + if (this.options.state && !this.options.radioAllOff && this.$element.is(":radio")) { + return this.$element; + } + if (this.options.indeterminate) { + this.indeterminate(false); + } + value = !!value; + this.$element.prop("checked", value).trigger("change.bootstrapSwitch", skip); + return this.$element; + }; + + BootstrapSwitch.prototype.toggleState = function(skip) { + if (this.options.disabled || this.options.readonly) { + return this.$element; + } + if (this.options.indeterminate) { + this.indeterminate(false); + return this.state(true); + } else { + return this.$element.prop("checked", !this.options.state).trigger("change.bootstrapSwitch", skip); + } + }; + + BootstrapSwitch.prototype.size = function(value) { + if (typeof value === "undefined") { + return this.options.size; + } + if (this.options.size != null) { + this.$wrapper.removeClass("" + this.options.baseClass + "-" + this.options.size); + } + if (value) { + this.$wrapper.addClass("" + this.options.baseClass + "-" + value); + } + this._width(); + this._containerPosition(); + this.options.size = value; + return this.$element; + }; + + BootstrapSwitch.prototype.animate = function(value) { + if (typeof value === "undefined") { + return this.options.animate; + } + value = !!value; + if (value === this.options.animate) { + return this.$element; + } + return this.toggleAnimate(); + }; + + BootstrapSwitch.prototype.toggleAnimate = function() { + this.options.animate = !this.options.animate; + this.$wrapper.toggleClass("" + this.options.baseClass + "-animate"); + return this.$element; + }; + + BootstrapSwitch.prototype.disabled = function(value) { + if (typeof value === "undefined") { + return this.options.disabled; + } + value = !!value; + if (value === this.options.disabled) { + return this.$element; + } + return this.toggleDisabled(); + }; + + BootstrapSwitch.prototype.toggleDisabled = function() { + this.options.disabled = !this.options.disabled; + this.$element.prop("disabled", this.options.disabled); + this.$wrapper.toggleClass("" + this.options.baseClass + "-disabled"); + return this.$element; + }; + + BootstrapSwitch.prototype.readonly = function(value) { + if (typeof value === "undefined") { + return this.options.readonly; + } + value = !!value; + if (value === this.options.readonly) { + return this.$element; + } + return this.toggleReadonly(); + }; + + BootstrapSwitch.prototype.toggleReadonly = function() { + this.options.readonly = !this.options.readonly; + this.$element.prop("readonly", this.options.readonly); + this.$wrapper.toggleClass("" + this.options.baseClass + "-readonly"); + return this.$element; + }; + + BootstrapSwitch.prototype.indeterminate = function(value) { + if (typeof value === "undefined") { + return this.options.indeterminate; + } + value = !!value; + if (value === this.options.indeterminate) { + return this.$element; + } + return this.toggleIndeterminate(); + }; + + BootstrapSwitch.prototype.toggleIndeterminate = function() { + this.options.indeterminate = !this.options.indeterminate; + this.$element.prop("indeterminate", this.options.indeterminate); + this.$wrapper.toggleClass("" + this.options.baseClass + "-indeterminate"); + this._containerPosition(); + return this.$element; + }; + + BootstrapSwitch.prototype.inverse = function(value) { + if (typeof value === "undefined") { + return this.options.inverse; + } + value = !!value; + if (value === this.options.inverse) { + return this.$element; + } + return this.toggleInverse(); + }; + + BootstrapSwitch.prototype.toggleInverse = function() { + var $off, $on; + this.$wrapper.toggleClass("" + this.options.baseClass + "-inverse"); + $on = this.$on.clone(true); + $off = this.$off.clone(true); + this.$on.replaceWith($off); + this.$off.replaceWith($on); + this.$on = $off; + this.$off = $on; + this.options.inverse = !this.options.inverse; + return this.$element; + }; + + BootstrapSwitch.prototype.onColor = function(value) { + var color; + color = this.options.onColor; + if (typeof value === "undefined") { + return color; + } + if (color != null) { + this.$on.removeClass("" + this.options.baseClass + "-" + color); + } + this.$on.addClass("" + this.options.baseClass + "-" + value); + this.options.onColor = value; + return this.$element; + }; + + BootstrapSwitch.prototype.offColor = function(value) { + var color; + color = this.options.offColor; + if (typeof value === "undefined") { + return color; + } + if (color != null) { + this.$off.removeClass("" + this.options.baseClass + "-" + color); + } + this.$off.addClass("" + this.options.baseClass + "-" + value); + this.options.offColor = value; + return this.$element; + }; + + BootstrapSwitch.prototype.onText = function(value) { + if (typeof value === "undefined") { + return this.options.onText; + } + this.$on.html(value); + this._width(); + this._containerPosition(); + this.options.onText = value; + return this.$element; + }; + + BootstrapSwitch.prototype.offText = function(value) { + if (typeof value === "undefined") { + return this.options.offText; + } + this.$off.html(value); + this._width(); + this._containerPosition(); + this.options.offText = value; + return this.$element; + }; + + BootstrapSwitch.prototype.labelText = function(value) { + if (typeof value === "undefined") { + return this.options.labelText; + } + this.$label.html(value); + this._width(); + this.options.labelText = value; + return this.$element; + }; + + BootstrapSwitch.prototype.handleWidth = function(value) { + if (typeof value === "undefined") { + return this.options.handleWidth; + } + this.options.handleWidth = value; + this._width(); + this._containerPosition(); + return this.$element; + }; + + BootstrapSwitch.prototype.labelWidth = function(value) { + if (typeof value === "undefined") { + return this.options.labelWidth; + } + this.options.labelWidth = value; + this._width(); + this._containerPosition(); + return this.$element; + }; + + BootstrapSwitch.prototype.baseClass = function(value) { + return this.options.baseClass; + }; + + BootstrapSwitch.prototype.wrapperClass = function(value) { + if (typeof value === "undefined") { + return this.options.wrapperClass; + } + if (!value) { + value = $.fn.bootstrapSwitch.defaults.wrapperClass; + } + this.$wrapper.removeClass(this._getClasses(this.options.wrapperClass).join(" ")); + this.$wrapper.addClass(this._getClasses(value).join(" ")); + this.options.wrapperClass = value; + return this.$element; + }; + + BootstrapSwitch.prototype.radioAllOff = function(value) { + if (typeof value === "undefined") { + return this.options.radioAllOff; + } + value = !!value; + if (value === this.options.radioAllOff) { + return this.$element; + } + this.options.radioAllOff = value; + return this.$element; + }; + + BootstrapSwitch.prototype.onInit = function(value) { + if (typeof value === "undefined") { + return this.options.onInit; + } + if (!value) { + value = $.fn.bootstrapSwitch.defaults.onInit; + } + this.options.onInit = value; + return this.$element; + }; + + BootstrapSwitch.prototype.onSwitchChange = function(value) { + if (typeof value === "undefined") { + return this.options.onSwitchChange; + } + if (!value) { + value = $.fn.bootstrapSwitch.defaults.onSwitchChange; + } + this.options.onSwitchChange = value; + return this.$element; + }; + + BootstrapSwitch.prototype.destroy = function() { + var $form; + $form = this.$element.closest("form"); + if ($form.length) { + $form.off("reset.bootstrapSwitch").removeData("bootstrap-switch"); + } + this.$container.children().not(this.$element).remove(); + this.$element.unwrap().unwrap().off(".bootstrapSwitch").removeData("bootstrap-switch"); + return this.$element; + }; + + BootstrapSwitch.prototype._width = function() { + var $handles, handleWidth; + $handles = this.$on.add(this.$off); + $handles.add(this.$label).css("width", ""); + handleWidth = this.options.handleWidth === "auto" ? Math.max(this.$on.width(), this.$off.width()) : this.options.handleWidth; + $handles.width(handleWidth); + this.$label.width((function(_this) { + return function(index, width) { + if (_this.options.labelWidth !== "auto") { + return _this.options.labelWidth; + } + if (width < handleWidth) { + return handleWidth; + } else { + return width; + } + }; + })(this)); + this._handleWidth = this.$on.outerWidth(); + this._labelWidth = this.$label.outerWidth(); + this.$container.width((this._handleWidth * 2) + this._labelWidth); + return this.$wrapper.width(this._handleWidth + this._labelWidth); + }; + + BootstrapSwitch.prototype._containerPosition = function(state, callback) { + if (state == null) { + state = this.options.state; + } + this.$container.css("margin-left", (function(_this) { + return function() { + var values; + values = [0, "-" + _this._handleWidth + "px"]; + if (_this.options.indeterminate) { + return "-" + (_this._handleWidth / 2) + "px"; + } + if (state) { + if (_this.options.inverse) { + return values[1]; + } else { + return values[0]; + } + } else { + if (_this.options.inverse) { + return values[0]; + } else { + return values[1]; + } + } + }; + })(this)); + if (!callback) { + return; + } + return setTimeout(function() { + return callback(); + }, 50); + }; + + BootstrapSwitch.prototype._init = function() { + var init, initInterval; + init = (function(_this) { + return function() { + _this._width(); + return _this._containerPosition(null, function() { + if (_this.options.animate) { + return _this.$wrapper.addClass("" + _this.options.baseClass + "-animate"); + } + }); + }; + })(this); + if (this.$wrapper.is(":visible")) { + return init(); + } + return initInterval = window.setInterval((function(_this) { + return function() { + if (_this.$wrapper.is(":visible")) { + init(); + return window.clearInterval(initInterval); + } + }; + })(this), 50); + }; + + BootstrapSwitch.prototype._elementHandlers = function() { + return this.$element.on({ + "change.bootstrapSwitch": (function(_this) { + return function(e, skip) { + var state; + e.preventDefault(); + e.stopImmediatePropagation(); + state = _this.$element.is(":checked"); + _this._containerPosition(state); + if (state === _this.options.state) { + return; + } + _this.options.state = state; + _this.$wrapper.toggleClass("" + _this.options.baseClass + "-off").toggleClass("" + _this.options.baseClass + "-on"); + if (!skip) { + if (_this.$element.is(":radio")) { + $("[name='" + (_this.$element.attr('name')) + "']").not(_this.$element).prop("checked", false).trigger("change.bootstrapSwitch", true); + } + return _this.$element.trigger("switchChange.bootstrapSwitch", [state]); + } + }; + })(this), + "focus.bootstrapSwitch": (function(_this) { + return function(e) { + e.preventDefault(); + return _this.$wrapper.addClass("" + _this.options.baseClass + "-focused"); + }; + })(this), + "blur.bootstrapSwitch": (function(_this) { + return function(e) { + e.preventDefault(); + return _this.$wrapper.removeClass("" + _this.options.baseClass + "-focused"); + }; + })(this), + "keydown.bootstrapSwitch": (function(_this) { + return function(e) { + if (!e.which || _this.options.disabled || _this.options.readonly) { + return; + } + switch (e.which) { + case 37: + e.preventDefault(); + e.stopImmediatePropagation(); + return _this.state(false); + case 39: + e.preventDefault(); + e.stopImmediatePropagation(); + return _this.state(true); + } + }; + })(this) + }); + }; + + BootstrapSwitch.prototype._handleHandlers = function() { + this.$on.on("click.bootstrapSwitch", (function(_this) { + return function(event) { + event.preventDefault(); + event.stopPropagation(); + _this.state(false); + return _this.$element.trigger("focus.bootstrapSwitch"); + }; + })(this)); + return this.$off.on("click.bootstrapSwitch", (function(_this) { + return function(event) { + event.preventDefault(); + event.stopPropagation(); + _this.state(true); + return _this.$element.trigger("focus.bootstrapSwitch"); + }; + })(this)); + }; + + BootstrapSwitch.prototype._labelHandlers = function() { + return this.$label.on({ + "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (function(_this) { + return function(e) { + if (_this._dragStart || _this.options.disabled || _this.options.readonly) { + return; + } + e.preventDefault(); + e.stopPropagation(); + _this._dragStart = (e.pageX || e.originalEvent.touches[0].pageX) - parseInt(_this.$container.css("margin-left"), 10); + if (_this.options.animate) { + _this.$wrapper.removeClass("" + _this.options.baseClass + "-animate"); + } + return _this.$element.trigger("focus.bootstrapSwitch"); + }; + })(this), + "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (function(_this) { + return function(e) { + var difference; + if (_this._dragStart == null) { + return; + } + e.preventDefault(); + difference = (e.pageX || e.originalEvent.touches[0].pageX) - _this._dragStart; + if (difference < -_this._handleWidth || difference > 0) { + return; + } + _this._dragEnd = difference; + return _this.$container.css("margin-left", "" + _this._dragEnd + "px"); + }; + })(this), + "mouseup.bootstrapSwitch touchend.bootstrapSwitch": (function(_this) { + return function(e) { + var state; + if (!_this._dragStart) { + return; + } + e.preventDefault(); + if (_this.options.animate) { + _this.$wrapper.addClass("" + _this.options.baseClass + "-animate"); + } + if (_this._dragEnd) { + state = _this._dragEnd > -(_this._handleWidth / 2); + _this._dragEnd = false; + _this.state(_this.options.inverse ? !state : state); + } else { + _this.state(!_this.options.state); + } + return _this._dragStart = false; + }; + })(this), + "mouseleave.bootstrapSwitch": (function(_this) { + return function(e) { + return _this.$label.trigger("mouseup.bootstrapSwitch"); + }; + })(this) + }); + }; + + BootstrapSwitch.prototype._externalLabelHandler = function() { + var $externalLabel; + $externalLabel = this.$element.closest("label"); + return $externalLabel.on("click", (function(_this) { + return function(event) { + event.preventDefault(); + event.stopImmediatePropagation(); + if (event.target === $externalLabel[0]) { + return _this.toggleState(); + } + }; + })(this)); + }; + + BootstrapSwitch.prototype._formHandler = function() { + var $form; + $form = this.$element.closest("form"); + if ($form.data("bootstrap-switch")) { + return; + } + return $form.on("reset.bootstrapSwitch", function() { + return window.setTimeout(function() { + return $form.find("input").filter(function() { + return $(this).data("bootstrap-switch"); + }).each(function() { + return $(this).bootstrapSwitch("state", this.checked); + }); + }, 1); + }).data("bootstrap-switch", true); + }; + + BootstrapSwitch.prototype._getClasses = function(classes) { + var c, cls, _i, _len; + if (!$.isArray(classes)) { + return ["" + this.options.baseClass + "-" + classes]; + } + cls = []; + for (_i = 0, _len = classes.length; _i < _len; _i++) { + c = classes[_i]; + cls.push("" + this.options.baseClass + "-" + c); + } + return cls; + }; + + return BootstrapSwitch; + + })(); + $.fn.bootstrapSwitch = function() { + var args, option, ret; + option = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + ret = this; + this.each(function() { + var $this, data; + $this = $(this); + data = $this.data("bootstrap-switch"); + if (!data) { + $this.data("bootstrap-switch", data = new BootstrapSwitch(this, option)); + } + if (typeof option === "string") { + return ret = data[option].apply(data, args); + } + }); + return ret; + }; + $.fn.bootstrapSwitch.Constructor = BootstrapSwitch; + return $.fn.bootstrapSwitch.defaults = { + state: true, + size: null, + animate: true, + disabled: false, + readonly: false, + indeterminate: false, + inverse: false, + radioAllOff: false, + onColor: "primary", + offColor: "default", + onText: "ON", + offText: "OFF", + labelText: " ", + handleWidth: "auto", + labelWidth: "auto", + baseClass: "bootstrap-switch", + wrapperClass: "wrapper", + onInit: function() {}, + onSwitchChange: function() {} + }; + })(window.jQuery, window); + +}).call(this); diff --git a/vendor/assets/components/bootstrap-switch/dist/js/bootstrap-switch.min.js b/vendor/assets/components/bootstrap-switch/dist/js/bootstrap-switch.min.js new file mode 100644 index 000000000..232ed1390 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/dist/js/bootstrap-switch.min.js @@ -0,0 +1,22 @@ +/* ======================================================================== + * bootstrap-switch - v3.3.2 + * http://www.bootstrap-switch.org + * ======================================================================== + * Copyright 2012-2013 Mattia Larentis + * + * ======================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== + */ + +(function(){var t=[].slice;!function(e,i){"use strict";var n;return n=function(){function t(t,i){null==i&&(i={}),this.$element=e(t),this.options=e.extend({},e.fn.bootstrapSwitch.defaults,{state:this.$element.is(":checked"),size:this.$element.data("size"),animate:this.$element.data("animate"),disabled:this.$element.is(":disabled"),readonly:this.$element.is("[readonly]"),indeterminate:this.$element.data("indeterminate"),inverse:this.$element.data("inverse"),radioAllOff:this.$element.data("radio-all-off"),onColor:this.$element.data("on-color"),offColor:this.$element.data("off-color"),onText:this.$element.data("on-text"),offText:this.$element.data("off-text"),labelText:this.$element.data("label-text"),handleWidth:this.$element.data("handle-width"),labelWidth:this.$element.data("label-width"),baseClass:this.$element.data("base-class"),wrapperClass:this.$element.data("wrapper-class")},i),this.$wrapper=e("
    ",{"class":function(t){return function(){var e;return e=[""+t.options.baseClass].concat(t._getClasses(t.options.wrapperClass)),e.push(t.options.state?""+t.options.baseClass+"-on":""+t.options.baseClass+"-off"),null!=t.options.size&&e.push(""+t.options.baseClass+"-"+t.options.size),t.options.disabled&&e.push(""+t.options.baseClass+"-disabled"),t.options.readonly&&e.push(""+t.options.baseClass+"-readonly"),t.options.indeterminate&&e.push(""+t.options.baseClass+"-indeterminate"),t.options.inverse&&e.push(""+t.options.baseClass+"-inverse"),t.$element.attr("id")&&e.push(""+t.options.baseClass+"-id-"+t.$element.attr("id")),e.join(" ")}}(this)()}),this.$container=e("
    ",{"class":""+this.options.baseClass+"-container"}),this.$on=e("",{html:this.options.onText,"class":""+this.options.baseClass+"-handle-on "+this.options.baseClass+"-"+this.options.onColor}),this.$off=e("",{html:this.options.offText,"class":""+this.options.baseClass+"-handle-off "+this.options.baseClass+"-"+this.options.offColor}),this.$label=e("",{html:this.options.labelText,"class":""+this.options.baseClass+"-label"}),this.$element.on("init.bootstrapSwitch",function(e){return function(){return e.options.onInit.apply(t,arguments)}}(this)),this.$element.on("switchChange.bootstrapSwitch",function(e){return function(){return e.options.onSwitchChange.apply(t,arguments)}}(this)),this.$container=this.$element.wrap(this.$container).parent(),this.$wrapper=this.$container.wrap(this.$wrapper).parent(),this.$element.before(this.options.inverse?this.$off:this.$on).before(this.$label).before(this.options.inverse?this.$on:this.$off),this.options.indeterminate&&this.$element.prop("indeterminate",!0),this._init(),this._elementHandlers(),this._handleHandlers(),this._labelHandlers(),this._formHandler(),this._externalLabelHandler(),this.$element.trigger("init.bootstrapSwitch")}return t.prototype._constructor=t,t.prototype.state=function(t,e){return"undefined"==typeof t?this.options.state:this.options.disabled||this.options.readonly?this.$element:this.options.state&&!this.options.radioAllOff&&this.$element.is(":radio")?this.$element:(this.options.indeterminate&&this.indeterminate(!1),t=!!t,this.$element.prop("checked",t).trigger("change.bootstrapSwitch",e),this.$element)},t.prototype.toggleState=function(t){return this.options.disabled||this.options.readonly?this.$element:this.options.indeterminate?(this.indeterminate(!1),this.state(!0)):this.$element.prop("checked",!this.options.state).trigger("change.bootstrapSwitch",t)},t.prototype.size=function(t){return"undefined"==typeof t?this.options.size:(null!=this.options.size&&this.$wrapper.removeClass(""+this.options.baseClass+"-"+this.options.size),t&&this.$wrapper.addClass(""+this.options.baseClass+"-"+t),this._width(),this._containerPosition(),this.options.size=t,this.$element)},t.prototype.animate=function(t){return"undefined"==typeof t?this.options.animate:(t=!!t,t===this.options.animate?this.$element:this.toggleAnimate())},t.prototype.toggleAnimate=function(){return this.options.animate=!this.options.animate,this.$wrapper.toggleClass(""+this.options.baseClass+"-animate"),this.$element},t.prototype.disabled=function(t){return"undefined"==typeof t?this.options.disabled:(t=!!t,t===this.options.disabled?this.$element:this.toggleDisabled())},t.prototype.toggleDisabled=function(){return this.options.disabled=!this.options.disabled,this.$element.prop("disabled",this.options.disabled),this.$wrapper.toggleClass(""+this.options.baseClass+"-disabled"),this.$element},t.prototype.readonly=function(t){return"undefined"==typeof t?this.options.readonly:(t=!!t,t===this.options.readonly?this.$element:this.toggleReadonly())},t.prototype.toggleReadonly=function(){return this.options.readonly=!this.options.readonly,this.$element.prop("readonly",this.options.readonly),this.$wrapper.toggleClass(""+this.options.baseClass+"-readonly"),this.$element},t.prototype.indeterminate=function(t){return"undefined"==typeof t?this.options.indeterminate:(t=!!t,t===this.options.indeterminate?this.$element:this.toggleIndeterminate())},t.prototype.toggleIndeterminate=function(){return this.options.indeterminate=!this.options.indeterminate,this.$element.prop("indeterminate",this.options.indeterminate),this.$wrapper.toggleClass(""+this.options.baseClass+"-indeterminate"),this._containerPosition(),this.$element},t.prototype.inverse=function(t){return"undefined"==typeof t?this.options.inverse:(t=!!t,t===this.options.inverse?this.$element:this.toggleInverse())},t.prototype.toggleInverse=function(){var t,e;return this.$wrapper.toggleClass(""+this.options.baseClass+"-inverse"),e=this.$on.clone(!0),t=this.$off.clone(!0),this.$on.replaceWith(t),this.$off.replaceWith(e),this.$on=t,this.$off=e,this.options.inverse=!this.options.inverse,this.$element},t.prototype.onColor=function(t){var e;return e=this.options.onColor,"undefined"==typeof t?e:(null!=e&&this.$on.removeClass(""+this.options.baseClass+"-"+e),this.$on.addClass(""+this.options.baseClass+"-"+t),this.options.onColor=t,this.$element)},t.prototype.offColor=function(t){var e;return e=this.options.offColor,"undefined"==typeof t?e:(null!=e&&this.$off.removeClass(""+this.options.baseClass+"-"+e),this.$off.addClass(""+this.options.baseClass+"-"+t),this.options.offColor=t,this.$element)},t.prototype.onText=function(t){return"undefined"==typeof t?this.options.onText:(this.$on.html(t),this._width(),this._containerPosition(),this.options.onText=t,this.$element)},t.prototype.offText=function(t){return"undefined"==typeof t?this.options.offText:(this.$off.html(t),this._width(),this._containerPosition(),this.options.offText=t,this.$element)},t.prototype.labelText=function(t){return"undefined"==typeof t?this.options.labelText:(this.$label.html(t),this._width(),this.options.labelText=t,this.$element)},t.prototype.handleWidth=function(t){return"undefined"==typeof t?this.options.handleWidth:(this.options.handleWidth=t,this._width(),this._containerPosition(),this.$element)},t.prototype.labelWidth=function(t){return"undefined"==typeof t?this.options.labelWidth:(this.options.labelWidth=t,this._width(),this._containerPosition(),this.$element)},t.prototype.baseClass=function(){return this.options.baseClass},t.prototype.wrapperClass=function(t){return"undefined"==typeof t?this.options.wrapperClass:(t||(t=e.fn.bootstrapSwitch.defaults.wrapperClass),this.$wrapper.removeClass(this._getClasses(this.options.wrapperClass).join(" ")),this.$wrapper.addClass(this._getClasses(t).join(" ")),this.options.wrapperClass=t,this.$element)},t.prototype.radioAllOff=function(t){return"undefined"==typeof t?this.options.radioAllOff:(t=!!t,t===this.options.radioAllOff?this.$element:(this.options.radioAllOff=t,this.$element))},t.prototype.onInit=function(t){return"undefined"==typeof t?this.options.onInit:(t||(t=e.fn.bootstrapSwitch.defaults.onInit),this.options.onInit=t,this.$element)},t.prototype.onSwitchChange=function(t){return"undefined"==typeof t?this.options.onSwitchChange:(t||(t=e.fn.bootstrapSwitch.defaults.onSwitchChange),this.options.onSwitchChange=t,this.$element)},t.prototype.destroy=function(){var t;return t=this.$element.closest("form"),t.length&&t.off("reset.bootstrapSwitch").removeData("bootstrap-switch"),this.$container.children().not(this.$element).remove(),this.$element.unwrap().unwrap().off(".bootstrapSwitch").removeData("bootstrap-switch"),this.$element},t.prototype._width=function(){var t,e;return t=this.$on.add(this.$off),t.add(this.$label).css("width",""),e="auto"===this.options.handleWidth?Math.max(this.$on.width(),this.$off.width()):this.options.handleWidth,t.width(e),this.$label.width(function(t){return function(i,n){return"auto"!==t.options.labelWidth?t.options.labelWidth:e>n?e:n}}(this)),this._handleWidth=this.$on.outerWidth(),this._labelWidth=this.$label.outerWidth(),this.$container.width(2*this._handleWidth+this._labelWidth),this.$wrapper.width(this._handleWidth+this._labelWidth)},t.prototype._containerPosition=function(t,e){return null==t&&(t=this.options.state),this.$container.css("margin-left",function(e){return function(){var i;return i=[0,"-"+e._handleWidth+"px"],e.options.indeterminate?"-"+e._handleWidth/2+"px":t?e.options.inverse?i[1]:i[0]:e.options.inverse?i[0]:i[1]}}(this)),e?setTimeout(function(){return e()},50):void 0},t.prototype._init=function(){var t,e;return t=function(t){return function(){return t._width(),t._containerPosition(null,function(){return t.options.animate?t.$wrapper.addClass(""+t.options.baseClass+"-animate"):void 0})}}(this),this.$wrapper.is(":visible")?t():e=i.setInterval(function(n){return function(){return n.$wrapper.is(":visible")?(t(),i.clearInterval(e)):void 0}}(this),50)},t.prototype._elementHandlers=function(){return this.$element.on({"change.bootstrapSwitch":function(t){return function(i,n){var o;return i.preventDefault(),i.stopImmediatePropagation(),o=t.$element.is(":checked"),t._containerPosition(o),o!==t.options.state?(t.options.state=o,t.$wrapper.toggleClass(""+t.options.baseClass+"-off").toggleClass(""+t.options.baseClass+"-on"),n?void 0:(t.$element.is(":radio")&&e("[name='"+t.$element.attr("name")+"']").not(t.$element).prop("checked",!1).trigger("change.bootstrapSwitch",!0),t.$element.trigger("switchChange.bootstrapSwitch",[o]))):void 0}}(this),"focus.bootstrapSwitch":function(t){return function(e){return e.preventDefault(),t.$wrapper.addClass(""+t.options.baseClass+"-focused")}}(this),"blur.bootstrapSwitch":function(t){return function(e){return e.preventDefault(),t.$wrapper.removeClass(""+t.options.baseClass+"-focused")}}(this),"keydown.bootstrapSwitch":function(t){return function(e){if(e.which&&!t.options.disabled&&!t.options.readonly)switch(e.which){case 37:return e.preventDefault(),e.stopImmediatePropagation(),t.state(!1);case 39:return e.preventDefault(),e.stopImmediatePropagation(),t.state(!0)}}}(this)})},t.prototype._handleHandlers=function(){return this.$on.on("click.bootstrapSwitch",function(t){return function(e){return e.preventDefault(),e.stopPropagation(),t.state(!1),t.$element.trigger("focus.bootstrapSwitch")}}(this)),this.$off.on("click.bootstrapSwitch",function(t){return function(e){return e.preventDefault(),e.stopPropagation(),t.state(!0),t.$element.trigger("focus.bootstrapSwitch")}}(this))},t.prototype._labelHandlers=function(){return this.$label.on({"mousedown.bootstrapSwitch touchstart.bootstrapSwitch":function(t){return function(e){return t._dragStart||t.options.disabled||t.options.readonly?void 0:(e.preventDefault(),e.stopPropagation(),t._dragStart=(e.pageX||e.originalEvent.touches[0].pageX)-parseInt(t.$container.css("margin-left"),10),t.options.animate&&t.$wrapper.removeClass(""+t.options.baseClass+"-animate"),t.$element.trigger("focus.bootstrapSwitch"))}}(this),"mousemove.bootstrapSwitch touchmove.bootstrapSwitch":function(t){return function(e){var i;if(null!=t._dragStart&&(e.preventDefault(),i=(e.pageX||e.originalEvent.touches[0].pageX)-t._dragStart,!(i<-t._handleWidth||i>0)))return t._dragEnd=i,t.$container.css("margin-left",""+t._dragEnd+"px")}}(this),"mouseup.bootstrapSwitch touchend.bootstrapSwitch":function(t){return function(e){var i;if(t._dragStart)return e.preventDefault(),t.options.animate&&t.$wrapper.addClass(""+t.options.baseClass+"-animate"),t._dragEnd?(i=t._dragEnd>-(t._handleWidth/2),t._dragEnd=!1,t.state(t.options.inverse?!i:i)):t.state(!t.options.state),t._dragStart=!1}}(this),"mouseleave.bootstrapSwitch":function(t){return function(){return t.$label.trigger("mouseup.bootstrapSwitch")}}(this)})},t.prototype._externalLabelHandler=function(){var t;return t=this.$element.closest("label"),t.on("click",function(e){return function(i){return i.preventDefault(),i.stopImmediatePropagation(),i.target===t[0]?e.toggleState():void 0}}(this))},t.prototype._formHandler=function(){var t;return t=this.$element.closest("form"),t.data("bootstrap-switch")?void 0:t.on("reset.bootstrapSwitch",function(){return i.setTimeout(function(){return t.find("input").filter(function(){return e(this).data("bootstrap-switch")}).each(function(){return e(this).bootstrapSwitch("state",this.checked)})},1)}).data("bootstrap-switch",!0)},t.prototype._getClasses=function(t){var i,n,o,s;if(!e.isArray(t))return[""+this.options.baseClass+"-"+t];for(n=[],o=0,s=t.length;s>o;o++)i=t[o],n.push(""+this.options.baseClass+"-"+i);return n},t}(),e.fn.bootstrapSwitch=function(){var i,o,s;return o=arguments[0],i=2<=arguments.length?t.call(arguments,1):[],s=this,this.each(function(){var t,a;return t=e(this),a=t.data("bootstrap-switch"),a||t.data("bootstrap-switch",a=new n(this,o)),"string"==typeof o?s=a[o].apply(a,i):void 0}),s},e.fn.bootstrapSwitch.Constructor=n,e.fn.bootstrapSwitch.defaults={state:!0,size:null,animate:!0,disabled:!1,readonly:!1,indeterminate:!1,inverse:!1,radioAllOff:!1,onColor:"primary",offColor:"default",onText:"ON",offText:"OFF",labelText:" ",handleWidth:"auto",labelWidth:"auto",baseClass:"bootstrap-switch",wrapperClass:"wrapper",onInit:function(){},onSwitchChange:function(){}}}(window.jQuery,window)}).call(this); \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/documentation-2.html b/vendor/assets/components/bootstrap-switch/documentation-2.html new file mode 100644 index 000000000..7f73683d3 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/documentation-2.html @@ -0,0 +1,320 @@ + + + + + + + + + Bootstrap Switch · Turn checkboxes and radio buttons in toggle switches + + + + + + + + Fork me on GitHub + +
    +
    +
    +

    Documentation

    +

    v2.0.1

    +
    +
    +
    +
    + +
    +

    + All the options are accepted only using data-* attributes on the element.
    + checked, disabled and readonly are exception to the rule, being + default HTML input attributes.
    + Therefore, there is not any way to specify the options in JavaScript during initialization. +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescriptionValuesDefault
    stateBooleanThe checkbox statetrue, false'checked' attribute or true
    sizeStringThe checkbox state'', 'mini', 'small', 'normal', 'large'''
    animateBooleanAnimate the switchtrue, falsetrue
    disabledBooleanDisable statetrue, false'disabled' attribute or false
    readonlyBooleanReadonly statetrue, false'readonly' attribute or false
    onStringColor of the left side of the switch'primary', 'info', 'success', 'warning', 'danger', 'default'null
    offStringColor of the right side of the switch'primary', 'info', 'success', 'warning', 'danger', 'default'null
    on-labelStringText of the left side of the switchString'ON'
    off-labelStringText of the right side of the switchString'OFF'
    text-labelStringText of the center handle of the switchString'&nbsp;'
    label-iconStringText of the center handle of the switch. Use to include external services iconsStringnull
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionAccepted ValuesReturned Values
    stateGet checkbox statetrue, false
    setStateSet checkbox state(value: true, false)[, skip: true, false]jQuery Object (input element)
    toggleStateToggle checkbox state[skip: true, false]jQuery Object (input element)
    toggleRadioStateToggle radio state[skip: true, false]jQuery Object (input element)
    toggleRadioStateAllowUncheckToggle radio state allowing uncheck of the radio input[uncheck: true, false | skip: true, false]jQuery Object (input element)
    setSizeClassSet the size of the switch'', 'mini', 'small', 'normal', 'large'jQuery Object (input element)
    setAnimatedAnimate the switchtrue, falsejQuery Object (input element)
    isDisabledGet disabled statetrue, false
    setDisabledSet disable statetrue, falsejQuery Object (input element)
    toggleDisabledToggle disabled statejQuery Object (input element)
    isReadOnlyGet Readonly statetrue, false
    setReadOnlySet Readonly statetrue, falsejQuery Object (input element)
    toggleReadOnlyToggle readonly statejQuery Object (input element)
    setOnClassColor of the left side of the switch'primary', 'info', 'success', 'warning', 'danger', 'default'jQuery Object (input element)
    setOffClassColor of the right side of the switch'primary', 'info', 'success', 'warning', 'danger', 'default'jQuery Object (input element)
    setOnLabelText of the left side of the switchStringjQuery Object (input element)
    setOffLabelText of the right side of the switchStringjQuery Object (input element)
    setTextLabelText of the center handle of the switchStringnull
    setTextIconText of the center handle of the switch. Use to include external services iconsStringnull
    destroyDestroy the instance of Bootstrap SwitchjQuery Object (input element)
    +
    +
    + +

    + The only event triggered it switch-change. It returns two parameters: event and + data.
    + The latter is an object that include el (the input DOM element) and value (the + new input state) +

    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/events.html b/vendor/assets/components/bootstrap-switch/events.html new file mode 100644 index 000000000..3a7dca0fc --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/events.html @@ -0,0 +1,108 @@ + + + + + + + + + Bootstrap Switch · Turn checkboxes and radio buttons in toggle switches + + + + + + + + Fork me on GitHub + +
    +
    +
    +

    Events

    +
    +
    +
    +

    + All the events are namespaced, therefore always append .bootstrapSwitch when you + attach your handlers.
    + You can register to the emitted events as follow: +

    +
    $('input[name="my-checkbox"]').on('switchChange.bootstrapSwitch', function(event, state) {
    +  console.log(this); // DOM element
    +  console.log(event); // jQuery event
    +  console.log(state); // true | false
    +});
    + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionParameters
    initTriggered on initialization. 'this' refers to the DOM element.event (jQuery Event object)
    switchChangeTriggered on switch state change. 'this' refers to the DOM element. + event (jQuery Event object), + state (true | false) +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/examples.html b/vendor/assets/components/bootstrap-switch/examples.html new file mode 100644 index 000000000..24a8cd874 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/examples.html @@ -0,0 +1,288 @@ + + + + + + + + + Bootstrap Switch · Turn checkboxes and radio buttons in toggle switches + + + + + + + + Fork me on GitHub + +
    +
    +
    +

    Examples

    +
    +
    +
    +
    +
    +

    State

    +

    + +

    +
    + + + + +
    +
    +
    +

    Size

    +

    + +

    +
    + + + + + +
    +
    +
    +

    Animate

    +

    + +

    +

    + + +

    +
    +
    +

    Disabled

    +

    + +

    +

    + + +

    +
    +
    +

    Readonly

    +

    + +

    +

    + + +

    +
    +
    +

    Indeterminate

    +

    + +

    +

    + + +

    +
    +
    +

    Inverse

    +

    + +

    +

    + + +

    +
    +
    +

    On Color

    +

    + +

    +

    +

    + + +
    + +

    +
    +
    +

    Off Color

    +

    + +

    +

    +

    + + +
    + +

    +
    +
    +

    On Text

    +

    + +

    +
    +
    + +
    +
    +
    +
    +

    Off Text

    +

    + +

    +
    +
    + +
    +
    +
    +
    +

    Label Text

    +

    + +

    +
    +
    + +
    +
    +
    +
    +

    Handle Width

    +

    + +

    +
    +
    + +
    +
    +
    +
    +

    Label Width

    +

    + +

    +
    +
    + +
    +
    +
    +
    +

    Create | Destroy

    +

    + +

    +
    +
    + +
    +
    +
    +


    +
    +

    Radio All Off

    +
    +
    +

    Disabled

    + + + +
    +
    +

    Enabled

    + + + +
    +

    +
    +

    Inside Modals

    + + +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/karma.json b/vendor/assets/components/bootstrap-switch/karma.json new file mode 100644 index 000000000..89d3f7725 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/karma.json @@ -0,0 +1,19 @@ +{ + "frameworks": ["jasmine"], + "files": [ + "bower_components/jquery/dist/jquery.js", + "bower_components/bootstrap/dist/js/bootstrap.js", + "test/bootstrap-switch.js", + "test/bootstrap-switch.tests.js" + ], + "reporters": ["progress"], + "port": 9876, + "colors": true, + "autoWatch": true, + "browsers": ["Firefox"], + "singleRun": false, + "plugins": [ + "karma-jasmine", + "karma-firefox-launcher" + ] +} diff --git a/vendor/assets/components/bootstrap-switch/main.html b/vendor/assets/components/bootstrap-switch/main.html new file mode 100644 index 000000000..5353329c5 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/main.html @@ -0,0 +1,67 @@ + + + + + + + + + Bootstrap Switch · Turn checkboxes and radio buttons in toggle switches + + + + + + + + Fork me on GitHub + +
    +
    + + + + + + + + \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/methods.html b/vendor/assets/components/bootstrap-switch/methods.html new file mode 100644 index 000000000..fb6517bf9 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/methods.html @@ -0,0 +1,123 @@ + + + + + + + + + Bootstrap Switch · Turn checkboxes and radio buttons in toggle switches + + + + + + + + Fork me on GitHub + +
    +
    +
    +

    Methods

    +
    +
    +
    +

    In Bootstrap Switch, every option is also a method.

    +

    If the second parameter is omitted, the method return the current value.

    +

    You can invoke methods as follows:

    +
    $('input[name="my-checkbox"]').bootstrapSwitch('state', true, true);
    +

    Additional Methods

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescription
    toggleStateToggle the switch state
    toggleAnimateToggle the animate option
    toggleDisabledToggle the disabled state
    toggleReadonlyToggle the readonly state
    toggleIndeterminateToggle the indeterminate state
    toggleInverseToggle the inverse option
    destroyDestroy the instance of Bootstrap Switch
    +

    Special Behaviours

    +
      +
    • The method state can receive an optional third parameter skip. if true, switchChange event is not executed. The default is false.
    • +
    • The method toggleState can receive an optional second parameter skip. if true, switchChange event is not executed. The default is false.
    • +
    • The method wrapperClass can accepts a falsy value as second parameter. If so, it resets the class to its default.
    • +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/options.html b/vendor/assets/components/bootstrap-switch/options.html new file mode 100644 index 000000000..d5480214a --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/options.html @@ -0,0 +1,248 @@ + + + + + + + + + Bootstrap Switch · Turn checkboxes and radio buttons in toggle switches + + + + + + + + Fork me on GitHub + +
    +
    +
    +

    Options

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameAttributeTypeDescriptionValuesDefault
    statecheckedBooleanThe checkbox statetrue, falsetrue
    sizedata-sizeStringThe checkbox sizenull, 'mini', 'small', 'normal', 'large'null
    animatedata-animateBooleanAnimate the switchtrue, falsetrue
    disableddisabledBooleanDisable statetrue, falsefalse
    readonlyreadonlyBooleanReadonly statetrue, falsefalse
    indeterminatedata-indeterminateBooleanIndeterminate statetrue, falsefalse
    inversedata-inverseBooleanInverse switch directiontrue, falsefalse
    radioAllOffdata-radio-all-offBooleanAllow this radio button to be unchecked by the usertrue, falsefalse
    onColordata-on-colorStringColor of the left side of the switch'primary', 'info', 'success', 'warning', 'danger', 'default''primary'
    offColordata-off-colorStringColor of the right side of the switch'primary', 'info', 'success', 'warning', 'danger', 'default''default'
    onTextdata-on-textStringText of the left side of the switchString'ON'
    offTextdata-off-textStringText of the right side of the switchString'OFF'
    labelTextdata-label-textStringText of the center handle of the switchString'&nbsp;'
    handleWidthdata-handle-widthString | NumberWidth of the left and right sides in pixels'auto' or Number'auto'
    labelWidthdata-label-widthString | NumberWidth of the center handle in pixels'auto' or Number'auto'
    baseClassdata-base-classStringGlobal class prefixString'bootstrap-switch'
    wrapperClassdata-wrapper-classString | ArrayContainer element class(es)String | Array'wrapper'
    onInitFunctionCallback function to execute on initializationFunction +
    function(event, state) {}
    +
    onSwitchChangeFunctionCallback function to execute on switch state changeFunction +
    function(event, state) {}
    +
    +

    Global Defaults Overriding

    +

    Follow the jQuery convention to override the default options of the library. For instance:

    +
    $.fn.bootstrapSwitch.defaults.size = 'large';
    +$.fn.bootstrapSwitch.defaults.onColor = 'success';
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/src/coffee/bootstrap-switch.coffee b/vendor/assets/components/bootstrap-switch/src/coffee/bootstrap-switch.coffee new file mode 100644 index 000000000..5f6d4feaa --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/src/coffee/bootstrap-switch.coffee @@ -0,0 +1,525 @@ +do ($ = window.jQuery, window) -> + "use strict" + + class BootstrapSwitch + constructor: (element, options = {}) -> + @$element = $ element + @options = $.extend {}, $.fn.bootstrapSwitch.defaults, + state: @$element.is ":checked" + size: @$element.data "size" + animate: @$element.data "animate" + disabled: @$element.is ":disabled" + readonly: @$element.is "[readonly]" + indeterminate: @$element.data "indeterminate" + inverse: @$element.data "inverse" + radioAllOff: @$element.data "radio-all-off" + onColor: @$element.data "on-color" + offColor: @$element.data "off-color" + onText: @$element.data "on-text" + offText: @$element.data "off-text" + labelText: @$element.data "label-text" + handleWidth: @$element.data "handle-width" + labelWidth: @$element.data "label-width" + baseClass: @$element.data "base-class" + wrapperClass: @$element.data "wrapper-class" + , options + @$wrapper = $ "
    ", + class: do => + classes = ["#{@options.baseClass}"].concat @_getClasses @options.wrapperClass + + classes.push if @options.state then "#{@options.baseClass}-on" else "#{@options.baseClass}-off" + classes.push "#{@options.baseClass}-#{@options.size}" if @options.size? + classes.push "#{@options.baseClass}-disabled" if @options.disabled + classes.push "#{@options.baseClass}-readonly" if @options.readonly + classes.push "#{@options.baseClass}-indeterminate" if @options.indeterminate + classes.push "#{@options.baseClass}-inverse" if @options.inverse + classes.push "#{@options.baseClass}-id-#{@$element.attr("id")}" if @$element.attr "id" + classes.join " " + @$container = $ "
    ", + class: "#{@options.baseClass}-container" + @$on = $ "", + html: @options.onText, + class: "#{@options.baseClass}-handle-on #{@options.baseClass}-#{@options.onColor}" + @$off = $ "", + html: @options.offText, + class: "#{@options.baseClass}-handle-off #{@options.baseClass}-#{@options.offColor}" + @$label = $ "", + html: @options.labelText + class: "#{@options.baseClass}-label" + + # set up events + @$element.on "init.bootstrapSwitch", => @options.onInit.apply element, arguments + @$element.on "switchChange.bootstrapSwitch", => @options.onSwitchChange.apply element, arguments + + # reassign elements after dom modification + @$container = @$element.wrap(@$container).parent() + @$wrapper = @$container.wrap(@$wrapper).parent() + + # insert handles and label and trigger event + @$element + .before(if @options.inverse then @$off else @$on) + .before(@$label) + .before(if @options.inverse then @$on else @$off) + + # indeterminate state + @$element.prop "indeterminate", true if @options.indeterminate + + # normalize handles width and set container position + @_init() + + # initialise handlers + @_elementHandlers() + @_handleHandlers() + @_labelHandlers() + @_formHandler() + @_externalLabelHandler() + + @$element.trigger "init.bootstrapSwitch" + + _constructor: BootstrapSwitch + + state: (value, skip) -> + return @options.state if typeof value is "undefined" + return @$element if @options.disabled or @options.readonly + return @$element if @options.state and not @options.radioAllOff and @$element.is ":radio" + + # remove indeterminate + @indeterminate false if @options.indeterminate + value = not not value + + @$element.prop("checked", value).trigger "change.bootstrapSwitch", skip + @$element + + toggleState: (skip) -> + return @$element if @options.disabled or @options.readonly + + if @options.indeterminate + @indeterminate false + @state true + else + @$element.prop("checked", not @options.state).trigger "change.bootstrapSwitch", skip + + size: (value) -> + return @options.size if typeof value is "undefined" + + @$wrapper.removeClass "#{@options.baseClass}-#{@options.size}" if @options.size? + @$wrapper.addClass "#{@options.baseClass}-#{value}" if value + @_width() + @_containerPosition() + @options.size = value + @$element + + animate: (value) -> + return @options.animate if typeof value is "undefined" + + value = not not value + return @$element if value is @options.animate + + @toggleAnimate() + + toggleAnimate: -> + @options.animate = not @options.animate + + @$wrapper.toggleClass "#{@options.baseClass}-animate" + @$element + + disabled: (value) -> + return @options.disabled if typeof value is "undefined" + + value = not not value + return @$element if value is @options.disabled + + @toggleDisabled() + + toggleDisabled: -> + @options.disabled = not @options.disabled + + @$element.prop "disabled", @options.disabled + @$wrapper.toggleClass "#{@options.baseClass}-disabled" + @$element + + readonly: (value) -> + return @options.readonly if typeof value is "undefined" + + value = not not value + return @$element if value is @options.readonly + + @toggleReadonly() + + toggleReadonly: -> + @options.readonly = not @options.readonly + + @$element.prop "readonly", @options.readonly + @$wrapper.toggleClass "#{@options.baseClass}-readonly" + @$element + + indeterminate: (value) -> + return @options.indeterminate if typeof value is "undefined" + + value = not not value + return @$element if value is @options.indeterminate + + @toggleIndeterminate() + + toggleIndeterminate: -> + @options.indeterminate = not @options.indeterminate + + @$element.prop "indeterminate", @options.indeterminate + @$wrapper.toggleClass "#{@options.baseClass}-indeterminate" + @_containerPosition() + @$element + + inverse: (value) -> + return @options.inverse if typeof value is "undefined" + + value = not not value + return @$element if value is @options.inverse + + @toggleInverse() + + toggleInverse: -> + @$wrapper.toggleClass "#{@options.baseClass}-inverse" + $on = @$on.clone true + $off = @$off.clone true + @$on.replaceWith $off + @$off.replaceWith $on + @$on = $off + @$off = $on + @options.inverse = not @options.inverse + @$element + + onColor: (value) -> + color = @options.onColor + + return color if typeof value is "undefined" + + @$on.removeClass "#{@options.baseClass}-#{color}" if color? + @$on.addClass "#{@options.baseClass}-#{value}" + @options.onColor = value + @$element + + offColor: (value) -> + color = @options.offColor + + return color if typeof value is "undefined" + + @$off.removeClass "#{@options.baseClass}-#{color}" if color? + @$off.addClass "#{@options.baseClass}-#{value}" + @options.offColor = value + @$element + + onText: (value) -> + return @options.onText if typeof value is "undefined" + + @$on.html value + @_width() + @_containerPosition() + @options.onText = value + @$element + + offText: (value) -> + return @options.offText if typeof value is "undefined" + + @$off.html value + @_width() + @_containerPosition() + @options.offText = value + @$element + + labelText: (value) -> + return @options.labelText if typeof value is "undefined" + + @$label.html value + @_width() + @options.labelText = value + @$element + + handleWidth: (value) -> + return @options.handleWidth if typeof value is "undefined" + + @options.handleWidth = value + @_width() + @_containerPosition() + @$element + + labelWidth: (value) -> + return @options.labelWidth if typeof value is "undefined" + + @options.labelWidth = value + @_width() + @_containerPosition() + @$element + + baseClass: (value) -> + @options.baseClass + + wrapperClass: (value) -> + return @options.wrapperClass if typeof value is "undefined" + + value = $.fn.bootstrapSwitch.defaults.wrapperClass unless value + + @$wrapper.removeClass @_getClasses(@options.wrapperClass).join " " + @$wrapper.addClass @_getClasses(value).join " " + @options.wrapperClass = value + @$element + + radioAllOff: (value) -> + return @options.radioAllOff if typeof value is "undefined" + + value = not not value + return @$element if value is @options.radioAllOff + + @options.radioAllOff = value + @$element + + onInit: (value) -> + return @options.onInit if typeof value is "undefined" + + value = $.fn.bootstrapSwitch.defaults.onInit unless value + + @options.onInit = value + @$element + + onSwitchChange: (value) -> + return @options.onSwitchChange if typeof value is "undefined" + + value = $.fn.bootstrapSwitch.defaults.onSwitchChange unless value + + @options.onSwitchChange = value + @$element + + destroy: -> + $form = @$element.closest "form" + + $form.off("reset.bootstrapSwitch").removeData "bootstrap-switch" if $form.length + @$container.children().not(@$element).remove() + @$element.unwrap().unwrap().off(".bootstrapSwitch").removeData "bootstrap-switch" + @$element + + _width: -> + $handles = @$on.add(@$off) + + # remove width from inline style + $handles.add(@$label).css("width", "") + + # save handleWidth for further label width calculation check + handleWidth = if @options.handleWidth is "auto" + then Math.max @$on.width(), @$off.width() + else @options.handleWidth + + # set handles width + $handles.width handleWidth + + # set label width + @$label.width (index, width) => + return @options.labelWidth if @options.labelWidth isnt "auto" + + if width < handleWidth then handleWidth else width + + # get handle and label widths + @_handleWidth = @$on.outerWidth() + @_labelWidth = @$label.outerWidth() + + # set container and wrapper widths + @$container.width (@_handleWidth * 2) + @_labelWidth + @$wrapper.width @_handleWidth + @_labelWidth + + _containerPosition: (state = @options.state, callback) -> + @$container + .css "margin-left", => + values = [0, "-#{@_handleWidth}px"] + + return "-#{@_handleWidth / 2}px" if @options.indeterminate + + if state + return if @options.inverse then values[1] else values[0] + else + return if @options.inverse then values[0] else values[1] + + return unless callback + + setTimeout -> + callback() + , 50 + + _init: -> + init = => + @_width() + @_containerPosition null, => + @$wrapper.addClass "#{@options.baseClass}-animate" if @options.animate + + return init() if @$wrapper.is ":visible" + + initInterval = window.setInterval => + if @$wrapper.is ":visible" + init() + window.clearInterval initInterval + , 50 + + _elementHandlers: -> + @$element.on + "change.bootstrapSwitch": (e, skip) => + e.preventDefault() + e.stopImmediatePropagation() + + state = @$element.is ":checked" + + @_containerPosition state + return if state is @options.state + + @options.state = state + @$wrapper.toggleClass("#{@options.baseClass}-off").toggleClass "#{@options.baseClass}-on" + + unless skip + if @$element.is ":radio" + $("[name='#{@$element.attr('name')}']") + .not(@$element) + .prop("checked", false) + .trigger "change.bootstrapSwitch", true + + @$element.trigger "switchChange.bootstrapSwitch", [state] + + "focus.bootstrapSwitch": (e) => + e.preventDefault() + @$wrapper.addClass "#{@options.baseClass}-focused" + + "blur.bootstrapSwitch": (e) => + e.preventDefault() + @$wrapper.removeClass "#{@options.baseClass}-focused" + + "keydown.bootstrapSwitch": (e) => + return if not e.which or @options.disabled or @options.readonly + + switch e.which + when 37 + e.preventDefault() + e.stopImmediatePropagation() + + @state false + when 39 + e.preventDefault() + e.stopImmediatePropagation() + + @state true + + _handleHandlers: -> + @$on.on "click.bootstrapSwitch", (event) => + event.preventDefault() + event.stopPropagation() + + @state false + @$element.trigger "focus.bootstrapSwitch" + + @$off.on "click.bootstrapSwitch", (event) => + event.preventDefault() + event.stopPropagation() + + @state true + @$element.trigger "focus.bootstrapSwitch" + + _labelHandlers: -> + @$label.on + "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (e) => + return if @_dragStart or @options.disabled or @options.readonly + + e.preventDefault() + e.stopPropagation() + + @_dragStart = (e.pageX or e.originalEvent.touches[0].pageX) - parseInt @$container.css("margin-left"), 10 + @$wrapper.removeClass "#{@options.baseClass}-animate" if @options.animate + @$element.trigger "focus.bootstrapSwitch" + + "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (e) => + return unless @_dragStart? + + e.preventDefault() + + difference = (e.pageX or e.originalEvent.touches[0].pageX) - @_dragStart + return if difference < -@_handleWidth or difference > 0 + + @_dragEnd = difference + @$container.css "margin-left", "#{@_dragEnd}px" + + "mouseup.bootstrapSwitch touchend.bootstrapSwitch": (e) => + return unless @_dragStart + + e.preventDefault() + + @$wrapper.addClass "#{@options.baseClass}-animate" if @options.animate + if @_dragEnd + state = @_dragEnd > -(@_handleWidth / 2) + + @_dragEnd = false + @state if @options.inverse then not state else state + else + @state not @options.state + + @_dragStart = false + + "mouseleave.bootstrapSwitch": (e) => + @$label.trigger "mouseup.bootstrapSwitch" + + _externalLabelHandler: -> + $externalLabel = @$element.closest("label") + + $externalLabel.on "click", (event) => + event.preventDefault() + event.stopImmediatePropagation() + + # reimplement toggle state on external label only if it is not the target + @toggleState() if event.target is $externalLabel[0] + + _formHandler: -> + $form = @$element.closest "form" + + return if $form.data "bootstrap-switch" + + $form + .on "reset.bootstrapSwitch", -> + window.setTimeout -> + $form + .find("input") + .filter( -> $(@).data "bootstrap-switch") + .each -> $(@).bootstrapSwitch "state", @checked + , 1 + .data "bootstrap-switch", true + + _getClasses: (classes) -> + return ["#{@options.baseClass}-#{classes}"] unless $.isArray classes + + cls = [] + for c in classes + cls.push "#{@options.baseClass}-#{c}" + cls + + $.fn.bootstrapSwitch = (option, args...) -> + ret = @ + @each -> + $this = $ @ + data = $this.data "bootstrap-switch" + + $this.data "bootstrap-switch", data = new BootstrapSwitch @, option unless data + ret = data[option].apply data, args if typeof option is "string" + ret + + $.fn.bootstrapSwitch.Constructor = BootstrapSwitch + $.fn.bootstrapSwitch.defaults = + state: true + size: null + animate: true + disabled: false + readonly: false + indeterminate: false + inverse: false + radioAllOff: false + onColor: "primary" + offColor: "default" + onText: "ON" + offText: "OFF" + labelText: " " + handleWidth: "auto" + labelWidth: "auto" + baseClass: "bootstrap-switch" + wrapperClass: "wrapper" + onInit: -> + onSwitchChange: -> diff --git a/vendor/assets/components/bootstrap-switch/src/coffee/bootstrap-switch.tests.coffee b/vendor/assets/components/bootstrap-switch/src/coffee/bootstrap-switch.tests.coffee new file mode 100644 index 000000000..90db76899 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/src/coffee/bootstrap-switch.tests.coffee @@ -0,0 +1,27 @@ +describe "Bootstrap Switch", -> + + beforeEach -> + $.support.transition = false + $.fx.off = true + + afterEach -> + $(".#{$.fn.bootstrapSwitch.defaults.baseClass}").bootstrapSwitch "destroy" + + createElement = -> + $("", + type: "checkbox" + class: "switch" + ).appendTo "body" + + getOptions = ($element) -> + $element.data("bootstrap-switch").options + + it "should set the default options as element options, except state", -> + $switch = createElement().prop("checked", true).bootstrapSwitch() + expect(getOptions($switch)).toEqual $.fn.bootstrapSwitch.defaults + + it "should override default options with initialization ones", -> + $switch = createElement().prop("checked", false).bootstrapSwitch() + $switch2 = createElement().bootstrapSwitch state: false + expect(getOptions($switch).state).toBe false + expect(getOptions($switch2).state).toBe false diff --git a/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/bootstrap-switch.less b/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/bootstrap-switch.less new file mode 100644 index 000000000..360f414e2 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/bootstrap-switch.less @@ -0,0 +1,193 @@ +@bootstrap-switch-base: bootstrap-switch; + +.@{bootstrap-switch-base} { + display: inline-block; + direction: ltr; + cursor: pointer; + .border-radius(5px); + border: 1px solid; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + position: relative; + text-align: left; + overflow: hidden; + line-height: 8px; + z-index: 0; + .user-select(none); + vertical-align: middle; + .transition(~"border-color ease-in-out .15s, box-shadow ease-in-out .15s"); + + .@{bootstrap-switch-base}-container { + display: inline-block; + top: 0; + .border-radius(4px); + .translate3d(0, 0, 0); + } + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off, + .@{bootstrap-switch-base}-label { + .box-sizing(border-box); + cursor: pointer; + display: inline-block !important; + height: 100%; + padding-top: 4px; + padding-bottom: 4px; + padding-left: 8px; + padding-right: 8px; + font-size: 14px; + line-height: 20px; + } + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off { + text-align: center; + z-index: 1; + + &.@{bootstrap-switch-base}-primary { + .buttonBackground(@btnPrimaryBackgroundHighlight, @btnPrimaryBackground); + } + + &.@{bootstrap-switch-base}-info { + .buttonBackground(@btnInfoBackgroundHighlight, @btnInfoBackground); + } + + &.@{bootstrap-switch-base}-success { + .buttonBackground(@btnSuccessBackgroundHighlight, @btnSuccessBackground); + } + + &.@{bootstrap-switch-base}-warning { + .buttonBackground(@btnWarningBackgroundHighlight, @btnWarningBackground); + } + + &.@{bootstrap-switch-base}-danger { + .buttonBackground(@btnDangerBackgroundHighlight, @btnDangerBackground); + } + + &.@{bootstrap-switch-base}-default { + .buttonBackground(@btnBackgroundHighlight, @btnBackground, @grayDark, 0 1px 1px rgba(255,255,255,.75)); + } + } + + .@{bootstrap-switch-base}-label { + text-align: center; + margin-top: -1px; + margin-bottom: -1px; + z-index: 100; + border-left: 1px solid @btnBorder; + border-right: 1px solid @btnBorder; + .buttonBackground(@btnBackground, @btnBackgroundHighlight, @grayDark); + } + + .@{bootstrap-switch-base}-handle-on { + .border-left-radius(4px); + } + + .@{bootstrap-switch-base}-handle-off { + .border-right-radius(4px); + } + + input[type='radio'], + input[type='checkbox'] { + position: absolute !important; + top: 0; + left: 0; + .opacity(0); + z-index: -1; + + &.form-control { + height: auto; + } + } + + &.@{bootstrap-switch-base}-mini { + min-width: 71px; + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off, + .@{bootstrap-switch-base}-label { + padding: 3px 6px; + font-size: 10px; + line-height: 9px; + } + } + + &.@{bootstrap-switch-base}-small { + min-width: 79px; + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off, + .@{bootstrap-switch-base}-label { + padding: 3px 6px; + font-size: 12px; + line-height: 18px; + } + } + + &.@{bootstrap-switch-base}-large { + min-width: 120px; + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off, + .@{bootstrap-switch-base}-label { + padding: 9px 12px; + font-size: 16px; + line-height: normal; + } + } + + &.@{bootstrap-switch-base}-disabled, + &.@{bootstrap-switch-base}-readonly, + &.@{bootstrap-switch-base}-indeterminate { + cursor: default !important; + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off, + .@{bootstrap-switch-base}-label { + .opacity(50); + cursor: default !important; + } + } + + &.@{bootstrap-switch-base}-animate { + + .@{bootstrap-switch-base}-container { + .transition(margin-left .5s); + } + } + + &.@{bootstrap-switch-base}-inverse { + + .@{bootstrap-switch-base}-handle-on { + .border-left-radius(0); + .border-right-radius(4px); + } + + .@{bootstrap-switch-base}-handle-off { + .border-right-radius(0); + .border-left-radius(4px); + } + } + + &.@{bootstrap-switch-base}-focused { + border-color: rgba(82, 168, 236, .8); + outline: 0; + outline: thin dotted \9; + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82, 168, 236, .6)"); + } + + &.@{bootstrap-switch-base}-on, + &.@{bootstrap-switch-base}-inverse.@{bootstrap-switch-base}-off { + + .@{bootstrap-switch-base}-label { + .border-right-radius(4px); + } + } + + &.@{bootstrap-switch-base}-off, + &.@{bootstrap-switch-base}-inverse.@{bootstrap-switch-base}-on { + + .@{bootstrap-switch-base}-label { + .border-left-radius(4px); + } + } +} diff --git a/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/build.less b/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/build.less new file mode 100644 index 000000000..beda34a1f --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/build.less @@ -0,0 +1,3 @@ +@import "variables"; +@import "mixins"; +@import "bootstrap-switch"; \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/mixins.less b/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/mixins.less new file mode 100644 index 000000000..624338589 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/mixins.less @@ -0,0 +1,702 @@ +// +// Mixins +// -------------------------------------------------- + + +// UTILITY MIXINS +// -------------------------------------------------- + +// Clearfix +// -------- +// For clearing floats like a boss h5bp.com/q +.clearfix { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + // Fixes Opera/contenteditable bug: + // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 + line-height: 0; + } + &:after { + clear: both; + } +} + +// Webkit-style focus +// ------------------ +.tab-focus() { + // Default + outline: thin dotted #333; + // Webkit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +// ---------------------------------- +.center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// IE7 inline-block +// ---------------- +.ie7-inline-block() { + *display: inline; /* IE7 inline-block hack */ + *zoom: 1; +} + +// IE7 likes to collapse whitespace on either side of the inline-block elements. +// Ems because we're attempting to match the width of a space character. Left +// version is for form buttons, which typically come after other elements, and +// right version is for icons, which come before. Applying both is ok, but it will +// mean that space between those elements will be .6em (~2 space characters) in IE7, +// instead of the 1 space in other browsers. +.ie7-restore-left-whitespace() { + *margin-left: .3em; + + &:first-child { + *margin-left: 0; + } +} + +.ie7-restore-right-whitespace() { + *margin-right: .3em; +} + +// Sizing shortcuts +// ------------------------- +.size(@height, @width) { + width: @width; + height: @height; +} +.square(@size) { + .size(@size, @size); +} + +// Placeholder text +// ------------------------- +.placeholder(@color: @placeholderText) { + &:-moz-placeholder { + color: @color; + } + &:-ms-input-placeholder { + color: @color; + } + &::-webkit-input-placeholder { + color: @color; + } +} + +// Text overflow +// ------------------------- +// Requires inline-block or block for proper styling +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + + +// FONTS +// -------------------------------------------------- + +#font { + #family { + .serif() { + font-family: @serifFontFamily; + } + .sans-serif() { + font-family: @sansFontFamily; + } + .monospace() { + font-family: @monoFontFamily; + } + } + .shorthand(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + font-size: @size; + font-weight: @weight; + line-height: @lineHeight; + } + .serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .sans-serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .sans-serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .monospace(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .monospace; + #font > .shorthand(@size, @weight, @lineHeight); + } +} + + +// FORMS +// -------------------------------------------------- + +// Block level inputs +.input-block-level { + display: block; + width: 100%; + min-height: @inputHeight; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + .box-sizing(border-box); // Makes inputs behave like true block-level elements +} + + + +// Mixin for form field states +.formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) { + // Set the text color + .control-label, + .help-block, + .help-inline { + color: @textColor; + } + // Style inputs accordingly + .checkbox, + .radio, + input, + select, + textarea { + color: @textColor; + } + input, + select, + textarea { + border-color: @borderColor; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work + &:focus { + border-color: darken(@borderColor, 10%); + @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@borderColor, 20%); + .box-shadow(@shadow); + } + } + // Give a small background color for input-prepend/-append + .input-prepend .add-on, + .input-append .add-on { + color: @textColor; + background-color: @backgroundColor; + border-color: @textColor; + } +} + + + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Border Radius +.border-radius(@radius) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +// Single Corner Border Radius +.border-top-left-radius(@radius) { + -webkit-border-top-left-radius: @radius; + -moz-border-radius-topleft: @radius; + border-top-left-radius: @radius; +} +.border-top-right-radius(@radius) { + -webkit-border-top-right-radius: @radius; + -moz-border-radius-topright: @radius; + border-top-right-radius: @radius; +} +.border-bottom-right-radius(@radius) { + -webkit-border-bottom-right-radius: @radius; + -moz-border-radius-bottomright: @radius; + border-bottom-right-radius: @radius; +} +.border-bottom-left-radius(@radius) { + -webkit-border-bottom-left-radius: @radius; + -moz-border-radius-bottomleft: @radius; + border-bottom-left-radius: @radius; +} + +// Single Side Border Radius +.border-top-radius(@radius) { + .border-top-right-radius(@radius); + .border-top-left-radius(@radius); +} +.border-right-radius(@radius) { + .border-top-right-radius(@radius); + .border-bottom-right-radius(@radius); +} +.border-bottom-radius(@radius) { + .border-bottom-right-radius(@radius); + .border-bottom-left-radius(@radius); +} +.border-left-radius(@radius) { + .border-top-left-radius(@radius); + .border-bottom-left-radius(@radius); +} + +// Drop shadows +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; + -moz-box-shadow: @shadow; + box-shadow: @shadow; +} + +// Transitions +.transition(@transition) { + -webkit-transition: @transition; + -moz-transition: @transition; + -o-transition: @transition; + transition: @transition; +} +.transition-delay(@transition-delay) { + -webkit-transition-delay: @transition-delay; + -moz-transition-delay: @transition-delay; + -o-transition-delay: @transition-delay; + transition-delay: @transition-delay; +} +.transition-duration(@transition-duration) { + -webkit-transition-duration: @transition-duration; + -moz-transition-duration: @transition-duration; + -o-transition-duration: @transition-duration; + transition-duration: @transition-duration; +} + +// Transformations +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -moz-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); + -o-transform: rotate(@degrees); + transform: rotate(@degrees); +} +.scale(@ratio) { + -webkit-transform: scale(@ratio); + -moz-transform: scale(@ratio); + -ms-transform: scale(@ratio); + -o-transform: scale(@ratio); + transform: scale(@ratio); +} +.translate(@x, @y) { + -webkit-transform: translate(@x, @y); + -moz-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); + -o-transform: translate(@x, @y); + transform: translate(@x, @y); +} +.skew(@x, @y) { + -webkit-transform: skew(@x, @y); + -moz-transform: skew(@x, @y); + -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885 + -o-transform: skew(@x, @y); + transform: skew(@x, @y); + -webkit-backface-visibility: hidden; // See https://github.com/twbs/bootstrap/issues/5319 +} +.translate3d(@x, @y, @z) { + -webkit-transform: translate3d(@x, @y, @z); + -moz-transform: translate3d(@x, @y, @z); + -o-transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden +// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples +.backface-visibility(@visibility){ + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Background clipping +// Heads up: FF 3.6 and under need "padding" instead of "padding-box" +.background-clip(@clip) { + -webkit-background-clip: @clip; + -moz-background-clip: @clip; + background-clip: @clip; +} + +// Background sizing +.background-size(@size) { + -webkit-background-size: @size; + -moz-background-size: @size; + -o-background-size: @size; + background-size: @size; +} + + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// User select +// For selecting text on the page +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; + -o-user-select: @select; + user-select: @select; +} + +// Resize anything +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +.content-columns(@columnCount, @columnGap: @gridGutterWidth) { + -webkit-column-count: @columnCount; + -moz-column-count: @columnCount; + column-count: @columnCount; + -webkit-column-gap: @columnGap; + -moz-column-gap: @columnGap; + column-gap: @columnGap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; + -o-hyphens: @mode; + hyphens: @mode; +} + +// Opacity +.opacity(@opacity) { + opacity: @opacity / 100; + filter: ~"alpha(opacity=@{opacity})"; +} + + + +// BACKGROUNDS +// -------------------------------------------------- + +// Add an alphatransparency value to any background or border color (via Elyse Holladay) +#translucent { + .background(@color: @white, @alpha: 1) { + background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + } + .border(@color: @white, @alpha: 1) { + border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + .background-clip(padding-box); + } +} + +// Gradient Bar Colors for buttons and alerts +.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { + color: @textColor; + text-shadow: @textShadow; + #gradient > .vertical(@primaryColor, @secondaryColor); + border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); +} + +// Gradients +#gradient { + .horizontal(@startColor: #555, @endColor: #333) { + background-color: @endColor; + background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down + } + .vertical(@startColor: #555, @endColor: #333) { + background-color: mix(@startColor, @endColor, 60%); + background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down + } + .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { + background-color: @endColor; + background-repeat: repeat-x; + background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 + } + .horizontal-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: mix(@midColor, @endColor, 80%); + background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(to right, @startColor, @midColor @colorStop, @endColor); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback + } + + .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: mix(@midColor, @endColor, 80%); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@innerColor: #555, @outerColor: #333) { + background-color: @outerColor; + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor)); + background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor); + background-image: -moz-radial-gradient(circle, @innerColor, @outerColor); + background-image: -o-radial-gradient(circle, @innerColor, @outerColor); + background-repeat: no-repeat; + } + .striped(@color: #555, @angle: 45deg) { + background-color: @color; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + } +} +// Reset filters for IE +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} + + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +.nav-divider(@top: #e5e5e5, @bottom: @white) { + // IE7 needs a set width since we gave a height. Restricting just + // to IE7 to keep the 1px left/right space in other browsers. + // It is unclear where IE is getting the extra space that we need + // to negative-margin away, but so it goes. + *width: 100%; + height: 1px; + margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px + *margin: -5px 0 5px; + overflow: hidden; + background-color: @top; + border-bottom: 1px solid @bottom; +} + +// Button backgrounds +// ------------------ +.buttonBackground(@startColor, @endColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { + // gradientBar will set the background to a pleasing blend of these, to support IE<=9 + .gradientBar(@startColor, @endColor, @textColor, @textShadow); + *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + .reset-filter(); + + // in these cases the gradient won't cover the background, so we override + &:hover, &:focus, &:active, &.active, &.disabled, &[disabled] { + color: @textColor; + background-color: @endColor; + *background-color: darken(@endColor, 5%); + } + + // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves + &:active, + &.active { + background-color: darken(@endColor, 10%) e("\9"); + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin. +.navbarVerticalAlign(@elementHeight) { + margin-top: (@navbarHeight - @elementHeight) / 2; +} + + + +// Grid System +// ----------- + +// Centered container element +.container-fixed() { + margin-right: auto; + margin-left: auto; + .clearfix(); +} + +// Table columns +.tableColumns(@columnSpan: 1) { + float: none; // undo default grid column styles + width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells + margin-left: 0; // undo default grid column styles +} + +// Make a Grid +// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior +.makeRow() { + margin-left: @gridGutterWidth * -1; + .clearfix(); +} +.makeColumn(@columns: 1, @offset: 0) { + float: left; + margin-left: (@gridColumnWidth * @offset) + (@gridGutterWidth * (@offset - 1)) + (@gridGutterWidth * 2); + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); +} + +// The Grid +#grid { + + .core (@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + .span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + .offset@{index} { .offset(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1)); + } + + .span (@columns) { + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); + } + + .row { + margin-left: @gridGutterWidth * -1; + .clearfix(); + } + + [class*="span"] { + float: left; + min-height: 1px; // prevent collapsing columns + margin-left: @gridGutterWidth; + } + + // Set the container width, and override it for fixed navbars in media queries + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { .span(@gridColumns); } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + + } + + .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) { + + .spanX (@index) when (@index > 0) { + .span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + .offset@{index} { .offset(@index); } + .offset@{index}:first-child { .offsetFirstChild(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth*2); + *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + (@fluidGridGutterWidth*2) - (.5 / @gridRowWidth * 100 * 1%); + } + + .offsetFirstChild (@columns) { + margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth); + *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + + .span (@columns) { + width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)); + *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%); + } + + .row-fluid { + width: 100%; + .clearfix(); + [class*="span"] { + .input-block-level(); + float: left; + margin-left: @fluidGridGutterWidth; + *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + [class*="span"]:first-child { + margin-left: 0; + } + + // Space grid-sized controls properly if multiple per line + .controls-row [class*="span"] + [class*="span"] { + margin-left: @fluidGridGutterWidth; + } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + } + + } + + .input(@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .span(@columns) { + width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 14; + } + + input, + textarea, + .uneditable-input { + margin-left: 0; // override margin-left from core grid system + } + + // Space grid-sized controls properly if multiple per line + .controls-row [class*="span"] + [class*="span"] { + margin-left: @gridGutterWidth; + } + + // generate .spanX + .spanX (@gridColumns); + + } +} diff --git a/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/variables.less b/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/variables.less new file mode 100644 index 000000000..31c131b1e --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/src/less/bootstrap2/variables.less @@ -0,0 +1,301 @@ +// +// Variables +// -------------------------------------------------- + + +// Global values +// -------------------------------------------------- + + +// Grays +// ------------------------- +@black: #000; +@grayDarker: #222; +@grayDark: #333; +@gray: #555; +@grayLight: #999; +@grayLighter: #eee; +@white: #fff; + + +// Accent colors +// ------------------------- +@blue: #049cdb; +@blueDark: #0064cd; +@green: #46a546; +@red: #9d261d; +@yellow: #ffc40d; +@orange: #f89406; +@pink: #c3325f; +@purple: #7a43b6; + + +// Scaffolding +// ------------------------- +@bodyBackground: @white; +@textColor: @grayDark; + + +// Links +// ------------------------- +@linkColor: #08c; +@linkColorHover: darken(@linkColor, 15%); + + +// Typography +// ------------------------- +@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif; +@serifFontFamily: Georgia, "Times New Roman", Times, serif; +@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace; + +@baseFontSize: 14px; +@baseFontFamily: @sansFontFamily; +@baseLineHeight: 20px; +@altFontFamily: @serifFontFamily; + +@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily +@headingsFontWeight: bold; // instead of browser default, bold +@headingsColor: inherit; // empty to use BS default, @textColor + + +// Component sizing +// ------------------------- +// Based on 14px font-size and 20px line-height + +@fontSizeLarge: @baseFontSize * 1.25; // ~18px +@fontSizeSmall: @baseFontSize * 0.85; // ~12px +@fontSizeMini: @baseFontSize * 0.75; // ~11px + +@paddingLarge: 11px 19px; // 44px +@paddingSmall: 2px 10px; // 26px +@paddingMini: 0 6px; // 22px + +@baseBorderRadius: 4px; +@borderRadiusLarge: 6px; +@borderRadiusSmall: 3px; + + +// Tables +// ------------------------- +@tableBackground: transparent; // overall background-color +@tableBackgroundAccent: #f9f9f9; // for striping +@tableBackgroundHover: #f5f5f5; // for hover +@tableBorder: #ddd; // table and cell border + +// Buttons +// ------------------------- +@btnBackground: @white; +@btnBackgroundHighlight: darken(@white, 10%); +@btnBorder: #ccc; + +@btnPrimaryBackground: @linkColor; +@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 20%); + +@btnInfoBackground: #5bc0de; +@btnInfoBackgroundHighlight: #2f96b4; + +@btnSuccessBackground: #62c462; +@btnSuccessBackgroundHighlight: #51a351; + +@btnWarningBackground: lighten(@orange, 15%); +@btnWarningBackgroundHighlight: @orange; + +@btnDangerBackground: #ee5f5b; +@btnDangerBackgroundHighlight: #bd362f; + +@btnInverseBackground: #444; +@btnInverseBackgroundHighlight: @grayDarker; + + +// Forms +// ------------------------- +@inputBackground: @white; +@inputBorder: #ccc; +@inputBorderRadius: @baseBorderRadius; +@inputDisabledBackground: @grayLighter; +@formActionsBackground: #f5f5f5; +@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border + + +// Dropdowns +// ------------------------- +@dropdownBackground: @white; +@dropdownBorder: rgba(0,0,0,.2); +@dropdownDividerTop: #e5e5e5; +@dropdownDividerBottom: @white; + +@dropdownLinkColor: @grayDark; +@dropdownLinkColorHover: @white; +@dropdownLinkColorActive: @white; + +@dropdownLinkBackgroundActive: @linkColor; +@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive; + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +@zindexDropdown: 1000; +@zindexPopover: 1010; +@zindexTooltip: 1030; +@zindexFixedNavbar: 1030; +@zindexModalBackdrop: 1040; +@zindexModal: 1050; + + +// Sprite icons path +// ------------------------- +@iconSpritePath: "../img/glyphicons-halflings.png"; +@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png"; + + +// Input placeholder text color +// ------------------------- +@placeholderText: @grayLight; + + +// Hr border color +// ------------------------- +@hrBorder: @grayLighter; + + +// Horizontal forms & lists +// ------------------------- +@horizontalComponentOffset: 180px; + + +// Wells +// ------------------------- +@wellBackground: #f5f5f5; + + +// Navbar +// ------------------------- +@navbarCollapseWidth: 979px; +@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1; + +@navbarHeight: 40px; +@navbarBackgroundHighlight: #ffffff; +@navbarBackground: darken(@navbarBackgroundHighlight, 5%); +@navbarBorder: darken(@navbarBackground, 12%); + +@navbarText: #777; +@navbarLinkColor: #777; +@navbarLinkColorHover: @grayDark; +@navbarLinkColorActive: @gray; +@navbarLinkBackgroundHover: transparent; +@navbarLinkBackgroundActive: darken(@navbarBackground, 5%); + +@navbarBrandColor: @navbarLinkColor; + +// Inverted navbar +@navbarInverseBackground: #111111; +@navbarInverseBackgroundHighlight: #222222; +@navbarInverseBorder: #252525; + +@navbarInverseText: @grayLight; +@navbarInverseLinkColor: @grayLight; +@navbarInverseLinkColorHover: @white; +@navbarInverseLinkColorActive: @navbarInverseLinkColorHover; +@navbarInverseLinkBackgroundHover: transparent; +@navbarInverseLinkBackgroundActive: @navbarInverseBackground; + +@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%); +@navbarInverseSearchBackgroundFocus: @white; +@navbarInverseSearchBorder: @navbarInverseBackground; +@navbarInverseSearchPlaceholderColor: #ccc; + +@navbarInverseBrandColor: @navbarInverseLinkColor; + + +// Pagination +// ------------------------- +@paginationBackground: #fff; +@paginationBorder: #ddd; +@paginationActiveBackground: #f5f5f5; + + +// Hero unit +// ------------------------- +@heroUnitBackground: @grayLighter; +@heroUnitHeadingColor: inherit; +@heroUnitLeadColor: inherit; + + +// Form states and alerts +// ------------------------- +@warningText: #c09853; +@warningBackground: #fcf8e3; +@warningBorder: darken(spin(@warningBackground, -10), 3%); + +@errorText: #b94a48; +@errorBackground: #f2dede; +@errorBorder: darken(spin(@errorBackground, -10), 3%); + +@successText: #468847; +@successBackground: #dff0d8; +@successBorder: darken(spin(@successBackground, -10), 5%); + +@infoText: #3a87ad; +@infoBackground: #d9edf7; +@infoBorder: darken(spin(@infoBackground, -10), 7%); + + +// Tooltips and popovers +// ------------------------- +@tooltipColor: #fff; +@tooltipBackground: #000; +@tooltipArrowWidth: 5px; +@tooltipArrowColor: @tooltipBackground; + +@popoverBackground: #fff; +@popoverArrowWidth: 10px; +@popoverArrowColor: #fff; +@popoverTitleBackground: darken(@popoverBackground, 3%); + +// Special enhancement for popovers +@popoverArrowOuterWidth: @popoverArrowWidth + 1; +@popoverArrowOuterColor: rgba(0,0,0,.25); + + + +// GRID +// -------------------------------------------------- + + +// Default 940px grid +// ------------------------- +@gridColumns: 12; +@gridColumnWidth: 60px; +@gridGutterWidth: 20px; +@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// 1200px min +@gridColumnWidth1200: 70px; +@gridGutterWidth1200: 30px; +@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1)); + +// 768px-979px +@gridColumnWidth768: 42px; +@gridGutterWidth768: 20px; +@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1)); + + +// Fluid grid +// ------------------------- +@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth); +@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth); + +// 1200px min +@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200); +@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200); + +// 768px-979px +@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768); +@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768); diff --git a/vendor/assets/components/bootstrap-switch/src/less/bootstrap3/bootstrap-switch.less b/vendor/assets/components/bootstrap-switch/src/less/bootstrap3/bootstrap-switch.less new file mode 100644 index 000000000..4a23c419a --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/src/less/bootstrap3/bootstrap-switch.less @@ -0,0 +1,193 @@ +@bootstrap-switch-base: bootstrap-switch; + +.@{bootstrap-switch-base} { + display: inline-block; + direction: ltr; + cursor: pointer; + border-radius: @border-radius-base; + border: 1px solid; + border-color: @btn-default-border; + position: relative; + text-align: left; + overflow: hidden; + line-height: 8px; + z-index: 0; + .user-select(none); + vertical-align: middle; + .transition(~"border-color ease-in-out .15s, box-shadow ease-in-out .15s"); + + .@{bootstrap-switch-base}-container { + display: inline-block; + top: 0; + border-radius: @border-radius-base; + .translate3d(0, 0, 0); + } + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off, + .@{bootstrap-switch-base}-label { + .box-sizing(border-box); + cursor: pointer; + display: inline-block !important; + height: 100%; + padding: @padding-base-vertical @padding-base-horizontal; + font-size: @font-size-base; + line-height: @line-height-computed; + } + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off { + text-align: center; + z-index: 1; + + &.@{bootstrap-switch-base}-primary { + color: #fff; + background: @btn-primary-bg; + } + + &.@{bootstrap-switch-base}-info { + color: #fff; + background: @btn-info-bg; + } + + &.@{bootstrap-switch-base}-success { + color: #fff; + background: @btn-success-bg; + } + + &.@{bootstrap-switch-base}-warning { + background: @btn-warning-bg; + color: #fff; + } + + &.@{bootstrap-switch-base}-danger { + color: #fff; + background: @btn-danger-bg; + } + + &.@{bootstrap-switch-base}-default { + color: #000; + background: @gray-lighter; + } + } + + .@{bootstrap-switch-base}-label { + text-align: center; + margin-top: -1px; + margin-bottom: -1px; + z-index: 100; + color: @btn-default-color; + background: @btn-default-bg; + } + + .@{bootstrap-switch-base}-handle-on { + .border-left-radius(@border-radius-base - 1); + } + + .@{bootstrap-switch-base}-handle-off { + .border-right-radius(@border-radius-base - 1); + } + + input[type='radio'], + input[type='checkbox'] { + position: absolute !important; + top: 0; + left: 0; + .opacity(0); + z-index: -1; + + &.form-control { + height: auto; + } + } + + &.@{bootstrap-switch-base}-mini { + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off, + .@{bootstrap-switch-base}-label { + padding: @padding-xs-vertical @padding-xs-horizontal; + font-size: @font-size-small; + line-height: @line-height-small; + } + } + + &.@{bootstrap-switch-base}-small { + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off, + .@{bootstrap-switch-base}-label { + padding: @padding-small-vertical @padding-small-horizontal; + font-size: @font-size-small; + line-height: @line-height-small; + } + } + + &.@{bootstrap-switch-base}-large { + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off, + .@{bootstrap-switch-base}-label { + padding: @padding-base-vertical @padding-large-horizontal; + font-size: @font-size-large; + line-height: @line-height-large; + } + } + + &.@{bootstrap-switch-base}-disabled, + &.@{bootstrap-switch-base}-readonly, + &.@{bootstrap-switch-base}-indeterminate { + cursor: default !important; + + .@{bootstrap-switch-base}-handle-on, + .@{bootstrap-switch-base}-handle-off, + .@{bootstrap-switch-base}-label { + .opacity(.5); + cursor: default !important; + } + } + + &.@{bootstrap-switch-base}-animate { + + .@{bootstrap-switch-base}-container { + .transition(margin-left .5s); + } + } + + &.@{bootstrap-switch-base}-inverse { + + .@{bootstrap-switch-base}-handle-on { + .border-left-radius(0); + .border-right-radius(@border-radius-base - 1); + } + + .@{bootstrap-switch-base}-handle-off { + .border-right-radius(0); + .border-left-radius(@border-radius-base - 1); + } + } + + &.@{bootstrap-switch-base}-focused { + @color-rgba: rgba(red(@input-border-focus), green(@input-border-focus), blue(@input-border-focus), .6); + border-color: @input-border-focus; + outline: 0; + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}"); + } + + &.@{bootstrap-switch-base}-on, + &.@{bootstrap-switch-base}-inverse.@{bootstrap-switch-base}-off { + + .@{bootstrap-switch-base}-label { + .border-right-radius(@border-radius-base - 1); + } + } + + &.@{bootstrap-switch-base}-off, + &.@{bootstrap-switch-base}-inverse.@{bootstrap-switch-base}-on { + + + .@{bootstrap-switch-base}-label { + .border-left-radius(@border-radius-base - 1); + } + } +} diff --git a/vendor/assets/components/bootstrap-switch/src/less/bootstrap3/build.less b/vendor/assets/components/bootstrap-switch/src/less/bootstrap3/build.less new file mode 100644 index 000000000..beda34a1f --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/src/less/bootstrap3/build.less @@ -0,0 +1,3 @@ +@import "variables"; +@import "mixins"; +@import "bootstrap-switch"; \ No newline at end of file diff --git a/vendor/assets/components/bootstrap-switch/src/less/bootstrap3/mixins.less b/vendor/assets/components/bootstrap-switch/src/less/bootstrap3/mixins.less new file mode 100644 index 000000000..71723dba4 --- /dev/null +++ b/vendor/assets/components/bootstrap-switch/src/less/bootstrap3/mixins.less @@ -0,0 +1,929 @@ +// +// Mixins +// -------------------------------------------------- + + +// Utilities +// ------------------------- + +// Clearfix +// Source: http://nicolasgallagher.com/micro-clearfix-hack/ +// +// For modern browsers +// 1. The space content is one way to avoid an Opera bug when the +// contenteditable attribute is included anywhere else in the document. +// Otherwise it causes space to appear at the top and bottom of elements +// that are clearfixed. +// 2. The use of `table` rather than `block` is only necessary if using +// `:before` to contain the top-margins of child elements. +.clearfix() { + &:before, + &:after { + content: " "; // 1 + display: table; // 2 + } + &:after { + clear: both; + } +} + +// WebKit-style focus +.tab-focus() { + // Default + outline: thin dotted; + // WebKit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +.center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// Sizing shortcuts +.size(@width; @height) { + width: @width; + height: @height; +} +.square(@size) { + .size(@size; @size); +} + +// Placeholder text +.placeholder(@color: @input-color-placeholder) { + &::-moz-placeholder { color: @color; // Firefox + opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526 + &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+ + &::-webkit-input-placeholder { color: @color; } // Safari and Chrome +} + +// Text overflow +// Requires inline-block or block for proper styling +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// +// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for +// mixins being reused as classes with the same name, this doesn't hold up. As +// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. Note +// that we cannot chain the mixins together in Less, so they are repeated. +// +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 + +// Deprecated as of v3.0.1 (will be removed in v4) +.hide-text() { + font: ~"0/0" a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +// New mixin to use as of v3.0.1 +.text-hide() { + .hide-text(); +} + + + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Single side border-radius +.border-top-radius(@radius) { + border-top-right-radius: @radius; + border-top-left-radius: @radius; +} +.border-right-radius(@radius) { + border-bottom-right-radius: @radius; + border-top-right-radius: @radius; +} +.border-bottom-radius(@radius) { + border-bottom-right-radius: @radius; + border-bottom-left-radius: @radius; +} +.border-left-radius(@radius) { + border-bottom-left-radius: @radius; + border-top-left-radius: @radius; +} + +// Drop shadows +// +// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's +// supported browsers that have box shadow capabilities now support the +// standard `box-shadow` property. +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1 + box-shadow: @shadow; +} + +// Transitions +.transition(@transition) { + -webkit-transition: @transition; + transition: @transition; +} +.transition-property(@transition-property) { + -webkit-transition-property: @transition-property; + transition-property: @transition-property; +} +.transition-delay(@transition-delay) { + -webkit-transition-delay: @transition-delay; + transition-delay: @transition-delay; +} +.transition-duration(@transition-duration) { + -webkit-transition-duration: @transition-duration; + transition-duration: @transition-duration; +} +.transition-transform(@transition) { + -webkit-transition: -webkit-transform @transition; + -moz-transition: -moz-transform @transition; + -o-transition: -o-transform @transition; + transition: transform @transition; +} + +// Transformations +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); // IE9 only + transform: rotate(@degrees); +} +.scale(@ratio; @ratio-y...) { + -webkit-transform: scale(@ratio, @ratio-y); + -ms-transform: scale(@ratio, @ratio-y); // IE9 only + transform: scale(@ratio, @ratio-y); +} +.translate(@x; @y) { + -webkit-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); // IE9 only + transform: translate(@x, @y); +} +.skew(@x; @y) { + -webkit-transform: skew(@x, @y); + -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+ + transform: skew(@x, @y); +} +.translate3d(@x; @y; @z) { + -webkit-transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); +} + +.rotateX(@degrees) { + -webkit-transform: rotateX(@degrees); + -ms-transform: rotateX(@degrees); // IE9 only + transform: rotateX(@degrees); +} +.rotateY(@degrees) { + -webkit-transform: rotateY(@degrees); + -ms-transform: rotateY(@degrees); // IE9 only + transform: rotateY(@degrees); +} +.perspective(@perspective) { + -webkit-perspective: @perspective; + -moz-perspective: @perspective; + perspective: @perspective; +} +.perspective-origin(@perspective) { + -webkit-perspective-origin: @perspective; + -moz-perspective-origin: @perspective; + perspective-origin: @perspective; +} +.transform-origin(@origin) { + -webkit-transform-origin: @origin; + -moz-transform-origin: @origin; + -ms-transform-origin: @origin; // IE9 only + transform-origin: @origin; +} + +// Animations +.animation(@animation) { + -webkit-animation: @animation; + animation: @animation; +} +.animation-name(@name) { + -webkit-animation-name: @name; + animation-name: @name; +} +.animation-duration(@duration) { + -webkit-animation-duration: @duration; + animation-duration: @duration; +} +.animation-timing-function(@timing-function) { + -webkit-animation-timing-function: @timing-function; + animation-timing-function: @timing-function; +} +.animation-delay(@delay) { + -webkit-animation-delay: @delay; + animation-delay: @delay; +} +.animation-iteration-count(@iteration-count) { + -webkit-animation-iteration-count: @iteration-count; + animation-iteration-count: @iteration-count; +} +.animation-direction(@direction) { + -webkit-animation-direction: @direction; + animation-direction: @direction; +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden` +.backface-visibility(@visibility){ + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// User select +// For selecting text on the page +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; // IE10+ + user-select: @select; +} + +// Resize anything +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +.content-columns(@column-count; @column-gap: @grid-gutter-width) { + -webkit-column-count: @column-count; + -moz-column-count: @column-count; + column-count: @column-count; + -webkit-column-gap: @column-gap; + -moz-column-gap: @column-gap; + column-gap: @column-gap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; // IE10+ + -o-hyphens: @mode; + hyphens: @mode; +} + +// Opacity +.opacity(@opacity) { + opacity: @opacity; + // IE8 filter + @opacity-ie: (@opacity * 100); + filter: ~"alpha(opacity=@{opacity-ie})"; +} + + + +// GRADIENTS +// -------------------------------------------------- + +#gradient { + + // Horizontal gradient, from left to right + // + // Creates two color stops, start and end, by specifying a color and position for each color stop. + // Color stops are not available in IE9 and below. + .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) { + background-image: -webkit-linear-gradient(left, color-stop(@start-color @start-percent), color-stop(@end-color @end-percent)); // Safari 5.1-6, Chrome 10+ + background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down + } + + // Vertical gradient, from top to bottom + // + // Creates two color stops, start and end, by specifying a color and position for each color stop. + // Color stops are not available in IE9 and below. + .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) { + background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+ + background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down + } + + .directional(@start-color: #555; @end-color: #333; @deg: 45deg) { + background-repeat: repeat-x; + background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+ + background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + } + .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) { + background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); + background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback + } + .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) { + background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@inner-color: #555; @outer-color: #333) { + background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color); + background-image: radial-gradient(circle, @inner-color, @outer-color); + background-repeat: no-repeat; + } + .striped(@color: rgba(255,255,255,.15); @angle: 45deg) { + background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + } +} + +// Reset filters for IE +// +// When you need to remove a gradient background, do not forget to use this to reset +// the IE filter for IE9 and below. +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} + + + +// Retina images +// +// Short retina mixin for setting background-image and -size + +.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) { + background-image: url("@{file-1x}"); + + @media + only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and ( min--moz-device-pixel-ratio: 2), + only screen and ( -o-min-device-pixel-ratio: 2/1), + only screen and ( min-device-pixel-ratio: 2), + only screen and ( min-resolution: 192dpi), + only screen and ( min-resolution: 2dppx) { + background-image: url("@{file-2x}"); + background-size: @width-1x @height-1x; + } +} + + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. + +.img-responsive(@display: block) { + display: @display; + max-width: 100%; // Part 1: Set a maximum relative to the parent + height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching +} + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +.nav-divider(@color: #e5e5e5) { + height: 1px; + margin: ((@line-height-computed / 2) - 1) 0; + overflow: hidden; + background-color: @color; +} + +// Panels +// ------------------------- +.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) { + border-color: @border; + + & > .panel-heading { + color: @heading-text-color; + background-color: @heading-bg-color; + border-color: @heading-border; + + + .panel-collapse .panel-body { + border-top-color: @border; + } + } + & > .panel-footer { + + .panel-collapse .panel-body { + border-bottom-color: @border; + } + } +} + +// Alerts +// ------------------------- +.alert-variant(@background; @border; @text-color) { + background-color: @background; + border-color: @border; + color: @text-color; + + hr { + border-top-color: darken(@border, 5%); + } + .alert-link { + color: darken(@text-color, 10%); + } +} + +// Tables +// ------------------------- +.table-row-variant(@state; @background) { + // Exact selectors below required to override `.table-striped` and prevent + // inheritance to nested tables. + .table > thead > tr, + .table > tbody > tr, + .table > tfoot > tr { + > td.@{state}, + > th.@{state}, + &.@{state} > td, + &.@{state} > th { + background-color: @background; + } + } + + // Hover states for `.table-hover` + // Note: this is not available for cells or rows within `thead` or `tfoot`. + .table-hover > tbody > tr { + > td.@{state}:hover, + > th.@{state}:hover, + &.@{state}:hover > td, + &.@{state}:hover > th { + background-color: darken(@background, 5%); + } + } +} + +// List Groups +// ------------------------- +.list-group-item-variant(@state; @background; @color) { + .list-group-item-@{state} { + color: @color; + background-color: @background; + + a& { + color: @color; + + .list-group-item-heading { color: inherit; } + + &:hover, + &:focus { + color: @color; + background-color: darken(@background, 5%); + } + &.active, + &.active:hover, + &.active:focus { + color: #fff; + background-color: @color; + border-color: @color; + } + } + } +} + +// Button variants +// ------------------------- +// Easily pump out default styles, as well as :hover, :focus, :active, +// and disabled options for all buttons +.button-variant(@color; @background; @border) { + color: @color; + background-color: @background; + border-color: @border; + + &:hover, + &:focus, + &:active, + &.active, + .open .dropdown-toggle& { + color: @color; + background-color: darken(@background, 8%); + border-color: darken(@border, 12%); + } + &:active, + &.active, + .open .dropdown-toggle& { + background-image: none; + } + &.disabled, + &[disabled], + fieldset[disabled] & { + &, + &:hover, + &:focus, + &:active, + &.active { + background-color: @background; + border-color: @border; + } + } + + .badge { + color: @background; + background-color: @color; + } +} + +// Button sizes +// ------------------------- +.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + line-height: @line-height; + border-radius: @border-radius; +} + +// Pagination +// ------------------------- +.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) { + > li { + > a, + > span { + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + } + &:first-child { + > a, + > span { + .border-left-radius(@border-radius); + } + } + &:last-child { + > a, + > span { + .border-right-radius(@border-radius); + } + } + } +} + +// Labels +// ------------------------- +.label-variant(@color) { + background-color: @color; + &[href] { + &:hover, + &:focus { + background-color: darken(@color, 10%); + } + } +} + +// Contextual backgrounds +// ------------------------- +.bg-variant(@color) { + background-color: @color; + a&:hover { + background-color: darken(@color, 10%); + } +} + +// Typography +// ------------------------- +.text-emphasis-variant(@color) { + color: @color; + a&:hover { + color: darken(@color, 10%); + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. +.navbar-vertical-align(@element-height) { + margin-top: ((@navbar-height - @element-height) / 2); + margin-bottom: ((@navbar-height - @element-height) / 2); +} + +// Progress bars +// ------------------------- +.progress-bar-variant(@color) { + background-color: @color; + .progress-striped & { + #gradient > .striped(); + } +} + +// Responsive utilities +// ------------------------- +// More easily include all the states for responsive-utilities.less. +.responsive-visibility() { + display: block !important; + table& { display: table; } + tr& { display: table-row !important; } + th&, + td& { display: table-cell !important; } +} + +.responsive-invisibility() { + display: none !important; +} + + +// Grid System +// ----------- + +// Centered container element +.container-fixed() { + margin-right: auto; + margin-left: auto; + padding-left: (@grid-gutter-width / 2); + padding-right: (@grid-gutter-width / 2); + &:extend(.clearfix all); +} + +// Creates a wrapper for a series of columns +.make-row(@gutter: @grid-gutter-width) { + margin-left: (@gutter / -2); + margin-right: (@gutter / -2); + &:extend(.clearfix all); +} + +// Generate the extra small columns +.make-xs-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + float: left; + width: percentage((@columns / @grid-columns)); + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); +} +.make-xs-column-offset(@columns) { + @media (min-width: @screen-xs-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-xs-column-push(@columns) { + @media (min-width: @screen-xs-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-xs-column-pull(@columns) { + @media (min-width: @screen-xs-min) { + right: percentage((@columns / @grid-columns)); + } +} + + +// Generate the small columns +.make-sm-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-sm-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-offset(@columns) { + @media (min-width: @screen-sm-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-push(@columns) { + @media (min-width: @screen-sm-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-pull(@columns) { + @media (min-width: @screen-sm-min) { + right: percentage((@columns / @grid-columns)); + } +} + + +// Generate the medium columns +.make-md-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-md-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-md-column-offset(@columns) { + @media (min-width: @screen-md-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-md-column-push(@columns) { + @media (min-width: @screen-md-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-md-column-pull(@columns) { + @media (min-width: @screen-md-min) { + right: percentage((@columns / @grid-columns)); + } +} + + +// Generate the large columns +.make-lg-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-lg-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-offset(@columns) { + @media (min-width: @screen-lg-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-push(@columns) { + @media (min-width: @screen-lg-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-pull(@columns) { + @media (min-width: @screen-lg-min) { + right: percentage((@columns / @grid-columns)); + } +} + + +// Framework grid generation +// +// Used only by Bootstrap to generate the correct number of grid classes given +// any value of `@grid-columns`. + +.make-grid-columns() { + // Common styles for all sizes of grid columns, widths 1-12 + .col(@index) when (@index = 1) { // initial + @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; + .col((@index + 1), @item); + } + .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo + @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; + .col((@index + 1), ~"@{list}, @{item}"); + } + .col(@index, @list) when (@index > @grid-columns) { // terminal + @{list} { + position: relative; + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: (@grid-gutter-width / 2); + padding-right: (@grid-gutter-width / 2); + } + } + .col(1); // kickstart it +} + +.float-grid-columns(@class) { + .col(@index) when (@index = 1) { // initial + @item: ~".col-@{class}-@{index}"; + .col((@index + 1), @item); + } + .col(@index, @list) when (@index =< @grid-columns) { // general + @item: ~".col-@{class}-@{index}"; + .col((@index + 1), ~"@{list}, @{item}"); + } + .col(@index, @list) when (@index > @grid-columns) { // terminal + @{list} { + float: left; + } + } + .col(1); // kickstart it +} + +.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) { + .col-@{class}-@{index} { + width: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = push) { + .col-@{class}-push-@{index} { + left: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = pull) { + .col-@{class}-pull-@{index} { + right: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = offset) { + .col-@{class}-offset-@{index} { + margin-left: percentage((@index / @grid-columns)); + } +} + +// Basic looping in LESS +.loop-grid-columns(@index, @class, @type) when (@index >= 0) { + .calc-grid-column(@index, @class, @type); + // next iteration + .loop-grid-columns((@index - 1), @class, @type); +} + +// Create grid for specific class +.make-grid(@class) { + .float-grid-columns(@class); + .loop-grid-columns(@grid-columns, @class, width); + .loop-grid-columns(@grid-columns, @class, pull); + .loop-grid-columns(@grid-columns, @class, push); + .loop-grid-columns(@grid-columns, @class, offset); +} + +// Form validation states +// +// Used in forms.less to generate the form validation CSS for warnings, errors, +// and successes. + +.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) { + // Color the label and help text + .help-block, + .control-label, + .radio, + .checkbox, + .radio-inline, + .checkbox-inline { + color: @text-color; + } + // Set the border and box shadow on specific inputs to match + .form-control { + border-color: @border-color; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work + &:focus { + border-color: darken(@border-color, 10%); + @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%); + .box-shadow(@shadow); + } + } + // Set validation states also for addons + .input-group-addon { + color: @text-color; + border-color: @border-color; + background-color: @background-color; + } + // Optional feedback icon + .form-control-feedback { + color: @text-color; + } +} + +// Form control focus state +// +// Generate a customized focus state and for any input with the specified color, +// which defaults to the `@input-focus-border` variable. +// +// We highly encourage you to not customize the default value, but instead use +// this to tweak colors on an as-needed basis. This aesthetic change is based on +// WebKit's default styles, but applicable to a wider range of browsers. Its +// usability and accessibility should be taken into account with any change. +// +// Example usage: change the default blue border and shadow to white for better +// contrast against a dark gray background. + +.form-control-focus(@color: @input-border-focus) { + @color-rgba: rgba(red(@color), green(@color), blue(@color), .6); + &:focus { + border-color: @color; + outline: 0; + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}"); + } +} + +// Form control sizing +// +// Relative text size, padding, and border-radii changes for form controls. For +// horizontal sizing, wrap controls in the predefined grid classes. `` background color +@input-bg: #fff; +//** `` background color +@input-bg-disabled: @gray-lighter; + +//** Text color for ``s +@input-color: @gray; +//** `` border color +@input-border: #ccc; +//** `` border radius +@input-border-radius: @border-radius-base; +//** Border color for inputs on focus +@input-border-focus: #66afe9; + +//** Placeholder text color +@input-color-placeholder: @gray-light; + +//** Default `.form-control` height +@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2); +//** Large `.form-control` height +@input-height-large: (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2); +//** Small `.form-control` height +@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2); + +@legend-color: @gray-dark; +@legend-border-color: #e5e5e5; + +//** Background color for textual input addons +@input-group-addon-bg: @gray-lighter; +//** Border color for textual input addons +@input-group-addon-border-color: @input-border; + + +//== Dropdowns +// +//## Dropdown menu container and contents. + +//** Background for the dropdown menu. +@dropdown-bg: #fff; +//** Dropdown menu `border-color`. +@dropdown-border: rgba(0,0,0,.15); +//** Dropdown menu `border-color` **for IE8**. +@dropdown-fallback-border: #ccc; +//** Divider color for between dropdown items. +@dropdown-divider-bg: #e5e5e5; + +//** Dropdown link text color. +@dropdown-link-color: @gray-dark; +//** Hover color for dropdown links. +@dropdown-link-hover-color: darken(@gray-dark, 5%); +//** Hover background for dropdown links. +@dropdown-link-hover-bg: #f5f5f5; + +//** Active dropdown menu item text color. +@dropdown-link-active-color: @component-active-color; +//** Active dropdown menu item background color. +@dropdown-link-active-bg: @component-active-bg; + +//** Disabled dropdown menu item background color. +@dropdown-link-disabled-color: @gray-light; + +//** Text color for headers within dropdown menus. +@dropdown-header-color: @gray-light; + +// Note: Deprecated @dropdown-caret-color as of v3.1.0 +@dropdown-caret-color: #000; + + +//-- Z-index master list +// +// Warning: Avoid customizing these values. They're used for a bird's eye view +// of components dependent on the z-axis and are designed to all work together. +// +// Note: These variables are not generated into the Customizer. + +@zindex-navbar: 1000; +@zindex-dropdown: 1000; +@zindex-popover: 1010; +@zindex-tooltip: 1030; +@zindex-navbar-fixed: 1030; +@zindex-modal-background: 1040; +@zindex-modal: 1050; + + +//== Media queries breakpoints +// +//## Define the breakpoints at which your layout will change, adapting to different screen sizes. + +// Extra small screen / phone +// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1 +@screen-xs: 480px; +@screen-xs-min: @screen-xs; +@screen-phone: @screen-xs-min; + +// Small screen / tablet +// Note: Deprecated @screen-sm and @screen-tablet as of v3.0.1 +@screen-sm: 768px; +@screen-sm-min: @screen-sm; +@screen-tablet: @screen-sm-min; + +// Medium screen / desktop +// Note: Deprecated @screen-md and @screen-desktop as of v3.0.1 +@screen-md: 992px; +@screen-md-min: @screen-md; +@screen-desktop: @screen-md-min; + +// Large screen / wide desktop +// Note: Deprecated @screen-lg and @screen-lg-desktop as of v3.0.1 +@screen-lg: 1200px; +@screen-lg-min: @screen-lg; +@screen-lg-desktop: @screen-lg-min; + +// So media queries don't overlap when required, provide a maximum +@screen-xs-max: (@screen-sm-min - 1); +@screen-sm-max: (@screen-md-min - 1); +@screen-md-max: (@screen-lg-min - 1); + + +//== Grid system +// +//## Define your custom responsive grid. + +//** Number of columns in the grid. +@grid-columns: 12; +//** Padding between columns. Gets divided in half for the left and right. +@grid-gutter-width: 30px; +// Navbar collapse +//** Point at which the navbar becomes uncollapsed. +@grid-float-breakpoint: @screen-sm-min; +//** Point at which the navbar begins collapsing. +@grid-float-breakpoint-max: (@grid-float-breakpoint - 1); + + +//== Container sizes +// +//## Define the maximum width of `.container` for different screen sizes. + +// Small screen / tablet +@container-tablet: ((720px + @grid-gutter-width)); +//** For `@screen-sm-min` and up. +@container-sm: @container-tablet; + +// Medium screen / desktop +@container-desktop: ((940px + @grid-gutter-width)); +//** For `@screen-md-min` and up. +@container-md: @container-desktop; + +// Large screen / wide desktop +@container-large-desktop: ((1140px + @grid-gutter-width)); +//** For `@screen-lg-min` and up. +@container-lg: @container-large-desktop; + + +//== Navbar +// +//## + +// Basics of a navbar +@navbar-height: 50px; +@navbar-margin-bottom: @line-height-computed; +@navbar-border-radius: @border-radius-base; +@navbar-padding-horizontal: floor((@grid-gutter-width / 2)); +@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2); +@navbar-collapse-max-height: 340px; + +@navbar-default-color: #777; +@navbar-default-bg: #f8f8f8; +@navbar-default-border: darken(@navbar-default-bg, 6.5%); + +// Navbar links +@navbar-default-link-color: #777; +@navbar-default-link-hover-color: #333; +@navbar-default-link-hover-bg: transparent; +@navbar-default-link-active-color: #555; +@navbar-default-link-active-bg: darken(@navbar-default-bg, 6.5%); +@navbar-default-link-disabled-color: #ccc; +@navbar-default-link-disabled-bg: transparent; + +// Navbar brand label +@navbar-default-brand-color: @navbar-default-link-color; +@navbar-default-brand-hover-color: darken(@navbar-default-brand-color, 10%); +@navbar-default-brand-hover-bg: transparent; + +// Navbar toggle +@navbar-default-toggle-hover-bg: #ddd; +@navbar-default-toggle-icon-bar-bg: #888; +@navbar-default-toggle-border-color: #ddd; + + +// Inverted navbar +// Reset inverted navbar basics +@navbar-inverse-color: @gray-light; +@navbar-inverse-bg: #222; +@navbar-inverse-border: darken(@navbar-inverse-bg, 10%); + +// Inverted navbar links +@navbar-inverse-link-color: @gray-light; +@navbar-inverse-link-hover-color: #fff; +@navbar-inverse-link-hover-bg: transparent; +@navbar-inverse-link-active-color: @navbar-inverse-link-hover-color; +@navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%); +@navbar-inverse-link-disabled-color: #444; +@navbar-inverse-link-disabled-bg: transparent; + +// Inverted navbar brand label +@navbar-inverse-brand-color: @navbar-inverse-link-color; +@navbar-inverse-brand-hover-color: #fff; +@navbar-inverse-brand-hover-bg: transparent; + +// Inverted navbar toggle +@navbar-inverse-toggle-hover-bg: #333; +@navbar-inverse-toggle-icon-bar-bg: #fff; +@navbar-inverse-toggle-border-color: #333; + + +//== Navs +// +//## + +//=== Shared nav styles +@nav-link-padding: 10px 15px; +@nav-link-hover-bg: @gray-lighter; + +@nav-disabled-link-color: @gray-light; +@nav-disabled-link-hover-color: @gray-light; + +@nav-open-link-hover-color: #fff; + +//== Tabs +@nav-tabs-border-color: #ddd; + +@nav-tabs-link-hover-border-color: @gray-lighter; + +@nav-tabs-active-link-hover-bg: @body-bg; +@nav-tabs-active-link-hover-color: @gray; +@nav-tabs-active-link-hover-border-color: #ddd; + +@nav-tabs-justified-link-border-color: #ddd; +@nav-tabs-justified-active-link-border-color: @body-bg; + +//== Pills +@nav-pills-border-radius: @border-radius-base; +@nav-pills-active-link-hover-bg: @component-active-bg; +@nav-pills-active-link-hover-color: @component-active-color; + + +//== Pagination +// +//## + +@pagination-color: @link-color; +@pagination-bg: #fff; +@pagination-border: #ddd; + +@pagination-hover-color: @link-hover-color; +@pagination-hover-bg: @gray-lighter; +@pagination-hover-border: #ddd; + +@pagination-active-color: #fff; +@pagination-active-bg: @brand-primary; +@pagination-active-border: @brand-primary; + +@pagination-disabled-color: @gray-light; +@pagination-disabled-bg: #fff; +@pagination-disabled-border: #ddd; + + +//== Pager +// +//## + +@pager-bg: @pagination-bg; +@pager-border: @pagination-border; +@pager-border-radius: 15px; + +@pager-hover-bg: @pagination-hover-bg; + +@pager-active-bg: @pagination-active-bg; +@pager-active-color: @pagination-active-color; + +@pager-disabled-color: @pagination-disabled-color; + + +//== Jumbotron +// +//## + +@jumbotron-padding: 30px; +@jumbotron-color: inherit; +@jumbotron-bg: @gray-lighter; +@jumbotron-heading-color: inherit; +@jumbotron-font-size: ceil((@font-size-base * 1.5)); + + +//== Form states and alerts +// +//## Define colors for form feedback states and, by default, alerts. + +@state-success-text: #3c763d; +@state-success-bg: #dff0d8; +@state-success-border: darken(spin(@state-success-bg, -10), 5%); + +@state-info-text: #31708f; +@state-info-bg: #d9edf7; +@state-info-border: darken(spin(@state-info-bg, -10), 7%); + +@state-warning-text: #8a6d3b; +@state-warning-bg: #fcf8e3; +@state-warning-border: darken(spin(@state-warning-bg, -10), 5%); + +@state-danger-text: #a94442; +@state-danger-bg: #f2dede; +@state-danger-border: darken(spin(@state-danger-bg, -10), 5%); + + +//== Tooltips +// +//## + +//** Tooltip max width +@tooltip-max-width: 200px; +//** Tooltip text color +@tooltip-color: #fff; +//** Tooltip background color +@tooltip-bg: #000; +@tooltip-opacity: .9; + +//** Tooltip arrow width +@tooltip-arrow-width: 5px; +//** Tooltip arrow color +@tooltip-arrow-color: @tooltip-bg; + + +//== Popovers +// +//## + +//** Popover body background color +@popover-bg: #fff; +//** Popover maximum width +@popover-max-width: 276px; +//** Popover border color +@popover-border-color: rgba(0,0,0,.2); +//** Popover fallback border color +@popover-fallback-border-color: #ccc; + +//** Popover title background color +@popover-title-bg: darken(@popover-bg, 3%); + +//** Popover arrow width +@popover-arrow-width: 10px; +//** Popover arrow color +@popover-arrow-color: #fff; + +//** Popover outer arrow width +@popover-arrow-outer-width: (@popover-arrow-width + 1); +//** Popover outer arrow color +@popover-arrow-outer-color: fadein(@popover-border-color, 5%); +//** Popover outer arrow fallback color +@popover-arrow-outer-fallback-color: darken(@popover-fallback-border-color, 20%); + + +//== Labels +// +//## + +//** Default label background color +@label-default-bg: @gray-light; +//** Primary label background color +@label-primary-bg: @brand-primary; +//** Success label background color +@label-success-bg: @brand-success; +//** Info label background color +@label-info-bg: @brand-info; +//** Warning label background color +@label-warning-bg: @brand-warning; +//** Danger label background color +@label-danger-bg: @brand-danger; + +//** Default label text color +@label-color: #fff; +//** Default text color of a linked label +@label-link-hover-color: #fff; + + +//== Modals +// +//## + +//** Padding applied to the modal body +@modal-inner-padding: 20px; + +//** Padding applied to the modal title +@modal-title-padding: 15px; +//** Modal title line-height +@modal-title-line-height: @line-height-base; + +//** Background color of modal content area +@modal-content-bg: #fff; +//** Modal content border color +@modal-content-border-color: rgba(0,0,0,.2); +//** Modal content border color **for IE8** +@modal-content-fallback-border-color: #999; + +//** Modal backdrop background color +@modal-backdrop-bg: #000; +//** Modal backdrop opacity +@modal-backdrop-opacity: .5; +//** Modal header border color +@modal-header-border-color: #e5e5e5; +//** Modal footer border color +@modal-footer-border-color: @modal-header-border-color; + +@modal-lg: 900px; +@modal-md: 600px; +@modal-sm: 300px; + + +//== Alerts +// +//## Define alert colors, border radius, and padding. + +@alert-padding: 15px; +@alert-border-radius: @border-radius-base; +@alert-link-font-weight: bold; + +@alert-success-bg: @state-success-bg; +@alert-success-text: @state-success-text; +@alert-success-border: @state-success-border; + +@alert-info-bg: @state-info-bg; +@alert-info-text: @state-info-text; +@alert-info-border: @state-info-border; + +@alert-warning-bg: @state-warning-bg; +@alert-warning-text: @state-warning-text; +@alert-warning-border: @state-warning-border; + +@alert-danger-bg: @state-danger-bg; +@alert-danger-text: @state-danger-text; +@alert-danger-border: @state-danger-border; + + +//== Progress bars +// +//## + +//** Background color of the whole progress component +@progress-bg: #f5f5f5; +//** Progress bar text color +@progress-bar-color: #fff; + +//** Default progress bar color +@progress-bar-bg: @brand-primary; +//** Success progress bar color +@progress-bar-success-bg: @brand-success; +//** Warning progress bar color +@progress-bar-warning-bg: @brand-warning; +//** Danger progress bar color +@progress-bar-danger-bg: @brand-danger; +//** Info progress bar color +@progress-bar-info-bg: @brand-info; + + +//== List group +// +//## + +//** Background color on `.list-group-item` +@list-group-bg: #fff; +//** `.list-group-item` border color +@list-group-border: #ddd; +//** List group border radius +@list-group-border-radius: @border-radius-base; + +//** Background color of single list elements on hover +@list-group-hover-bg: #f5f5f5; +//** Text color of active list elements +@list-group-active-color: @component-active-color; +//** Background color of active list elements +@list-group-active-bg: @component-active-bg; +//** Border color of active list elements +@list-group-active-border: @list-group-active-bg; +@list-group-active-text-color: lighten(@list-group-active-bg, 40%); + +@list-group-link-color: #555; +@list-group-link-heading-color: #333; + + +//== Panels +// +//## + +@panel-bg: #fff; +@panel-body-padding: 15px; +@panel-border-radius: @border-radius-base; + +//** Border color for elements within panels +@panel-inner-border: #ddd; +@panel-footer-bg: #f5f5f5; + +@panel-default-text: @gray-dark; +@panel-default-border: #ddd; +@panel-default-heading-bg: #f5f5f5; + +@panel-primary-text: #fff; +@panel-primary-border: @brand-primary; +@panel-primary-heading-bg: @brand-primary; + +@panel-success-text: @state-success-text; +@panel-success-border: @state-success-border; +@panel-success-heading-bg: @state-success-bg; + +@panel-info-text: @state-info-text; +@panel-info-border: @state-info-border; +@panel-info-heading-bg: @state-info-bg; + +@panel-warning-text: @state-warning-text; +@panel-warning-border: @state-warning-border; +@panel-warning-heading-bg: @state-warning-bg; + +@panel-danger-text: @state-danger-text; +@panel-danger-border: @state-danger-border; +@panel-danger-heading-bg: @state-danger-bg; + + +//== Thumbnails +// +//## + +//** Padding around the thumbnail image +@thumbnail-padding: 4px; +//** Thumbnail background color +@thumbnail-bg: @body-bg; +//** Thumbnail border color +@thumbnail-border: #ddd; +//** Thumbnail border radius +@thumbnail-border-radius: @border-radius-base; + +//** Custom text color for thumbnail captions +@thumbnail-caption-color: @text-color; +//** Padding around the thumbnail caption +@thumbnail-caption-padding: 9px; + + +//== Wells +// +//## + +@well-bg: #f5f5f5; +@well-border: darken(@well-bg, 7%); + + +//== Badges +// +//## + +@badge-color: #fff; +//** Linked badge text color on hover +@badge-link-hover-color: #fff; +@badge-bg: @gray-light; + +//** Badge text color in active nav link +@badge-active-color: @link-color; +//** Badge background color in active nav link +@badge-active-bg: #fff; + +@badge-font-weight: bold; +@badge-line-height: 1; +@badge-border-radius: 10px; + + +//== Breadcrumbs +// +//## + +@breadcrumb-padding-vertical: 8px; +@breadcrumb-padding-horizontal: 15px; +//** Breadcrumb background color +@breadcrumb-bg: #f5f5f5; +//** Breadcrumb text color +@breadcrumb-color: #ccc; +//** Text color of current page in the breadcrumb +@breadcrumb-active-color: @gray-light; +//** Textual separator for between breadcrumb elements +@breadcrumb-separator: "/"; + + +//== Carousel +// +//## + +@carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6); + +@carousel-control-color: #fff; +@carousel-control-width: 15%; +@carousel-control-opacity: .5; +@carousel-control-font-size: 20px; + +@carousel-indicator-active-bg: #fff; +@carousel-indicator-border-color: #fff; + +@carousel-caption-color: #fff; + + +//== Close +// +//## + +@close-font-weight: bold; +@close-color: #000; +@close-text-shadow: 0 1px 0 #fff; + + +//== Code +// +//## + +@code-color: #c7254e; +@code-bg: #f9f2f4; + +@kbd-color: #fff; +@kbd-bg: #333; + +@pre-bg: #f5f5f5; +@pre-color: @gray-dark; +@pre-border-color: #ccc; +@pre-scrollable-max-height: 340px; + + +//== Type +// +//## + +//** Text muted color +@text-muted: @gray-light; +//** Abbreviations and acronyms border color +@abbr-border-color: @gray-light; +//** Headings small color +@headings-small-color: @gray-light; +//** Blockquote small color +@blockquote-small-color: @gray-light; +//** Blockquote font size +@blockquote-font-size: (@font-size-base * 1.25); +//** Blockquote border color +@blockquote-border-color: @gray-lighter; +//** Page header border color +@page-header-border-color: @gray-lighter; + + +//== Miscellaneous +// +//## + +//** Horizontal line color. +@hr-border: @gray-lighter; + +//** Horizontal offset for forms and lists. +@component-offset-horizontal: 180px; diff --git a/vendor/assets/components/d3/.bower.json b/vendor/assets/components/d3/.bower.json new file mode 100644 index 000000000..ba7873b0b --- /dev/null +++ b/vendor/assets/components/d3/.bower.json @@ -0,0 +1,36 @@ +{ + "name": "d3", + "main": "d3.js", + "scripts": [ + "d3.js" + ], + "ignore": [ + ".DS_Store", + ".git", + ".gitignore", + ".npmignore", + ".spmignore", + ".travis.yml", + "Makefile", + "bin", + "component.json", + "composer.json", + "index.js", + "lib", + "node_modules", + "package.json", + "src", + "test" + ], + "homepage": "https://github.com/mbostock/d3", + "version": "3.5.9", + "_release": "3.5.9", + "_resolution": { + "type": "version", + "tag": "v3.5.9", + "commit": "9951df10b4c72a25e9e8796100a02d9ecab47a0e" + }, + "_source": "git://github.com/mbostock/d3.git", + "_target": "^3.4.4", + "_originalSource": "d3" +} \ No newline at end of file diff --git a/vendor/assets/components/d3/.gitattributes b/vendor/assets/components/d3/.gitattributes new file mode 100644 index 000000000..aa9cd050d --- /dev/null +++ b/vendor/assets/components/d3/.gitattributes @@ -0,0 +1,5 @@ +bower.json -diff merge=ours +component.json -diff merge=ours +d3.js -diff merge=ours +d3.min.js -diff merge=ours +package.js -diff merge=ours diff --git a/vendor/assets/components/d3/CONTRIBUTING.md b/vendor/assets/components/d3/CONTRIBUTING.md new file mode 100644 index 000000000..5ced343e0 --- /dev/null +++ b/vendor/assets/components/d3/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing + +**Important:** these GitHub issues are for *bug reports and feature requests only*. Please use [StackOverflow](http://stackoverflow.com/questions/tagged/d3.js) or the [d3-js Google group](https://groups.google.com/d/forum/d3-js) for general help. + +If you’re looking for ways to contribute, please [peruse open issues](https://github.com/mbostock/d3/issues?milestone=&page=1&state=open). The icebox is a good place to find ideas that are not currently in development. If you already have an idea, please check past issues to see whether your idea or a similar one was previously discussed. + +Before submitting a pull request, consider implementing a live example first, say using [bl.ocks.org](http://bl.ocks.org). Real-world use cases go a long way to demonstrating the usefulness of a proposed feature. The more complex a feature’s implementation, the more usefulness it should provide. Share your demo using the #d3js tag on Twitter or by sending it to the [d3-js Google group](https://groups.google.com/d/forum/d3-js). + +If your proposed feature does not involve changing core functionality, consider submitting it instead as a [D3 plugin](https://github.com/d3/d3-plugins). New core features should be for general use, whereas plugins are suitable for more specialized use cases. When in doubt, it’s easier to start with a plugin before “graduating” to core. + +To contribute new documentation or add examples to the gallery, just [edit the Wiki](https://github.com/mbostock/d3/wiki)! + +## How to Submit a Pull Request + +1. Click the “Fork” button to create your personal fork of the D3 repository. + +2. After cloning your fork of the D3 repository in the terminal, run `npm install` to install D3’s dependencies. + +3. Create a new branch for your new feature. For example: `git checkout -b my-awesome-feature`. A dedicated branch for your pull request means you can develop multiple features at the same time, and ensures that your pull request is stable even if you later decide to develop an unrelated feature. + +4. The `d3.js` and `d3.min.js` files are built from source files in the `src` directory. _Do not edit `d3.js` directly._ Instead, edit the source files, and then run `make` to build the generated files. + +5. Use `make test` to run tests and verify your changes. If you are adding a new feature, you should add new tests! If you are changing existing functionality, make sure the existing tests run, or update them as appropriate. + +6. Sign D3’s [Individual Contributor License Agreement](https://docs.google.com/forms/d/1CzjdBKtDuA8WeuFJinadx956xLQ4Xriv7-oDvXnZMaI/viewform). Unless you are submitting a trivial patch (such as fixing a typo), this form is needed to verify that you are able to contribute. + +7. Submit your pull request, and good luck! diff --git a/vendor/assets/components/d3/LICENSE b/vendor/assets/components/d3/LICENSE new file mode 100644 index 000000000..f27c7e670 --- /dev/null +++ b/vendor/assets/components/d3/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2010-2015, Michael Bostock +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* The name Michael Bostock may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/assets/components/d3/README.md b/vendor/assets/components/d3/README.md new file mode 100644 index 000000000..80647af7f --- /dev/null +++ b/vendor/assets/components/d3/README.md @@ -0,0 +1,9 @@ +# Data-Driven Documents + + + +**D3.js** is a JavaScript library for manipulating documents based on data. **D3** helps you bring data to life using HTML, SVG, and CSS. **D3** emphasizes web standards and combines powerful visualization components with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers without tying yourself to a proprietary framework. + +Want to learn more? [See the wiki.](https://github.com/mbostock/d3/wiki) + +For examples, [see the gallery](https://github.com/mbostock/d3/wiki/Gallery) and [mbostock’s bl.ocks](http://bl.ocks.org/mbostock). diff --git a/vendor/assets/components/d3/bower.json b/vendor/assets/components/d3/bower.json new file mode 100644 index 000000000..53080bf43 --- /dev/null +++ b/vendor/assets/components/d3/bower.json @@ -0,0 +1,25 @@ +{ + "name": "d3", + "main": "d3.js", + "scripts": [ + "d3.js" + ], + "ignore": [ + ".DS_Store", + ".git", + ".gitignore", + ".npmignore", + ".spmignore", + ".travis.yml", + "Makefile", + "bin", + "component.json", + "composer.json", + "index.js", + "lib", + "node_modules", + "package.json", + "src", + "test" + ] +} diff --git a/vendor/assets/components/d3/d3.js b/vendor/assets/components/d3/d3.js new file mode 100644 index 000000000..8873e0a84 --- /dev/null +++ b/vendor/assets/components/d3/d3.js @@ -0,0 +1,9550 @@ +!function() { + var d3 = { + version: "3.5.9" + }; + var d3_arraySlice = [].slice, d3_array = function(list) { + return d3_arraySlice.call(list); + }; + var d3_document = this.document; + function d3_documentElement(node) { + return node && (node.ownerDocument || node.document || node).documentElement; + } + function d3_window(node) { + return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView); + } + if (d3_document) { + try { + d3_array(d3_document.documentElement.childNodes)[0].nodeType; + } catch (e) { + d3_array = function(list) { + var i = list.length, array = new Array(i); + while (i--) array[i] = list[i]; + return array; + }; + } + } + if (!Date.now) Date.now = function() { + return +new Date(); + }; + if (d3_document) { + try { + d3_document.createElement("DIV").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_element_prototype.setAttribute = function(name, value) { + d3_element_setAttribute.call(this, name, value + ""); + }; + d3_element_prototype.setAttributeNS = function(space, local, value) { + d3_element_setAttributeNS.call(this, space, local, value + ""); + }; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; + } + } + d3.ascending = d3_ascending; + function d3_ascending(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + } + d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + d3.min = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; + } + return a; + }; + d3.max = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && b > a) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; + } + return a; + }; + d3.extent = function(array, f) { + var i = -1, n = array.length, a, b, c; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } + return [ a, c ]; + }; + function d3_number(x) { + return x === null ? NaN : +x; + } + function d3_numeric(x) { + return !isNaN(x); + } + d3.sum = function(array, f) { + var s = 0, n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = +array[i])) s += a; + } else { + while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a; + } + return s; + }; + d3.mean = function(array, f) { + var s = 0, n = array.length, a, i = -1, j = n; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j; + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j; + } + if (j) return s / j; + }; + d3.quantile = function(values, p) { + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; + return e ? v + e * (values[h] - v) : v; + }; + d3.median = function(array, f) { + var numbers = [], n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a); + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a); + } + if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5); + }; + d3.variance = function(array, f) { + var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) { + if (d3_numeric(a = d3_number(array[i]))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } else { + while (++i < n) { + if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } + if (j > 1) return s / (j - 1); + }; + d3.deviation = function() { + var v = d3.variance.apply(this, arguments); + return v ? Math.sqrt(v) : v; + }; + function d3_bisector(compare) { + return { + left: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1; + } + return lo; + } + }; + } + var d3_bisect = d3_bisector(d3_ascending); + d3.bisectLeft = d3_bisect.left; + d3.bisect = d3.bisectRight = d3_bisect.right; + d3.bisector = function(f) { + return d3_bisector(f.length === 1 ? function(d, x) { + return d3_ascending(f(d), x); + } : f); + }; + d3.shuffle = function(array, i0, i1) { + if ((m = arguments.length) < 3) { + i1 = array.length; + if (m < 2) i0 = 0; + } + var m = i1 - i0, t, i; + while (m) { + i = Math.random() * m-- | 0; + t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t; + } + return array; + }; + d3.permute = function(array, indexes) { + var i = indexes.length, permutes = new Array(i); + while (i--) permutes[i] = array[indexes[i]]; + return permutes; + }; + d3.pairs = function(array) { + var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n); + while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ]; + return pairs; + }; + d3.zip = function() { + if (!(n = arguments.length)) return []; + for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) { + for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) { + zip[j] = arguments[j][i]; + } + } + return zips; + }; + function d3_zipLength(d) { + return d.length; + } + d3.transpose = function(matrix) { + return d3.zip.apply(d3, matrix); + }; + d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; + }; + d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; + }; + d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({ + key: key, + value: map[key] + }); + return entries; + }; + d3.merge = function(arrays) { + var n = arrays.length, m, i = -1, j = 0, merged, array; + while (++i < n) j += arrays[i].length; + merged = new Array(j); + while (--n >= 0) { + array = arrays[n]; + m = array.length; + while (--m >= 0) { + merged[--j] = array[m]; + } + } + return merged; + }; + var abs = Math.abs; + d3.range = function(start, stop, step) { + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; + } + } + if ((stop - start) / step === Infinity) throw new Error("infinite range"); + var range = [], k = d3_range_integerScale(abs(step)), i = -1, j; + start *= k, stop *= k, step *= k; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); + return range; + }; + function d3_range_integerScale(x) { + var k = 1; + while (x * k % 1) k *= 10; + return k; + } + function d3_class(ctor, properties) { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); + } + } + d3.map = function(object, f) { + var map = new d3_Map(); + if (object instanceof d3_Map) { + object.forEach(function(key, value) { + map.set(key, value); + }); + } else if (Array.isArray(object)) { + var i = -1, n = object.length, o; + if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o); + } else { + for (var key in object) map.set(key, object[key]); + } + return map; + }; + function d3_Map() { + this._ = Object.create(null); + } + var d3_map_proto = "__proto__", d3_map_zero = "\x00"; + d3_class(d3_Map, { + has: d3_map_has, + get: function(key) { + return this._[d3_map_escape(key)]; + }, + set: function(key, value) { + return this._[d3_map_escape(key)] = value; + }, + remove: d3_map_remove, + keys: d3_map_keys, + values: function() { + var values = []; + for (var key in this._) values.push(this._[key]); + return values; + }, + entries: function() { + var entries = []; + for (var key in this._) entries.push({ + key: d3_map_unescape(key), + value: this._[key] + }); + return entries; + }, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]); + } + }); + function d3_map_escape(key) { + return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key; + } + function d3_map_unescape(key) { + return (key += "")[0] === d3_map_zero ? key.slice(1) : key; + } + function d3_map_has(key) { + return d3_map_escape(key) in this._; + } + function d3_map_remove(key) { + return (key = d3_map_escape(key)) in this._ && delete this._[key]; + } + function d3_map_keys() { + var keys = []; + for (var key in this._) keys.push(d3_map_unescape(key)); + return keys; + } + function d3_map_size() { + var size = 0; + for (var key in this._) ++size; + return size; + } + function d3_map_empty() { + for (var key in this._) return false; + return true; + } + d3.nest = function() { + var nest = {}, keys = [], sortKeys = [], sortValues, rollup; + function map(mapType, array, depth) { + if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; + var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values; + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); + } else { + valuesByKey.set(keyValue, [ object ]); + } + } + if (mapType) { + object = mapType(); + setter = function(keyValue, values) { + object.set(keyValue, map(mapType, values, depth)); + }; + } else { + object = {}; + setter = function(keyValue, values) { + object[keyValue] = map(mapType, values, depth); + }; + } + valuesByKey.forEach(setter); + return object; + } + function entries(map, depth) { + if (depth >= keys.length) return map; + var array = [], sortKey = sortKeys[depth++]; + map.forEach(function(key, keyMap) { + array.push({ + key: key, + values: entries(keyMap, depth) + }); + }); + return sortKey ? array.sort(function(a, b) { + return sortKey(a.key, b.key); + }) : array; + } + nest.map = function(array, mapType) { + return map(mapType, array, 0); + }; + nest.entries = function(array) { + return entries(map(d3.map, array, 0), 0); + }; + nest.key = function(d) { + keys.push(d); + return nest; + }; + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + nest.rollup = function(f) { + rollup = f; + return nest; + }; + return nest; + }; + d3.set = function(array) { + var set = new d3_Set(); + if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]); + return set; + }; + function d3_Set() { + this._ = Object.create(null); + } + d3_class(d3_Set, { + has: d3_map_has, + add: function(key) { + this._[d3_map_escape(key += "")] = true; + return key; + }, + remove: d3_map_remove, + values: d3_map_keys, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key)); + } + }); + d3.behavior = {}; + function d3_identity(d) { + return d; + } + d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; + }; + function d3_rebind(target, source, method) { + return function() { + var value = method.apply(source, arguments); + return value === source ? target : value; + }; + } + function d3_vendorSymbol(object, name) { + if (name in object) return name; + name = name.charAt(0).toUpperCase() + name.slice(1); + for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) { + var prefixName = d3_vendorPrefixes[i] + name; + if (prefixName in object) return prefixName; + } + } + var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ]; + function d3_noop() {} + d3.dispatch = function() { + var dispatch = new d3_dispatch(), i = -1, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + return dispatch; + }; + function d3_dispatch() {} + d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), name = ""; + if (i >= 0) { + name = type.slice(i + 1); + type = type.slice(0, i); + } + if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); + if (arguments.length === 2) { + if (listener == null) for (type in this) { + if (this.hasOwnProperty(type)) this[type].on(name, null); + } + return this; + } + }; + function d3_dispatch_event(dispatch) { + var listeners = [], listenerByName = new d3_Map(); + function event() { + var z = listeners, i = -1, n = z.length, l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + return dispatch; + } + event.on = function(name, listener) { + var l = listenerByName.get(name), i; + if (arguments.length < 2) return l && l.on; + if (l) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + listenerByName.remove(name); + } + if (listener) listeners.push(listenerByName.set(name, { + on: listener + })); + return dispatch; + }; + return event; + } + d3.event = null; + function d3_eventPreventDefault() { + d3.event.preventDefault(); + } + function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; + } + function d3_eventDispatch(target) { + var dispatch = new d3_dispatch(), i = 0, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + return dispatch; + } + d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); + }; + var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + var d3_subclass = {}.__proto__ ? function(object, prototype) { + object.__proto__ = prototype; + } : function(object, prototype) { + for (var property in prototype) object[property] = prototype[property]; + }; + function d3_selection(groups) { + d3_subclass(groups, d3_selectionPrototype); + return groups; + } + var d3_select = function(s, n) { + return n.querySelector(s); + }, d3_selectAll = function(s, n) { + return n.querySelectorAll(s); + }, d3_selectMatches = function(n, s) { + var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")]; + d3_selectMatches = function(n, s) { + return d3_selectMatcher.call(n, s); + }; + return d3_selectMatches(n, s); + }; + if (typeof Sizzle === "function") { + d3_select = function(s, n) { + return Sizzle(s, n)[0] || null; + }; + d3_selectAll = Sizzle; + d3_selectMatches = Sizzle.matchesSelector; + } + d3.selection = function() { + return d3.select(d3_document.documentElement); + }; + var d3_selectionPrototype = d3.selection.prototype = []; + d3_selectionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, group, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i, j)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selector(selector) { + return typeof selector === "function" ? selector : function() { + return d3_select(selector, this); + }; + } + d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, node; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j))); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selectorAll(selector) { + return typeof selector === "function" ? selector : function() { + return d3_selectAll(selector, this); + }; + } + var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: "http://www.w3.org/1999/xhtml", + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + d3.ns = { + prefix: d3_nsPrefix, + qualify: function(name) { + var i = name.indexOf(":"), prefix = name; + if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); + return d3_nsPrefix.hasOwnProperty(prefix) ? { + space: d3_nsPrefix[prefix], + local: name + } : name; + } + }; + d3_selectionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(); + name = d3.ns.qualify(name); + return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); + } + for (value in name) this.each(d3_selection_attr(value, name[value])); + return this; + } + return this.each(d3_selection_attr(name, value)); + }; + function d3_selection_attr(name, value) { + name = d3.ns.qualify(name); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrConstant() { + this.setAttribute(name, value); + } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); + } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); + } + return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; + } + function d3_collapse(s) { + return s.trim().replace(/\s+/g, " "); + } + d3_selectionPrototype.classed = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1; + if (value = node.classList) { + while (++i < n) if (!value.contains(name[i])) return false; + } else { + value = node.getAttribute("class"); + while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; + } + return true; + } + for (value in name) this.each(d3_selection_classed(value, name[value])); + return this; + } + return this.each(d3_selection_classed(name, value)); + }; + function d3_selection_classedRe(name) { + return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); + } + function d3_selection_classes(name) { + return (name + "").trim().split(/^|\s+/); + } + function d3_selection_classed(name, value) { + name = d3_selection_classes(name).map(d3_selection_classedName); + var n = name.length; + function classedConstant() { + var i = -1; + while (++i < n) name[i](this, value); + } + function classedFunction() { + var i = -1, x = value.apply(this, arguments); + while (++i < n) name[i](this, x); + } + return typeof value === "function" ? classedFunction : classedConstant; + } + function d3_selection_classedName(name) { + var re = d3_selection_classedRe(name); + return function(node, value) { + if (c = node.classList) return value ? c.add(name) : c.remove(name); + var c = node.getAttribute("class") || ""; + if (value) { + re.lastIndex = 0; + if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name)); + } else { + node.setAttribute("class", d3_collapse(c.replace(re, " "))); + } + }; + } + d3_selectionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); + return this; + } + if (n < 2) { + var node = this.node(); + return d3_window(node).getComputedStyle(node, null).getPropertyValue(name); + } + priority = ""; + } + return this.each(d3_selection_style(name, value, priority)); + }; + function d3_selection_style(name, value, priority) { + function styleNull() { + this.style.removeProperty(name); + } + function styleConstant() { + this.style.setProperty(name, value, priority); + } + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); + } + return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; + } + d3_selectionPrototype.property = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") return this.node()[name]; + for (value in name) this.each(d3_selection_property(value, name[value])); + return this; + } + return this.each(d3_selection_property(name, value)); + }; + function d3_selection_property(name, value) { + function propertyNull() { + delete this[name]; + } + function propertyConstant() { + this[name] = value; + } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; else this[name] = x; + } + return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; + } + d3_selectionPrototype.text = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + } : value == null ? function() { + this.textContent = ""; + } : function() { + this.textContent = value; + }) : this.node().textContent; + }; + d3_selectionPrototype.html = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + } : value == null ? function() { + this.innerHTML = ""; + } : function() { + this.innerHTML = value; + }) : this.node().innerHTML; + }; + d3_selectionPrototype.append = function(name) { + name = d3_selection_creator(name); + return this.select(function() { + return this.appendChild(name.apply(this, arguments)); + }); + }; + function d3_selection_creator(name) { + function create() { + var document = this.ownerDocument, namespace = this.namespaceURI; + return namespace ? document.createElementNS(namespace, name) : document.createElement(name); + } + function createNS() { + return this.ownerDocument.createElementNS(name.space, name.local); + } + return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create; + } + d3_selectionPrototype.insert = function(name, before) { + name = d3_selection_creator(name); + before = d3_selection_selector(before); + return this.select(function() { + return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null); + }); + }; + d3_selectionPrototype.remove = function() { + return this.each(d3_selectionRemove); + }; + function d3_selectionRemove() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + } + d3_selectionPrototype.data = function(value, key) { + var i = -1, n = this.length, group, node; + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } + function bind(group, groupData) { + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; + if (key) { + var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue; + for (i = -1; ++i < n; ) { + if (node = group[i]) { + if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) { + exitNodes[i] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + keyValues[i] = keyValue; + } + } + for (i = -1; ++i < m; ) { + if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) { + enterNodes[i] = d3_selection_dataNode(nodeData); + } else if (node !== true) { + updateNodes[i] = node; + node.__data__ = nodeData; + } + nodeByKeyValue.set(keyValue, true); + } + for (i = -1; ++i < n; ) { + if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0; ) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + } + } + for (;i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + } + for (;i < n; ++i) { + exitNodes[i] = group[i]; + } + } + enterNodes.update = updateNodes; + enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); + if (typeof value === "function") { + while (++i < n) { + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); + } + } else { + while (++i < n) { + bind(group = this[i], value); + } + } + update.enter = function() { + return enter; + }; + update.exit = function() { + return exit; + }; + return update; + }; + function d3_selection_dataNode(data) { + return { + __data__: data + }; + } + d3_selectionPrototype.datum = function(value) { + return arguments.length ? this.property("__data__", value) : this.property("__data__"); + }; + d3_selectionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); + }; + } + d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; + }; + d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); + return this.order(); + }; + function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3_ascending; + return function(a, b) { + return a && b ? comparator(a.__data__, b.__data__) : !a - !b; + }; + } + d3_selectionPrototype.each = function(callback) { + return d3_selection_each(this, function(node, i, j) { + callback.call(node, node.__data__, i, j); + }); + }; + function d3_selection_each(groups, callback) { + for (var j = 0, m = groups.length; j < m; j++) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { + if (node = group[i]) callback(node, i, j); + } + } + return groups; + } + d3_selectionPrototype.call = function(callback) { + var args = d3_array(arguments); + callback.apply(args[0] = this, args); + return this; + }; + d3_selectionPrototype.empty = function() { + return !this.node(); + }; + d3_selectionPrototype.node = function() { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; + } + } + return null; + }; + d3_selectionPrototype.size = function() { + var n = 0; + d3_selection_each(this, function() { + ++n; + }); + return n; + }; + function d3_selection_enter(selection) { + d3_subclass(selection, d3_selection_enterPrototype); + return selection; + } + var d3_selection_enterPrototype = []; + d3.selection.enter = d3_selection_enter; + d3.selection.enter.prototype = d3_selection_enterPrototype; + d3_selection_enterPrototype.append = d3_selectionPrototype.append; + d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; + d3_selection_enterPrototype.node = d3_selectionPrototype.node; + d3_selection_enterPrototype.call = d3_selectionPrototype.call; + d3_selection_enterPrototype.size = d3_selectionPrototype.size; + d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, upgroup, group, node; + for (var j = -1, m = this.length; ++j < m; ) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + d3_selection_enterPrototype.insert = function(name, before) { + if (arguments.length < 2) before = d3_selection_enterInsertBefore(this); + return d3_selectionPrototype.insert.call(this, name, before); + }; + function d3_selection_enterInsertBefore(enter) { + var i0, j0; + return function(d, i, j) { + var group = enter[j].update, n = group.length, node; + if (j != j0) j0 = j, i0 = 0; + if (i >= i0) i0 = i + 1; + while (!(node = group[i0]) && ++i0 < n) ; + return node; + }; + } + d3.select = function(node) { + var group; + if (typeof node === "string") { + group = [ d3_select(node, d3_document) ]; + group.parentNode = d3_document.documentElement; + } else { + group = [ node ]; + group.parentNode = d3_documentElement(node); + } + return d3_selection([ group ]); + }; + d3.selectAll = function(nodes) { + var group; + if (typeof nodes === "string") { + group = d3_array(d3_selectAll(nodes, d3_document)); + group.parentNode = d3_document.documentElement; + } else { + group = d3_array(nodes); + group.parentNode = null; + } + return d3_selection([ group ]); + }; + d3_selectionPrototype.on = function(type, listener, capture) { + var n = arguments.length; + if (n < 3) { + if (typeof type !== "string") { + if (n < 2) listener = false; + for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); + return this; + } + if (n < 2) return (n = this.node()["__on" + type]) && n._; + capture = false; + } + return this.each(d3_selection_on(type, listener, capture)); + }; + function d3_selection_on(type, listener, capture) { + var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener; + if (i > 0) type = type.slice(0, i); + var filter = d3_selection_onFilters.get(type); + if (filter) type = filter, wrap = d3_selection_onFilter; + function onRemove() { + var l = this[name]; + if (l) { + this.removeEventListener(type, l, l.$); + delete this[name]; + } + } + function onAdd() { + var l = wrap(listener, d3_array(arguments)); + onRemove.call(this); + this.addEventListener(type, this[name] = l, l.$ = capture); + l._ = listener; + } + function removeAll() { + var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match; + for (var name in this) { + if (match = name.match(re)) { + var l = this[name]; + this.removeEventListener(match[1], l, l.$); + delete this[name]; + } + } + } + return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll; + } + var d3_selection_onFilters = d3.map({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }); + if (d3_document) { + d3_selection_onFilters.forEach(function(k) { + if ("on" + k in d3_document) d3_selection_onFilters.remove(k); + }); + } + function d3_selection_onListener(listener, argumentz) { + return function(e) { + var o = d3.event; + d3.event = e; + argumentz[0] = this.__data__; + try { + listener.apply(this, argumentz); + } finally { + d3.event = o; + } + }; + } + function d3_selection_onFilter(listener, argumentz) { + var l = d3_selection_onListener(listener, argumentz); + return function(e) { + var target = this, related = e.relatedTarget; + if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) { + l.call(target, e); + } + }; + } + var d3_event_dragSelect, d3_event_dragId = 0; + function d3_event_dragSuppress(node) { + var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault); + if (d3_event_dragSelect == null) { + d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect"); + } + if (d3_event_dragSelect) { + var style = d3_documentElement(node).style, select = style[d3_event_dragSelect]; + style[d3_event_dragSelect] = "none"; + } + return function(suppressClick) { + w.on(name, null); + if (d3_event_dragSelect) style[d3_event_dragSelect] = select; + if (suppressClick) { + var off = function() { + w.on(click, null); + }; + w.on(click, function() { + d3_eventPreventDefault(); + off(); + }, true); + setTimeout(off, 0); + } + }; + } + d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); + }; + var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0; + function d3_mousePoint(container, e) { + if (e.changedTouches) e = e.changedTouches[0]; + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + if (d3_mouse_bug44083 < 0) { + var window = d3_window(container); + if (window.scrollX || window.scrollY) { + svg = d3.select("body").append("svg").style({ + position: "absolute", + top: 0, + left: 0, + margin: 0, + padding: 0, + border: "none" + }, "important"); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + } + if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, + point.y = e.clientY; + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [ point.x, point.y ]; + } + var rect = container.getBoundingClientRect(); + return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; + } + d3.touch = function(container, touches, identifier) { + if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches; + if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) { + if ((touch = touches[i]).identifier === identifier) { + return d3_mousePoint(container, touch); + } + } + }; + d3.behavior.drag = function() { + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend"); + function drag() { + this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart); + } + function dragstart(id, position, subject, move, end) { + return function() { + var that = this, target = d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId); + if (origin) { + dragOffset = origin.apply(that, arguments); + dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ]; + } else { + dragOffset = [ 0, 0 ]; + } + dispatch({ + type: "dragstart" + }); + function moved() { + var position1 = position(parent, dragId), dx, dy; + if (!position1) return; + dx = position1[0] - position0[0]; + dy = position1[1] - position0[1]; + dragged |= dx | dy; + position0 = position1; + dispatch({ + type: "drag", + x: position1[0] + dragOffset[0], + y: position1[1] + dragOffset[1], + dx: dx, + dy: dy + }); + } + function ended() { + if (!position(parent, dragId)) return; + dragSubject.on(move + dragName, null).on(end + dragName, null); + dragRestore(dragged); + dispatch({ + type: "dragend" + }); + } + }; + } + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + return d3.rebind(drag, event, "on"); + }; + function d3_behavior_dragTouchId() { + return d3.event.changedTouches[0].identifier; + } + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π; + function d3_sgn(x) { + return x > 0 ? 1 : x < 0 ? -1 : 0; + } + function d3_cross2d(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); + } + function d3_acos(x) { + return x > 1 ? 0 : x < -1 ? π : Math.acos(x); + } + function d3_asin(x) { + return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x); + } + function d3_sinh(x) { + return ((x = Math.exp(x)) - 1 / x) / 2; + } + function d3_cosh(x) { + return ((x = Math.exp(x)) + 1 / x) / 2; + } + function d3_tanh(x) { + return ((x = Math.exp(2 * x)) - 1) / (x + 1); + } + function d3_haversin(x) { + return (x = Math.sin(x / 2)) * x; + } + var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4; + d3.interpolateZoom = function(p0, p1) { + var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2], dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, i, S; + if (d2 < ε2) { + S = Math.log(w1 / w0) / ρ; + i = function(t) { + return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * t * S) ]; + }; + } else { + var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1); + S = (r1 - r0) / ρ; + i = function(t) { + var s = t * S, coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0)); + return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ]; + }; + } + i.duration = S * 1e3; + return i; + }; + d3.behavior.zoom = function() { + var view = { + x: 0, + y: 0, + k: 1 + }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1; + if (!d3_behavior_zoomWheel) { + d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); + } + function zoom(g) { + g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted); + } + zoom.event = function(g) { + g.each(function() { + var dispatch = event.of(this, arguments), view1 = view; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.zoom", function() { + view = this.__chart__ || { + x: 0, + y: 0, + k: 1 + }; + zoomstarted(dispatch); + }).tween("zoom:zoom", function() { + var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]); + return function(t) { + var l = i(t), k = dx / l[2]; + this.__chart__ = view = { + x: cx - l[0] * k, + y: cy - l[1] * k, + k: k + }; + zoomed(dispatch); + }; + }).each("interrupt.zoom", function() { + zoomended(dispatch); + }).each("end.zoom", function() { + zoomended(dispatch); + }); + } else { + this.__chart__ = view; + zoomstarted(dispatch); + zoomed(dispatch); + zoomended(dispatch); + } + }); + }; + zoom.translate = function(_) { + if (!arguments.length) return [ view.x, view.y ]; + view = { + x: +_[0], + y: +_[1], + k: view.k + }; + rescale(); + return zoom; + }; + zoom.scale = function(_) { + if (!arguments.length) return view.k; + view = { + x: view.x, + y: view.y, + k: null + }; + scaleTo(+_); + rescale(); + return zoom; + }; + zoom.scaleExtent = function(_) { + if (!arguments.length) return scaleExtent; + scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ]; + return zoom; + }; + zoom.center = function(_) { + if (!arguments.length) return center; + center = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.size = function(_) { + if (!arguments.length) return size; + size = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.duration = function(_) { + if (!arguments.length) return duration; + duration = +_; + return zoom; + }; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + function location(p) { + return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ]; + } + function point(l) { + return [ l[0] * view.k + view.x, l[1] * view.k + view.y ]; + } + function scaleTo(s) { + view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } + function translateTo(p, l) { + l = point(l); + view.x += p[0] - l[0]; + view.y += p[1] - l[1]; + } + function zoomTo(that, p, l, k) { + that.__chart__ = { + x: view.x, + y: view.y, + k: view.k + }; + scaleTo(Math.pow(2, k)); + translateTo(center0 = p, l); + that = d3.select(that); + if (duration > 0) that = that.transition().duration(duration); + that.call(zoom.event); + } + function rescale() { + if (x1) x1.domain(x0.range().map(function(x) { + return (x - view.x) / view.k; + }).map(x0.invert)); + if (y1) y1.domain(y0.range().map(function(y) { + return (y - view.y) / view.k; + }).map(y0.invert)); + } + function zoomstarted(dispatch) { + if (!zooming++) dispatch({ + type: "zoomstart" + }); + } + function zoomed(dispatch) { + rescale(); + dispatch({ + type: "zoom", + scale: view.k, + translate: [ view.x, view.y ] + }); + } + function zoomended(dispatch) { + if (!--zooming) dispatch({ + type: "zoomend" + }), center0 = null; + } + function mousedowned() { + var that = this, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that); + d3_selection_interrupt.call(that); + zoomstarted(dispatch); + function moved() { + dragged = 1; + translateTo(d3.mouse(that), location0); + zoomed(dispatch); + } + function ended() { + subject.on(mousemove, null).on(mouseup, null); + dragRestore(dragged); + zoomended(dispatch); + } + } + function touchstarted() { + var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that); + started(); + zoomstarted(dispatch); + subject.on(mousedown, null).on(touchstart, started); + function relocate() { + var touches = d3.touches(that); + scale0 = view.k; + touches.forEach(function(t) { + if (t.identifier in locations0) locations0[t.identifier] = location(t); + }); + return touches; + } + function started() { + var target = d3.event.target; + d3.select(target).on(touchmove, moved).on(touchend, ended); + targets.push(target); + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + locations0[changed[i].identifier] = null; + } + var touches = relocate(), now = Date.now(); + if (touches.length === 1) { + if (now - touchtime < 500) { + var p = touches[0]; + zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1); + d3_eventPreventDefault(); + } + touchtime = now; + } else if (touches.length > 1) { + var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1]; + distance0 = dx * dx + dy * dy; + } + } + function moved() { + var touches = d3.touches(that), p0, l0, p1, l1; + d3_selection_interrupt.call(that); + for (var i = 0, n = touches.length; i < n; ++i, l1 = null) { + p1 = touches[i]; + if (l1 = locations0[p1.identifier]) { + if (l0) break; + p0 = p1, l0 = l1; + } + } + if (l1) { + var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0); + p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; + l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; + scaleTo(scale1 * scale0); + } + touchtime = null; + translateTo(p0, l0); + zoomed(dispatch); + } + function ended() { + if (d3.event.touches.length) { + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + delete locations0[changed[i].identifier]; + } + for (var identifier in locations0) { + return void relocate(); + } + } + d3.selectAll(targets).on(zoomName, null); + subject.on(mousedown, mousedowned).on(touchstart, touchstarted); + dragRestore(); + zoomended(dispatch); + } + } + function mousewheeled() { + var dispatch = event.of(this, arguments); + if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), + translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch); + mousewheelTimer = setTimeout(function() { + mousewheelTimer = null; + zoomended(dispatch); + }, 50); + d3_eventPreventDefault(); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k); + translateTo(center0, translate0); + zoomed(dispatch); + } + function dblclicked() { + var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2; + zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1); + } + return d3.rebind(zoom, event, "on"); + }; + var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel; + d3.color = d3_color; + function d3_color() {} + d3_color.prototype.toString = function() { + return this.rgb() + ""; + }; + d3.hsl = d3_hsl; + function d3_hsl(h, s, l) { + return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l); + } + var d3_hslPrototype = d3_hsl.prototype = new d3_color(); + d3_hslPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, this.l / k); + }; + d3_hslPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, k * this.l); + }; + d3_hslPrototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); + }; + function d3_hsl_rgb(h, s, l) { + var m1, m2; + h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h; + s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + function v(h) { + if (h > 360) h -= 360; else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + function vv(h) { + return Math.round(v(h) * 255); + } + return new d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + } + d3.hcl = d3_hcl; + function d3_hcl(h, c, l) { + return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l); + } + var d3_hclPrototype = d3_hcl.prototype = new d3_color(); + d3_hclPrototype.brighter = function(k) { + return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.darker = function(k) { + return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.rgb = function() { + return d3_hcl_lab(this.h, this.c, this.l).rgb(); + }; + function d3_hcl_lab(h, c, l) { + if (isNaN(h)) h = 0; + if (isNaN(c)) c = 0; + return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); + } + d3.lab = d3_lab; + function d3_lab(l, a, b) { + return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b); + } + var d3_lab_K = 18; + var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; + var d3_labPrototype = d3_lab.prototype = new d3_color(); + d3_labPrototype.brighter = function(k) { + return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.darker = function(k) { + return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.rgb = function() { + return d3_lab_rgb(this.l, this.a, this.b); + }; + function d3_lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = d3_lab_xyz(x) * d3_lab_X; + y = d3_lab_xyz(y) * d3_lab_Y; + z = d3_lab_xyz(z) * d3_lab_Z; + return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + } + function d3_lab_hcl(l, a, b) { + return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l); + } + function d3_lab_xyz(x) { + return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + function d3_xyz_lab(x) { + return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + function d3_xyz_rgb(r) { + return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); + } + d3.rgb = d3_rgb; + function d3_rgb(r, g, b) { + return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b); + } + function d3_rgbNumber(value) { + return new d3_rgb(value >> 16, value >> 8 & 255, value & 255); + } + function d3_rgbString(value) { + return d3_rgbNumber(value) + ""; + } + var d3_rgbPrototype = d3_rgb.prototype = new d3_color(); + d3_rgbPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + var r = this.r, g = this.g, b = this.b, i = 30; + if (!r && !g && !b) return new d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k)); + }; + d3_rgbPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_rgb(k * this.r, k * this.g, k * this.b); + }; + d3_rgbPrototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); + }; + d3_rgbPrototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); + }; + function d3_rgb_hex(v) { + return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); + } + function d3_rgb_parse(format, rgb, hsl) { + var r = 0, g = 0, b = 0, m1, m2, color; + m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase()); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": + { + return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); + } + + case "rgb": + { + return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); + } + } + } + if (color = d3_rgb_names.get(format)) { + return rgb(color.r, color.g, color.b); + } + if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) { + if (format.length === 4) { + r = (color & 3840) >> 4; + r = r >> 4 | r; + g = color & 240; + g = g >> 4 | g; + b = color & 15; + b = b << 4 | b; + } else if (format.length === 7) { + r = (color & 16711680) >> 16; + g = (color & 65280) >> 8; + b = color & 255; + } + } + return rgb(r, g, b); + } + function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; + h *= 60; + } else { + h = NaN; + s = l > 0 && l < 1 ? 0 : h; + } + return new d3_hsl(h, s, l); + } + function d3_rgb_lab(r, g, b) { + r = d3_rgb_xyz(r); + g = d3_rgb_xyz(g); + b = d3_rgb_xyz(b); + var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); + return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + function d3_rgb_xyz(r) { + return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); + } + function d3_rgb_parseNumber(c) { + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; + } + var d3_rgb_names = d3.map({ + aliceblue: 15792383, + antiquewhite: 16444375, + aqua: 65535, + aquamarine: 8388564, + azure: 15794175, + beige: 16119260, + bisque: 16770244, + black: 0, + blanchedalmond: 16772045, + blue: 255, + blueviolet: 9055202, + brown: 10824234, + burlywood: 14596231, + cadetblue: 6266528, + chartreuse: 8388352, + chocolate: 13789470, + coral: 16744272, + cornflowerblue: 6591981, + cornsilk: 16775388, + crimson: 14423100, + cyan: 65535, + darkblue: 139, + darkcyan: 35723, + darkgoldenrod: 12092939, + darkgray: 11119017, + darkgreen: 25600, + darkgrey: 11119017, + darkkhaki: 12433259, + darkmagenta: 9109643, + darkolivegreen: 5597999, + darkorange: 16747520, + darkorchid: 10040012, + darkred: 9109504, + darksalmon: 15308410, + darkseagreen: 9419919, + darkslateblue: 4734347, + darkslategray: 3100495, + darkslategrey: 3100495, + darkturquoise: 52945, + darkviolet: 9699539, + deeppink: 16716947, + deepskyblue: 49151, + dimgray: 6908265, + dimgrey: 6908265, + dodgerblue: 2003199, + firebrick: 11674146, + floralwhite: 16775920, + forestgreen: 2263842, + fuchsia: 16711935, + gainsboro: 14474460, + ghostwhite: 16316671, + gold: 16766720, + goldenrod: 14329120, + gray: 8421504, + green: 32768, + greenyellow: 11403055, + grey: 8421504, + honeydew: 15794160, + hotpink: 16738740, + indianred: 13458524, + indigo: 4915330, + ivory: 16777200, + khaki: 15787660, + lavender: 15132410, + lavenderblush: 16773365, + lawngreen: 8190976, + lemonchiffon: 16775885, + lightblue: 11393254, + lightcoral: 15761536, + lightcyan: 14745599, + lightgoldenrodyellow: 16448210, + lightgray: 13882323, + lightgreen: 9498256, + lightgrey: 13882323, + lightpink: 16758465, + lightsalmon: 16752762, + lightseagreen: 2142890, + lightskyblue: 8900346, + lightslategray: 7833753, + lightslategrey: 7833753, + lightsteelblue: 11584734, + lightyellow: 16777184, + lime: 65280, + limegreen: 3329330, + linen: 16445670, + magenta: 16711935, + maroon: 8388608, + mediumaquamarine: 6737322, + mediumblue: 205, + mediumorchid: 12211667, + mediumpurple: 9662683, + mediumseagreen: 3978097, + mediumslateblue: 8087790, + mediumspringgreen: 64154, + mediumturquoise: 4772300, + mediumvioletred: 13047173, + midnightblue: 1644912, + mintcream: 16121850, + mistyrose: 16770273, + moccasin: 16770229, + navajowhite: 16768685, + navy: 128, + oldlace: 16643558, + olive: 8421376, + olivedrab: 7048739, + orange: 16753920, + orangered: 16729344, + orchid: 14315734, + palegoldenrod: 15657130, + palegreen: 10025880, + paleturquoise: 11529966, + palevioletred: 14381203, + papayawhip: 16773077, + peachpuff: 16767673, + peru: 13468991, + pink: 16761035, + plum: 14524637, + powderblue: 11591910, + purple: 8388736, + rebeccapurple: 6697881, + red: 16711680, + rosybrown: 12357519, + royalblue: 4286945, + saddlebrown: 9127187, + salmon: 16416882, + sandybrown: 16032864, + seagreen: 3050327, + seashell: 16774638, + sienna: 10506797, + silver: 12632256, + skyblue: 8900331, + slateblue: 6970061, + slategray: 7372944, + slategrey: 7372944, + snow: 16775930, + springgreen: 65407, + steelblue: 4620980, + tan: 13808780, + teal: 32896, + thistle: 14204888, + tomato: 16737095, + turquoise: 4251856, + violet: 15631086, + wheat: 16113331, + white: 16777215, + whitesmoke: 16119285, + yellow: 16776960, + yellowgreen: 10145074 + }); + d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgbNumber(value)); + }); + function d3_functor(v) { + return typeof v === "function" ? v : function() { + return v; + }; + } + d3.functor = d3_functor; + d3.xhr = d3_xhrType(d3_identity); + function d3_xhrType(response) { + return function(url, mimeType, callback) { + if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, + mimeType = null; + return d3_xhr(url, mimeType, response, callback); + }; + } + function d3_xhr(url, mimeType, response, callback) { + var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null; + if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest(); + "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() { + request.readyState > 3 && respond(); + }; + function respond() { + var status = request.status, result; + if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) { + try { + result = response.call(xhr, request); + } catch (e) { + dispatch.error.call(xhr, e); + return; + } + dispatch.load.call(xhr, result); + } else { + dispatch.error.call(xhr, request); + } + } + request.onprogress = function(event) { + var o = d3.event; + d3.event = event; + try { + dispatch.progress.call(xhr, request); + } finally { + d3.event = o; + } + }; + xhr.header = function(name, value) { + name = (name + "").toLowerCase(); + if (arguments.length < 2) return headers[name]; + if (value == null) delete headers[name]; else headers[name] = value + ""; + return xhr; + }; + xhr.mimeType = function(value) { + if (!arguments.length) return mimeType; + mimeType = value == null ? null : value + ""; + return xhr; + }; + xhr.responseType = function(value) { + if (!arguments.length) return responseType; + responseType = value; + return xhr; + }; + xhr.response = function(value) { + response = value; + return xhr; + }; + [ "get", "post" ].forEach(function(method) { + xhr[method] = function() { + return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments))); + }; + }); + xhr.send = function(method, data, callback) { + if (arguments.length === 2 && typeof data === "function") callback = data, data = null; + request.open(method, url, true); + if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*"; + if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]); + if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType); + if (responseType != null) request.responseType = responseType; + if (callback != null) xhr.on("error", callback).on("load", function(request) { + callback(null, request); + }); + dispatch.beforesend.call(xhr, request); + request.send(data == null ? null : data); + return xhr; + }; + xhr.abort = function() { + request.abort(); + return xhr; + }; + d3.rebind(xhr, dispatch, "on"); + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); + } + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; + } + function d3_xhrHasResponse(request) { + var type = request.responseType; + return type && type !== "text" ? request.response : request.responseText; + } + d3.dsv = function(delimiter, mimeType) { + var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); + function dsv(url, row, callback) { + if (arguments.length < 3) callback = row, row = null; + var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback); + xhr.row = function(_) { + return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row; + }; + return xhr; + } + function response(request) { + return dsv.parse(request.responseText); + } + function typedResponse(f) { + return function(request) { + return dsv.parse(request.responseText, f); + }; + } + dsv.parse = function(text, f) { + var o; + return dsv.parseRows(text, function(row, i) { + if (o) return o(row, i - 1); + var a = new Function("d", "return {" + row.map(function(name, i) { + return JSON.stringify(name) + ": d[" + i + "]"; + }).join(",") + "}"); + o = f ? function(row, i) { + return f(a(row), i); + } : a; + }); + }; + dsv.parseRows = function(text, f) { + var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol; + function token() { + if (I >= N) return EOF; + if (eol) return eol = false, EOL; + var j = I; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < N) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + ++i; + } + } + I = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) ++I; + } else if (c === 10) { + eol = true; + } + return text.slice(j + 1, i).replace(/""/g, '"'); + } + while (I < N) { + var c = text.charCodeAt(I++), k = 1; + if (c === 10) eol = true; else if (c === 13) { + eol = true; + if (text.charCodeAt(I) === 10) ++I, ++k; + } else if (c !== delimiterCode) continue; + return text.slice(j, I - k); + } + return text.slice(j); + } + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && (a = f(a, n++)) == null) continue; + rows.push(a); + } + return rows; + }; + dsv.format = function(rows) { + if (Array.isArray(rows[0])) return dsv.formatRows(rows); + var fieldSet = new d3_Set(), fields = []; + rows.forEach(function(row) { + for (var field in row) { + if (!fieldSet.has(field)) { + fields.push(fieldSet.add(field)); + } + } + }); + return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) { + return fields.map(function(field) { + return formatValue(row[field]); + }).join(delimiter); + })).join("\n"); + }; + dsv.formatRows = function(rows) { + return rows.map(formatRow).join("\n"); + }; + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + function formatValue(text) { + return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; + } + return dsv; + }; + d3.csv = d3.dsv(",", "text/csv"); + d3.tsv = d3.dsv(" ", "text/tab-separated-values"); + var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) { + setTimeout(callback, 17); + }; + d3.timer = function() { + d3_timer.apply(this, arguments); + }; + function d3_timer(callback, delay, then) { + var n = arguments.length; + if (n < 2) delay = 0; + if (n < 3) then = Date.now(); + var time = then + delay, timer = { + c: callback, + t: time, + n: null + }; + if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer; + d3_timer_queueTail = timer; + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + return timer; + } + function d3_timer_step() { + var now = d3_timer_mark(), delay = d3_timer_sweep() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + } + d3.timer.flush = function() { + d3_timer_mark(); + d3_timer_sweep(); + }; + function d3_timer_mark() { + var now = Date.now(), timer = d3_timer_queueHead; + while (timer) { + if (now >= timer.t && timer.c(now - timer.t)) timer.c = null; + timer = timer.n; + } + return now; + } + function d3_timer_sweep() { + var t0, t1 = d3_timer_queueHead, time = Infinity; + while (t1) { + if (t1.c) { + if (t1.t < time) time = t1.t; + t1 = (t0 = t1).n; + } else { + t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n; + } + } + d3_timer_queueTail = t0; + return time; + } + function d3_format_precision(x, p) { + return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); + } + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + d3.formatPrefix = function(value, precision) { + var i = 0; + if (value = +value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; + }; + function d3_formatPrefix(d, i) { + var k = Math.pow(10, abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + } + function d3_locale_numberFormat(locale) { + var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) { + var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0; + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = locale_grouping[j = (j + 1) % locale_grouping.length]; + } + return t.reverse().join(locale_thousands); + } : d3_identity; + return function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true; + if (precision) precision = +precision.substring(1); + if (zfill || fill === "0" && align === "=") { + zfill = fill = "0"; + align = "="; + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + + case "b": + case "o": + case "x": + case "X": + if (symbol === "#") prefix = "0" + type.toLowerCase(); + + case "c": + exponent = false; + + case "d": + integer = true; + precision = 0; + break; + + case "s": + scale = -1; + type = "r"; + break; + } + if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1]; + if (type == "r" && !precision) type = "g"; + if (precision != null) { + if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision)); + } + type = d3_format_types.get(type) || d3_format_typeDefault; + var zcomma = zfill && comma; + return function(value) { + var fullSuffix = suffix; + if (integer && value % 1) return ""; + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign; + if (scale < 0) { + var unit = d3.formatPrefix(value, precision); + value = unit.scale(value); + fullSuffix = unit.symbol + suffix; + } else { + value *= scale; + } + value = type(value, precision); + var i = value.lastIndexOf("."), before, after; + if (i < 0) { + var j = exponent ? value.lastIndexOf("e") : -1; + if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j); + } else { + before = value.substring(0, i); + after = locale_decimal + value.substring(i + 1); + } + if (!zfill && comma) before = formatGroup(before, Infinity); + var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : ""; + if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity); + negative += prefix; + value = before + after; + return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix; + }; + }; + } + var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + var d3_format_types = d3.map({ + b: function(x) { + return x.toString(2); + }, + c: function(x) { + return String.fromCharCode(x); + }, + o: function(x) { + return x.toString(8); + }, + x: function(x) { + return x.toString(16); + }, + X: function(x) { + return x.toString(16).toUpperCase(); + }, + g: function(x, p) { + return x.toPrecision(p); + }, + e: function(x, p) { + return x.toExponential(p); + }, + f: function(x, p) { + return x.toFixed(p); + }, + r: function(x, p) { + return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p)))); + } + }); + function d3_format_typeDefault(x) { + return x + ""; + } + var d3_time = d3.time = {}, d3_date = Date; + function d3_date_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + d3_date_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_date(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_date(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + function range_utc(t0, t1, dt) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_date = Date; + } + } + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; + } + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_date = Date; + } + }; + } + d3_time.year = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setMonth(0, 1); + return date; + }, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); + }, function(date) { + return date.getFullYear(); + }); + d3_time.years = d3_time.year.range; + d3_time.years.utc = d3_time.year.utc.range; + d3_time.day = d3_time_interval(function(date) { + var day = new d3_date(2e3, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3_time.days = d3_time.day.range; + d3_time.days.utc = d3_time.day.utc.range; + d3_time.dayOfYear = function(date) { + var year = d3_time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); + }; + [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) { + i = 7 - i; + var interval = d3_time[day] = d3_time_interval(function(date) { + (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3_time[day + "s"] = interval.range; + d3_time[day + "s"].utc = interval.utc.range; + d3_time[day + "OfYear"] = function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3_time.week = d3_time.sunday; + d3_time.weeks = d3_time.sunday.range; + d3_time.weeks.utc = d3_time.sunday.utc.range; + d3_time.weekOfYear = d3_time.sundayOfYear; + function d3_locale_timeFormat(locale) { + var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths; + function d3_time_format(template) { + var n = template.length; + function format(date) { + var string = [], i = -1, j = 0, c, p, f; + while (++i < n) { + if (template.charCodeAt(i) === 37) { + string.push(template.slice(j, i)); + if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i); + if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p); + string.push(c); + j = i + 1; + } + } + string.push(template.slice(j, i)); + return string.join(""); + } + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0, + Z: null + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)(); + if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("W" in d || "U" in d) { + if (!("w" in d)) d.w = "W" in d ? 1 : 0; + date.setFullYear(d.y, 0, 1); + date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7); + } else date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L); + return localZ ? date._ : date; + }; + format.toString = function() { + return template; + }; + return format; + } + function d3_time_parse(date, template, string, j) { + var c, p, t, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c === 37) { + t = template.charAt(i++); + p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + d3_time_format.utc = function(template) { + var local = d3_time_format(template); + function format(date) { + try { + d3_date = d3_date_utc; + var utc = new d3_date(); + utc._ = date; + return local(utc); + } finally { + d3_date = Date; + } + } + format.parse = function(string) { + try { + d3_date = d3_date_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_date = Date; + } + }; + format.toString = local.toString; + return format; + }; + d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti; + var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths); + locale_periods.forEach(function(p, i) { + d3_time_periodLookup.set(p.toLowerCase(), i); + }); + var d3_time_formats = { + a: function(d) { + return locale_shortDays[d.getDay()]; + }, + A: function(d) { + return locale_days[d.getDay()]; + }, + b: function(d) { + return locale_shortMonths[d.getMonth()]; + }, + B: function(d) { + return locale_months[d.getMonth()]; + }, + c: d3_time_format(locale_dateTime), + d: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + e: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + H: function(d, p) { + return d3_time_formatPad(d.getHours(), p, 2); + }, + I: function(d, p) { + return d3_time_formatPad(d.getHours() % 12 || 12, p, 2); + }, + j: function(d, p) { + return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3); + }, + L: function(d, p) { + return d3_time_formatPad(d.getMilliseconds(), p, 3); + }, + m: function(d, p) { + return d3_time_formatPad(d.getMonth() + 1, p, 2); + }, + M: function(d, p) { + return d3_time_formatPad(d.getMinutes(), p, 2); + }, + p: function(d) { + return locale_periods[+(d.getHours() >= 12)]; + }, + S: function(d, p) { + return d3_time_formatPad(d.getSeconds(), p, 2); + }, + U: function(d, p) { + return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d, p) { + return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2); + }, + x: d3_time_format(locale_date), + X: d3_time_format(locale_time), + y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 100, p, 2); + }, + Y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 1e4, p, 4); + }, + Z: d3_time_zone, + "%": function() { + return "%"; + } + }; + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + j: d3_time_parseDayOfYear, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + U: d3_time_parseWeekNumberSunday, + w: d3_time_parseWeekdayNumber, + W: d3_time_parseWeekNumberMonday, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear, + Z: d3_time_parseZone, + "%": d3_time_parseLiteralPercent + }; + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + return d3_time_format; + } + var d3_time_formatPads = { + "-": "", + _: " ", + "0": "0" + }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/; + function d3_time_formatPad(value, fill, width) { + var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map(), i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_parseWeekdayNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 1)); + return n ? (date.w = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberSunday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.U = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberMonday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.W = +n[0], i + n[0].length) : -1; + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 4)); + return n ? (date.y = +n[0], i + n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1; + } + function d3_time_parseZone(date, string, i) { + return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, + i + 5) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.m = n[0] - 1, i + n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.d = +n[0], i + n[0].length) : -1; + } + function d3_time_parseDayOfYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.j = +n[0], i + n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.H = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.M = +n[0], i + n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.S = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.L = +n[0], i + n[0].length) : -1; + } + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60; + return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2); + } + function d3_time_parseLiteralPercent(date, string, i) { + d3_time_percentRe.lastIndex = 0; + var n = d3_time_percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; + } + function d3_time_formatMulti(formats) { + var n = formats.length, i = -1; + while (++i < n) formats[i][0] = this(formats[i][0]); + return function(date) { + var i = 0, f = formats[i]; + while (!f[1](date)) f = formats[++i]; + return f[0](date); + }; + } + d3.locale = function(locale) { + return { + numberFormat: d3_locale_numberFormat(locale), + timeFormat: d3_locale_timeFormat(locale) + }; + }; + var d3_locale_enUS = d3.locale({ + decimal: ".", + thousands: ",", + grouping: [ 3 ], + currency: [ "$", "" ], + dateTime: "%a %b %e %X %Y", + date: "%m/%d/%Y", + time: "%H:%M:%S", + periods: [ "AM", "PM" ], + days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], + shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], + months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], + shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] + }); + d3.format = d3_locale_enUS.numberFormat; + d3.geo = {}; + function d3_adder() {} + d3_adder.prototype = { + s: 0, + t: 0, + add: function(y) { + d3_adderSum(y, this.t, d3_adderTemp); + d3_adderSum(d3_adderTemp.s, this.s, this); + if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t; + }, + reset: function() { + this.s = this.t = 0; + }, + valueOf: function() { + return this.s; + } + }; + var d3_adderTemp = new d3_adder(); + function d3_adderSum(a, b, o) { + var x = o.s = a + b, bv = x - a, av = x - bv; + o.t = a - av + (b - bv); + } + d3.geo.stream = function(object, listener) { + if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + object = object.coordinates; + listener.point(object[0], object[1], object[2]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder(); + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRingSum.reset(); + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + var area = 2 * d3_geo_areaRingSum; + d3_geo_areaSum += area < 0 ? 4 * π + area : area; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), + sinφ0 = Math.sin(φ); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + φ = φ * d3_radians / 2 + π / 4; + var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ); + d3_geo_areaRingSum.add(Math.atan2(v, u)); + λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ]; + } + function d3_geo_sphericalEqual(a, b) { + return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε; + } + d3.geo.bounds = function() { + var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range; + var bound = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + bound.point = ringPoint; + bound.lineStart = ringStart; + bound.lineEnd = ringEnd; + dλSum = 0; + d3_geo_area.polygonStart(); + }, + polygonEnd: function() { + d3_geo_area.polygonEnd(); + bound.point = point; + bound.lineStart = lineStart; + bound.lineEnd = lineEnd; + if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90; + range[0] = λ0, range[1] = λ1; + } + }; + function point(λ, φ) { + ranges.push(range = [ λ0 = λ, λ1 = λ ]); + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + function linePoint(λ, φ) { + var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]); + if (p0) { + var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal); + d3_geo_cartesianNormalize(inflection); + inflection = d3_geo_spherical(inflection); + var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180; + if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = inflection[1] * d3_degrees; + if (φi > φ1) φ1 = φi; + } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = -inflection[1] * d3_degrees; + if (φi < φ0) φ0 = φi; + } else { + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + if (antimeridian) { + if (λ < λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } else { + if (λ1 >= λ0) { + if (λ < λ0) λ0 = λ; + if (λ > λ1) λ1 = λ; + } else { + if (λ > λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } + } + } else { + point(λ, φ); + } + p0 = p, λ_ = λ; + } + function lineStart() { + bound.point = linePoint; + } + function lineEnd() { + range[0] = λ0, range[1] = λ1; + bound.point = point; + p0 = null; + } + function ringPoint(λ, φ) { + if (p0) { + var dλ = λ - λ_; + dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ; + } else λ__ = λ, φ__ = φ; + d3_geo_area.point(λ, φ); + linePoint(λ, φ); + } + function ringStart() { + d3_geo_area.lineStart(); + } + function ringEnd() { + ringPoint(λ__, φ__); + d3_geo_area.lineEnd(); + if (abs(dλSum) > ε) λ0 = -(λ1 = 180); + range[0] = λ0, range[1] = λ1; + p0 = null; + } + function angle(λ0, λ1) { + return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1; + } + function compareRanges(a, b) { + return a[0] - b[0]; + } + function withinRange(x, range) { + return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x; + } + return function(feature) { + φ1 = λ1 = -(λ0 = φ0 = Infinity); + ranges = []; + d3.geo.stream(feature, bound); + var n = ranges.length; + if (n) { + ranges.sort(compareRanges); + for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) { + b = ranges[i]; + if (withinRange(b[0], a) || withinRange(b[1], a)) { + if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1]; + if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0]; + } else { + merged.push(a = b); + } + } + var best = -Infinity, dλ; + for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) { + b = merged[i]; + if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1]; + } + } + ranges = range = null; + return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ]; + }; + }(); + d3.geo.centroid = function(object) { + d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, d3_geo_centroid); + var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z; + if (m < ε2) { + x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1; + if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0; + m = x * x + y * y + z * z; + if (m < ε2) return [ NaN, NaN ]; + } + return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ]; + }; + var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2; + var d3_geo_centroid = { + sphere: d3_noop, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ)); + } + function d3_geo_centroidPointXYZ(x, y, z) { + ++d3_geo_centroidW0; + d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0; + d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0; + d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } + function d3_geo_centroidRingStart() { + var λ00, φ00, x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ00 = λ, φ00 = φ; + d3_geo_centroid.point = nextPoint; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + d3_geo_centroid.lineEnd = function() { + nextPoint(λ00, φ00); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + d3_geo_centroid.point = d3_geo_centroidPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u); + d3_geo_centroidX2 += v * cx; + d3_geo_centroidY2 += v * cy; + d3_geo_centroidZ2 += v * cz; + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_compose(a, b) { + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); + } + if (a.invert && b.invert) compose.invert = function(x, y) { + return x = b.invert(x, y), x && a.invert(x[0], x[1]); + }; + return compose; + } + function d3_true() { + return true; + } + function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) { + var subject = [], clip = []; + segments.forEach(function(segment) { + if ((n = segment.length - 1) <= 0) return; + var n, p0 = segment[0], p1 = segment[n]; + if (d3_geo_sphericalEqual(p0, p1)) { + listener.lineStart(); + for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]); + listener.lineEnd(); + return; + } + var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false); + a.o = b; + subject.push(a); + clip.push(b); + a = new d3_geo_clipPolygonIntersection(p1, segment, null, false); + b = new d3_geo_clipPolygonIntersection(p1, null, a, true); + a.o = b; + subject.push(a); + clip.push(b); + }); + clip.sort(compare); + d3_geo_clipPolygonLinkCircular(subject); + d3_geo_clipPolygonLinkCircular(clip); + if (!subject.length) return; + for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) { + clip[i].e = entry = !entry; + } + var start = subject[0], points, point; + while (1) { + var current = start, isSubject = true; + while (current.v) if ((current = current.n) === start) return; + points = current.z; + listener.lineStart(); + do { + current.v = current.o.v = true; + if (current.e) { + if (isSubject) { + for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.n.x, 1, listener); + } + current = current.n; + } else { + if (isSubject) { + points = current.p.z; + for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.p.x, -1, listener); + } + current = current.p; + } + current = current.o; + points = current.z; + isSubject = !isSubject; + } while (!current.v); + listener.lineEnd(); + } + } + function d3_geo_clipPolygonLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.n = b = array[i]; + b.p = a; + a = b; + } + a.n = b = array[0]; + b.p = a; + } + function d3_geo_clipPolygonIntersection(point, points, other, entry) { + this.x = point; + this.z = points; + this.o = other; + this.e = entry; + this.v = false; + this.n = this.p = null; + } + function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) { + return function(rotate, listener) { + var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + segments = []; + polygon = []; + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon); + if (segments.length) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener); + } else if (clipStartInside) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (polygonStarted) listener.polygonEnd(), polygonStarted = false; + segments = polygon = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + var point = rotate(λ, φ); + if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ); + } + function pointLine(λ, φ) { + var point = rotate(λ, φ); + line.point(point[0], point[1]); + } + function lineStart() { + clip.point = pointLine; + line.lineStart(); + } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring; + function pointRing(λ, φ) { + ring.push([ λ, φ ]); + var point = rotate(λ, φ); + ringListener.point(point[0], point[1]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + ring.pop(); + polygon.push(ring); + ring = null; + if (!n) return; + if (clean & 1) { + segment = ringSegments[0]; + var n = segment.length - 1, i = -1, point; + if (n > 0) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + } + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipSegmentLength1(segment) { + return segment.length > 1; + } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + }, + rejoin: function() { + if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); + } + }; + } + function d3_geo_clipSort(a, b) { + return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]); + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0); + if (abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * halfπ; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (abs(from[0] - to[0]) > ε) { + var s = from[0] < to[0] ? π : -π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_pointInPolygon(point, polygon) { + var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0; + d3_geo_areaRingSum.reset(); + for (var i = 0, n = polygon.length; i < n; ++i) { + var ring = polygon[i], m = ring.length; + if (!m) continue; + var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1; + while (true) { + if (j === m) j = 0; + point = ring[j]; + var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ; + d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ))); + polarAngle += antimeridian ? dλ + sdλ * τ : dλ; + if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) { + var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point)); + d3_geo_cartesianNormalize(arc); + var intersection = d3_geo_cartesianCross(meridianNormal, arc); + d3_geo_cartesianNormalize(intersection); + var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]); + if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) { + winding += antimeridian ^ dλ >= 0 ? 1 : -1; + } + } + if (!j++) break; + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point; + } + } + return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < 0) ^ winding & 1; + } + function d3_geo_clipCircle(radius) { + var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, c0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0; + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } else if (notHemisphere && point0 && smallRadius ^ v) { + var t; + if (!(c & c0) && (t = intersect(point1, point0, true))) { + clean = 0; + if (smallRadius) { + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + } else { + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + } + } + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) { + listener.point(point1[0], point1[1]); + } + point0 = point1, v0 = v, c0 = c; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b, two) { + var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return !two && a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1); + if (t2 < 0) return; + var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + q = d3_geo_spherical(q); + if (!two) return q; + var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z; + if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z; + var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε; + if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z; + if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) { + var q1 = d3_geo_cartesianScale(u, (-w + t) / uu); + d3_geo_cartesianAdd(q1, A); + return [ q, d3_geo_spherical(q1) ]; + } + } + function code(λ, φ) { + var r = smallRadius ? radius : π - radius, code = 0; + if (λ < -r) code |= 1; else if (λ > r) code |= 2; + if (φ < -r) code |= 4; else if (φ > r) code |= 8; + return code; + } + } + function d3_geom_clipLine(x0, y0, x1, y1) { + return function(line) { + var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r; + r = x0 - ax; + if (!dx && r > 0) return; + r /= dx; + if (dx < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dx > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = x1 - ax; + if (!dx && r < 0) return; + r /= dx; + if (dx < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dx > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + r = y0 - ay; + if (!dy && r > 0) return; + r /= dy; + if (dy < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dy > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = y1 - ay; + if (!dy && r < 0) return; + r /= dy; + if (dy < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dy > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + if (t0 > 0) line.a = { + x: ax + t0 * dx, + y: ay + t0 * dy + }; + if (t1 < 1) line.b = { + x: ax + t1 * dx, + y: ay + t1 * dy + }; + return line; + }; + } + var d3_geo_clipExtentMAX = 1e9; + d3.geo.clipExtent = function() { + var x0, y0, x1, y1, stream, clip, clipExtent = { + stream: function(output) { + if (stream) stream.valid = false; + stream = clip(output); + stream.valid = true; + return stream; + }, + extent: function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]); + if (stream) stream.valid = false, stream = null; + return clipExtent; + } + }; + return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]); + }; + function d3_geo_clipExtent(x0, y0, x1, y1) { + return function(listener) { + var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring; + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + listener = bufferListener; + segments = []; + polygon = []; + clean = true; + }, + polygonEnd: function() { + listener = listener_; + segments = d3.merge(segments); + var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length; + if (inside || visible) { + listener.polygonStart(); + if (inside) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (visible) { + d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener); + } + listener.polygonEnd(); + } + segments = polygon = ring = null; + } + }; + function insidePolygon(p) { + var wn = 0, n = polygon.length, y = p[1]; + for (var i = 0; i < n; ++i) { + for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) { + b = v[j]; + if (a[1] <= y) { + if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn; + } else { + if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn; + } + a = b; + } + } + return wn !== 0; + } + function interpolate(from, to, direction, listener) { + var a = 0, a1 = 0; + if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) { + do { + listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); + } while ((a = (a + direction + 4) % 4) !== a1); + } else { + listener.point(to[0], to[1]); + } + } + function pointVisible(x, y) { + return x0 <= x && x <= x1 && y0 <= y && y <= y1; + } + function point(x, y) { + if (pointVisible(x, y)) listener.point(x, y); + } + var x__, y__, v__, x_, y_, v_, first, clean; + function lineStart() { + clip.point = linePoint; + if (polygon) polygon.push(ring = []); + first = true; + v_ = false; + x_ = y_ = NaN; + } + function lineEnd() { + if (segments) { + linePoint(x__, y__); + if (v__ && v_) bufferListener.rejoin(); + segments.push(bufferListener.buffer()); + } + clip.point = point; + if (v_) listener.lineEnd(); + } + function linePoint(x, y) { + x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x)); + y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y)); + var v = pointVisible(x, y); + if (polygon) ring.push([ x, y ]); + if (first) { + x__ = x, y__ = y, v__ = v; + first = false; + if (v) { + listener.lineStart(); + listener.point(x, y); + } + } else { + if (v && v_) listener.point(x, y); else { + var l = { + a: { + x: x_, + y: y_ + }, + b: { + x: x, + y: y + } + }; + if (clipLine(l)) { + if (!v_) { + listener.lineStart(); + listener.point(l.a.x, l.a.y); + } + listener.point(l.b.x, l.b.y); + if (!v) listener.lineEnd(); + clean = false; + } else if (v) { + listener.lineStart(); + listener.point(x, y); + clean = false; + } + } + } + x_ = x, y_ = y, v_ = v; + } + return clip; + }; + function corner(p, direction) { + return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2; + } + function compare(a, b) { + return comparePoints(a.x, b.x); + } + function comparePoints(a, b) { + var ca = corner(a, 1), cb = corner(b, 1); + return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0]; + } + } + function d3_geo_conic(projectAt) { + var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1); + p.parallels = function(_) { + if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ]; + return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180); + }; + return p; + } + function d3_geo_conicEqualArea(φ0, φ1) { + var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n; + function forward(λ, φ) { + var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n; + return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = ρ0 - y; + return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ]; + }; + return forward; + } + (d3.geo.conicEqualArea = function() { + return d3_geo_conic(d3_geo_conicEqualArea); + }).raw = d3_geo_conicEqualArea; + d3.geo.albers = function() { + return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070); + }; + d3.geo.albersUsa = function() { + var lower48 = d3.geo.albers(); + var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]); + var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]); + var point, pointStream = { + point: function(x, y) { + point = [ x, y ]; + } + }, lower48Point, alaskaPoint, hawaiiPoint; + function albersUsa(coordinates) { + var x = coordinates[0], y = coordinates[1]; + point = null; + (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y); + return point; + } + albersUsa.invert = function(coordinates) { + var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k; + return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates); + }; + albersUsa.stream = function(stream) { + var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream); + return { + point: function(x, y) { + lower48Stream.point(x, y); + alaskaStream.point(x, y); + hawaiiStream.point(x, y); + }, + sphere: function() { + lower48Stream.sphere(); + alaskaStream.sphere(); + hawaiiStream.sphere(); + }, + lineStart: function() { + lower48Stream.lineStart(); + alaskaStream.lineStart(); + hawaiiStream.lineStart(); + }, + lineEnd: function() { + lower48Stream.lineEnd(); + alaskaStream.lineEnd(); + hawaiiStream.lineEnd(); + }, + polygonStart: function() { + lower48Stream.polygonStart(); + alaskaStream.polygonStart(); + hawaiiStream.polygonStart(); + }, + polygonEnd: function() { + lower48Stream.polygonEnd(); + alaskaStream.polygonEnd(); + hawaiiStream.polygonEnd(); + } + }; + }; + albersUsa.precision = function(_) { + if (!arguments.length) return lower48.precision(); + lower48.precision(_); + alaska.precision(_); + hawaii.precision(_); + return albersUsa; + }; + albersUsa.scale = function(_) { + if (!arguments.length) return lower48.scale(); + lower48.scale(_); + alaska.scale(_ * .35); + hawaii.scale(_); + return albersUsa.translate(lower48.translate()); + }; + albersUsa.translate = function(_) { + if (!arguments.length) return lower48.translate(); + var k = lower48.scale(), x = +_[0], y = +_[1]; + lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point; + alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + return albersUsa; + }; + return albersUsa.scale(1070); + }; + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1; + var d3_geo_pathBounds = { + point: d3_geo_pathBoundsPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_pathBoundsPoint(x, y) { + if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x; + if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x; + if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y; + if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathBufferCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); + } + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + function d3_geo_pathBufferCircle(radius) { + return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z"; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + d3_geo_centroidX0 += x; + d3_geo_centroidY0 += y; + ++d3_geo_centroidZ0; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + z = y0 * x - x0 * y; + d3_geo_centroidX2 += z * (x0 + x); + d3_geo_centroidY2 += z * (y0 + y); + d3_geo_centroidZ2 += z * 3; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x + pointRadius, y); + context.arc(x, y, pointRadius, 0, τ); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + function d3_geo_resample(project) { + var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16; + function resample(stream) { + return (maxDepth ? resampleRecursive : resampleNone)(stream); + } + function resampleNone(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + }); + } + function resampleRecursive(stream) { + var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = ringStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function ringStart() { + lineStart(); + resample.point = ringPoint; + resample.lineEnd = ringEnd; + } + function ringPoint(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + } + function ringEnd() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } + } + } + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; + } + d3.geo.path = function() { + var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream; + function path(object) { + if (object) { + if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments)); + if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream); + d3.geo.stream(object, cacheStream); + } + return contextStream.result(); + } + path.area = function(object) { + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; + }; + path.centroid = function(object) { + d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ]; + }; + path.bounds = function(object) { + d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity); + d3.geo.stream(object, projectStream(d3_geo_pathBounds)); + return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ]; + }; + path.projection = function(_) { + if (!arguments.length) return projection; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; + return reset(); + }; + path.context = function(_) { + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); + if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius); + return reset(); + }; + path.pointRadius = function(_) { + if (!arguments.length) return pointRadius; + pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_); + return path; + }; + function reset() { + cacheStream = null; + return path; + } + return path.projection(d3.geo.albersUsa()).context(null); + }; + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(x, y) { + return project([ x * d3_degrees, y * d3_degrees ]); + }); + return function(stream) { + return d3_geo_projectionRadians(resample(stream)); + }; + } + d3.geo.transform = function(methods) { + return { + stream: function(stream) { + var transform = new d3_geo_transform(stream); + for (var k in methods) transform[k] = methods[k]; + return transform; + } + }; + }; + function d3_geo_transform(stream) { + this.stream = stream; + } + d3_geo_transform.prototype = { + point: function(x, y) { + this.stream.point(x, y); + }, + sphere: function() { + this.stream.sphere(); + }, + lineStart: function() { + this.stream.lineStart(); + }, + lineEnd: function() { + this.stream.lineEnd(); + }, + polygonStart: function() { + this.stream.polygonStart(); + }, + polygonEnd: function() { + this.stream.polygonEnd(); + } + }; + function d3_geo_transformPoint(stream, point) { + return { + point: point, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + } + d3.geo.projection = d3_geo_projection; + d3.geo.projectionMutator = d3_geo_projectionMutator; + function d3_geo_projection(project) { + return d3_geo_projectionMutator(function() { + return project; + })(); + } + function d3_geo_projectionMutator(projectAt) { + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; + } + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; + } + projection.stream = function(output) { + if (stream) stream.valid = false; + stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output)))); + stream.valid = true; + return stream; + }; + projection.clipAngle = function(_) { + if (!arguments.length) return clipAngle; + preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians); + return invalidate(); + }; + projection.clipExtent = function(_) { + if (!arguments.length) return clipExtent; + clipExtent = _; + postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity; + return invalidate(); + }; + projection.scale = function(_) { + if (!arguments.length) return k; + k = +_; + return reset(); + }; + projection.translate = function(_) { + if (!arguments.length) return [ x, y ]; + x = +_[0]; + y = +_[1]; + return reset(); + }; + projection.center = function(_) { + if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ]; + λ = _[0] % 360 * d3_radians; + φ = _[1] % 360 * d3_radians; + return reset(); + }; + projection.rotate = function(_) { + if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ]; + δλ = _[0] % 360 * d3_radians; + δφ = _[1] % 360 * d3_radians; + δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; + return reset(); + }; + d3.rebind(projection, projectResample, "precision"); + function reset() { + projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); + var center = project(λ, φ); + δx = x - center[0] * k; + δy = y + center[1] * k; + return invalidate(); + } + function invalidate() { + if (stream) stream.valid = false, stream = null; + return projection; + } + return function() { + project = projectAt.apply(this, arguments); + projection.invert = project.invert && invert; + return reset(); + }; + } + function d3_geo_projectionRadians(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + stream.point(x * d3_radians, y * d3_radians); + }); + } + function d3_geo_equirectangular(λ, φ) { + return [ λ, φ ]; + } + (d3.geo.equirectangular = function() { + return d3_geo_projection(d3_geo_equirectangular); + }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular; + d3.geo.rotation = function(rotate) { + rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0); + function forward(coordinates) { + coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + } + forward.invert = function(coordinates) { + coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + }; + return forward; + }; + function d3_geo_identityRotation(λ, φ) { + return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + } + d3_geo_identityRotation.invert = d3_geo_equirectangular; + function d3_geo_rotation(δλ, δφ, δγ) { + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation; + } + function d3_geo_forwardRotationλ(δλ) { + return function(λ, φ) { + return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + }; + } + function d3_geo_rotationλ(δλ) { + var rotation = d3_geo_forwardRotationλ(δλ); + rotation.invert = d3_geo_forwardRotationλ(-δλ); + return rotation; + } + function d3_geo_rotationφγ(δφ, δγ) { + var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ); + function rotation(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ; + return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ]; + } + rotation.invert = function(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ; + return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ]; + }; + return rotation; + } + d3.geo.circle = function() { + var origin = [ 0, 0 ], angle, precision = 6, interpolate; + function circle() { + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; + interpolate(null, null, 1, { + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; + } + }); + return { + type: "Polygon", + coordinates: [ ring ] + }; + } + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return circle; + }; + circle.angle = function(x) { + if (!arguments.length) return angle; + interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians); + return circle; + }; + circle.precision = function(_) { + if (!arguments.length) return precision; + interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians); + return circle; + }; + return circle.angle(90); + }; + function d3_geo_circleInterpolate(radius, precision) { + var cr = Math.cos(radius), sr = Math.sin(radius); + return function(from, to, direction, listener) { + var step = direction * precision; + if (from != null) { + from = d3_geo_circleAngle(cr, from); + to = d3_geo_circleAngle(cr, to); + if (direction > 0 ? from < to : from > to) from += direction * τ; + } else { + from = radius + direction * τ; + to = radius - .5 * step; + } + for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) { + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); + } + }; + } + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = d3_acos(-a[1]); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + d3.geo.distance = function(a, b) { + var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t; + return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ); + }; + d3.geo.graticule = function() { + var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5; + function graticule() { + return { + type: "MultiLineString", + coordinates: lines() + }; + } + function lines() { + return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) { + return abs(x % DX) > ε; + }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) { + return abs(y % DY) > ε; + }).map(y)); + } + graticule.lines = function() { + return lines().map(function(coordinates) { + return { + type: "LineString", + coordinates: coordinates + }; + }); + }; + graticule.outline = function() { + return { + type: "Polygon", + coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ] + }; + }; + graticule.extent = function(_) { + if (!arguments.length) return graticule.minorExtent(); + return graticule.majorExtent(_).minorExtent(_); + }; + graticule.majorExtent = function(_) { + if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ]; + X0 = +_[0][0], X1 = +_[1][0]; + Y0 = +_[0][1], Y1 = +_[1][1]; + if (X0 > X1) _ = X0, X0 = X1, X1 = _; + if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _; + return graticule.precision(precision); + }; + graticule.minorExtent = function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + x0 = +_[0][0], x1 = +_[1][0]; + y0 = +_[0][1], y1 = +_[1][1]; + if (x0 > x1) _ = x0, x0 = x1, x1 = _; + if (y0 > y1) _ = y0, y0 = y1, y1 = _; + return graticule.precision(precision); + }; + graticule.step = function(_) { + if (!arguments.length) return graticule.minorStep(); + return graticule.majorStep(_).minorStep(_); + }; + graticule.majorStep = function(_) { + if (!arguments.length) return [ DX, DY ]; + DX = +_[0], DY = +_[1]; + return graticule; + }; + graticule.minorStep = function(_) { + if (!arguments.length) return [ dx, dy ]; + dx = +_[0], dy = +_[1]; + return graticule; + }; + graticule.precision = function(_) { + if (!arguments.length) return precision; + precision = +_; + x = d3_geo_graticuleX(y0, y1, 90); + y = d3_geo_graticuleY(x0, x1, precision); + X = d3_geo_graticuleX(Y0, Y1, 90); + Y = d3_geo_graticuleY(X0, X1, precision); + return graticule; + }; + return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]); + }; + function d3_geo_graticuleX(y0, y1, dy) { + var y = d3.range(y0, y1 - ε, dy).concat(y1); + return function(x) { + return y.map(function(y) { + return [ x, y ]; + }); + }; + } + function d3_geo_graticuleY(x0, x1, dx) { + var x = d3.range(x0, x1 - ε, dx).concat(x1); + return function(y) { + return x.map(function(x) { + return [ x, y ]; + }); + }; + } + function d3_source(d) { + return d.source; + } + function d3_target(d) { + return d.target; + } + d3.geo.greatArc = function() { + var source = d3_source, source_, target = d3_target, target_; + function greatArc() { + return { + type: "LineString", + coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ] + }; + } + greatArc.distance = function() { + return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments)); + }; + greatArc.source = function(_) { + if (!arguments.length) return source; + source = _, source_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.target = function(_) { + if (!arguments.length) return target; + target = _, target_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.precision = function() { + return arguments.length ? greatArc : 0; + }; + return greatArc; + }; + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d); + var interpolate = d ? function(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ]; + } : function() { + return [ x0 * d3_degrees, y0 * d3_degrees ]; + }; + interpolate.distance = d; + return interpolate; + } + d3.geo.length = function(object) { + d3_geo_lengthSum = 0; + d3.geo.stream(object, d3_geo_length); + return d3_geo_lengthSum; + }; + var d3_geo_lengthSum; + var d3_geo_length = { + sphere: d3_noop, + point: d3_noop, + lineStart: d3_geo_lengthLineStart, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_lengthLineStart() { + var λ0, sinφ0, cosφ0; + d3_geo_length.point = function(λ, φ) { + λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ); + d3_geo_length.point = nextPoint; + }; + d3_geo_length.lineEnd = function() { + d3_geo_length.point = d3_geo_length.lineEnd = d3_noop; + }; + function nextPoint(λ, φ) { + var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t); + d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ); + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ; + } + } + function d3_geo_azimuthal(scale, angle) { + function azimuthal(λ, φ) { + var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ); + return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ]; + } + azimuthal.invert = function(x, y) { + var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c); + return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ]; + }; + return azimuthal; + } + var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) { + return Math.sqrt(2 / (1 + cosλcosφ)); + }, function(ρ) { + return 2 * Math.asin(ρ / 2); + }); + (d3.geo.azimuthalEqualArea = function() { + return d3_geo_projection(d3_geo_azimuthalEqualArea); + }).raw = d3_geo_azimuthalEqualArea; + var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) { + var c = Math.acos(cosλcosφ); + return c && c / Math.sin(c); + }, d3_identity); + (d3.geo.azimuthalEquidistant = function() { + return d3_geo_projection(d3_geo_azimuthalEquidistant); + }).raw = d3_geo_azimuthalEquidistant; + function d3_geo_conicConformal(φ0, φ1) { + var cosφ0 = Math.cos(φ0), t = function(φ) { + return Math.tan(π / 4 + φ / 2); + }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n; + if (!n) return d3_geo_mercator; + function forward(λ, φ) { + if (F > 0) { + if (φ < -halfπ + ε) φ = -halfπ + ε; + } else { + if (φ > halfπ - ε) φ = halfπ - ε; + } + var ρ = F / Math.pow(t(φ), n); + return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y); + return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ]; + }; + return forward; + } + (d3.geo.conicConformal = function() { + return d3_geo_conic(d3_geo_conicConformal); + }).raw = d3_geo_conicConformal; + function d3_geo_conicEquidistant(φ0, φ1) { + var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0; + if (abs(n) < ε) return d3_geo_equirectangular; + function forward(λ, φ) { + var ρ = G - φ; + return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = G - y; + return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ]; + }; + return forward; + } + (d3.geo.conicEquidistant = function() { + return d3_geo_conic(d3_geo_conicEquidistant); + }).raw = d3_geo_conicEquidistant; + var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / cosλcosφ; + }, Math.atan); + (d3.geo.gnomonic = function() { + return d3_geo_projection(d3_geo_gnomonic); + }).raw = d3_geo_gnomonic; + function d3_geo_mercator(λ, φ) { + return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ]; + } + d3_geo_mercator.invert = function(x, y) { + return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ]; + }; + function d3_geo_mercatorProjection(project) { + var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto; + m.scale = function() { + var v = scale.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.translate = function() { + var v = translate.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.clipExtent = function(_) { + var v = clipExtent.apply(m, arguments); + if (v === m) { + if (clipAuto = _ == null) { + var k = π * scale(), t = translate(); + clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]); + } + } else if (clipAuto) { + v = null; + } + return v; + }; + return m.clipExtent(null); + } + (d3.geo.mercator = function() { + return d3_geo_mercatorProjection(d3_geo_mercator); + }).raw = d3_geo_mercator; + var d3_geo_orthographic = d3_geo_azimuthal(function() { + return 1; + }, Math.asin); + (d3.geo.orthographic = function() { + return d3_geo_projection(d3_geo_orthographic); + }).raw = d3_geo_orthographic; + var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / (1 + cosλcosφ); + }, function(ρ) { + return 2 * Math.atan(ρ); + }); + (d3.geo.stereographic = function() { + return d3_geo_projection(d3_geo_stereographic); + }).raw = d3_geo_stereographic; + function d3_geo_transverseMercator(λ, φ) { + return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ]; + } + d3_geo_transverseMercator.invert = function(x, y) { + return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ]; + }; + (d3.geo.transverseMercator = function() { + var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate; + projection.center = function(_) { + return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]); + }; + projection.rotate = function(_) { + return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), + [ _[0], _[1], _[2] - 90 ]); + }; + return rotate([ 0, 0, 90 ]); + }).raw = d3_geo_transverseMercator; + d3.geom = {}; + function d3_geom_pointX(d) { + return d[0]; + } + function d3_geom_pointY(d) { + return d[1]; + } + d3.geom.hull = function(vertices) { + var x = d3_geom_pointX, y = d3_geom_pointY; + if (arguments.length) return hull(vertices); + function hull(data) { + if (data.length < 3) return []; + var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = []; + for (i = 0; i < n; i++) { + points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]); + } + points.sort(d3_geom_hullOrder); + for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]); + var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints); + var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = []; + for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]); + for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]); + return polygon; + } + hull.x = function(_) { + return arguments.length ? (x = _, hull) : x; + }; + hull.y = function(_) { + return arguments.length ? (y = _, hull) : y; + }; + return hull; + }; + function d3_geom_hullUpper(points) { + var n = points.length, hull = [ 0, 1 ], hs = 2; + for (var i = 2; i < n; i++) { + while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs; + hull[hs++] = i; + } + return hull.slice(0, hs); + } + function d3_geom_hullOrder(a, b) { + return a[0] - b[0] || a[1] - b[1]; + } + d3.geom.polygon = function(coordinates) { + d3_subclass(coordinates, d3_geom_polygonPrototype); + return coordinates; + }; + var d3_geom_polygonPrototype = d3.geom.polygon.prototype = []; + d3_geom_polygonPrototype.area = function() { + var i = -1, n = this.length, a, b = this[n - 1], area = 0; + while (++i < n) { + a = b; + b = this[i]; + area += a[1] * b[0] - a[0] * b[1]; + } + return area * .5; + }; + d3_geom_polygonPrototype.centroid = function(k) { + var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c; + if (!arguments.length) k = -1 / (6 * this.area()); + while (++i < n) { + a = b; + b = this[i]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [ x * k, y * k ]; + }; + d3_geom_polygonPrototype.clip = function(subject) { + var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = this[i]; + c = input[(m = input.length - closed) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + if (closed) subject.push(subject[0]); + a = b; + } + return subject; + }; + function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); + } + function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); + return [ x1 + ua * x21, y1 + ua * y21 ]; + } + function d3_geom_polygonClosed(coordinates) { + var a = coordinates[0], b = coordinates[coordinates.length - 1]; + return !(a[0] - b[0] || a[1] - b[1]); + } + var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = []; + function d3_geom_voronoiBeach() { + d3_geom_voronoiRedBlackNode(this); + this.edge = this.site = this.circle = null; + } + function d3_geom_voronoiCreateBeach(site) { + var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach(); + beach.site = site; + return beach; + } + function d3_geom_voronoiDetachBeach(beach) { + d3_geom_voronoiDetachCircle(beach); + d3_geom_voronoiBeaches.remove(beach); + d3_geom_voronoiBeachPool.push(beach); + d3_geom_voronoiRedBlackNode(beach); + } + function d3_geom_voronoiRemoveBeach(beach) { + var circle = beach.circle, x = circle.x, y = circle.cy, vertex = { + x: x, + y: y + }, previous = beach.P, next = beach.N, disappearing = [ beach ]; + d3_geom_voronoiDetachBeach(beach); + var lArc = previous; + while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) { + previous = lArc.P; + disappearing.unshift(lArc); + d3_geom_voronoiDetachBeach(lArc); + lArc = previous; + } + disappearing.unshift(lArc); + d3_geom_voronoiDetachCircle(lArc); + var rArc = next; + while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) { + next = rArc.N; + disappearing.push(rArc); + d3_geom_voronoiDetachBeach(rArc); + rArc = next; + } + disappearing.push(rArc); + d3_geom_voronoiDetachCircle(rArc); + var nArcs = disappearing.length, iArc; + for (iArc = 1; iArc < nArcs; ++iArc) { + rArc = disappearing[iArc]; + lArc = disappearing[iArc - 1]; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex); + } + lArc = disappearing[0]; + rArc = disappearing[nArcs - 1]; + rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiAddBeach(site) { + var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._; + while (node) { + dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x; + if (dxl > ε) node = node.L; else { + dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix); + if (dxr > ε) { + if (!node.R) { + lArc = node; + break; + } + node = node.R; + } else { + if (dxl > -ε) { + lArc = node.P; + rArc = node; + } else if (dxr > -ε) { + lArc = node; + rArc = node.N; + } else { + lArc = rArc = node; + } + break; + } + } + } + var newArc = d3_geom_voronoiCreateBeach(site); + d3_geom_voronoiBeaches.insert(lArc, newArc); + if (!lArc && !rArc) return; + if (lArc === rArc) { + d3_geom_voronoiDetachCircle(lArc); + rArc = d3_geom_voronoiCreateBeach(lArc.site); + d3_geom_voronoiBeaches.insert(newArc, rArc); + newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + return; + } + if (!rArc) { + newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + return; + } + d3_geom_voronoiDetachCircle(lArc); + d3_geom_voronoiDetachCircle(rArc); + var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = { + x: (cy * hb - by * hc) / d + ax, + y: (bx * hc - cx * hb) / d + ay + }; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex); + newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex); + rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiLeftBreakPoint(arc, directrix) { + var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix; + if (!pby2) return rfocx; + var lArc = arc.P; + if (!lArc) return -Infinity; + site = lArc.site; + var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix; + if (!plby2) return lfocx; + var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2; + if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx; + return (rfocx + lfocx) / 2; + } + function d3_geom_voronoiRightBreakPoint(arc, directrix) { + var rArc = arc.N; + if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix); + var site = arc.site; + return site.y === directrix ? site.x : Infinity; + } + function d3_geom_voronoiCell(site) { + this.site = site; + this.edges = []; + } + d3_geom_voronoiCell.prototype.prepare = function() { + var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge; + while (iHalfEdge--) { + edge = halfEdges[iHalfEdge].edge; + if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1); + } + halfEdges.sort(d3_geom_voronoiHalfEdgeOrder); + return halfEdges.length; + }; + function d3_geom_voronoiCloseCells(extent) { + var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end; + while (iCell--) { + cell = cells[iCell]; + if (!cell || !cell.prepare()) continue; + halfEdges = cell.edges; + nHalfEdges = halfEdges.length; + iHalfEdge = 0; + while (iHalfEdge < nHalfEdges) { + end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y; + start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y; + if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) { + halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? { + x: x0, + y: abs(x2 - x0) < ε ? y2 : y1 + } : abs(y3 - y1) < ε && x1 - x3 > ε ? { + x: abs(y2 - y1) < ε ? x2 : x1, + y: y1 + } : abs(x3 - x1) < ε && y3 - y0 > ε ? { + x: x1, + y: abs(x2 - x1) < ε ? y2 : y0 + } : abs(y3 - y0) < ε && x3 - x0 > ε ? { + x: abs(y2 - y0) < ε ? x2 : x0, + y: y0 + } : null), cell.site, null)); + ++nHalfEdges; + } + } + } + } + function d3_geom_voronoiHalfEdgeOrder(a, b) { + return b.angle - a.angle; + } + function d3_geom_voronoiCircle() { + d3_geom_voronoiRedBlackNode(this); + this.x = this.y = this.arc = this.site = this.cy = null; + } + function d3_geom_voronoiAttachCircle(arc) { + var lArc = arc.P, rArc = arc.N; + if (!lArc || !rArc) return; + var lSite = lArc.site, cSite = arc.site, rSite = rArc.site; + if (lSite === rSite) return; + var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by; + var d = 2 * (ax * cy - ay * cx); + if (d >= -ε2) return; + var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by; + var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle(); + circle.arc = arc; + circle.site = cSite; + circle.x = x + bx; + circle.y = cy + Math.sqrt(x * x + y * y); + circle.cy = cy; + arc.circle = circle; + var before = null, node = d3_geom_voronoiCircles._; + while (node) { + if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) { + if (node.L) node = node.L; else { + before = node.P; + break; + } + } else { + if (node.R) node = node.R; else { + before = node; + break; + } + } + } + d3_geom_voronoiCircles.insert(before, circle); + if (!before) d3_geom_voronoiFirstCircle = circle; + } + function d3_geom_voronoiDetachCircle(arc) { + var circle = arc.circle; + if (circle) { + if (!circle.P) d3_geom_voronoiFirstCircle = circle.N; + d3_geom_voronoiCircles.remove(circle); + d3_geom_voronoiCirclePool.push(circle); + d3_geom_voronoiRedBlackNode(circle); + arc.circle = null; + } + } + function d3_geom_voronoiClipEdges(extent) { + var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e; + while (i--) { + e = edges[i]; + if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) { + e.a = e.b = null; + edges.splice(i, 1); + } + } + } + function d3_geom_voronoiConnectEdge(edge, extent) { + var vb = edge.b; + if (vb) return true; + var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb; + if (ry === ly) { + if (fx < x0 || fx >= x1) return; + if (lx > rx) { + if (!va) va = { + x: fx, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: fx, + y: y1 + }; + } else { + if (!va) va = { + x: fx, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: fx, + y: y0 + }; + } + } else { + fm = (lx - rx) / (ry - ly); + fb = fy - fm * fx; + if (fm < -1 || fm > 1) { + if (lx > rx) { + if (!va) va = { + x: (y0 - fb) / fm, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: (y1 - fb) / fm, + y: y1 + }; + } else { + if (!va) va = { + x: (y1 - fb) / fm, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: (y0 - fb) / fm, + y: y0 + }; + } + } else { + if (ly < ry) { + if (!va) va = { + x: x0, + y: fm * x0 + fb + }; else if (va.x >= x1) return; + vb = { + x: x1, + y: fm * x1 + fb + }; + } else { + if (!va) va = { + x: x1, + y: fm * x1 + fb + }; else if (va.x < x0) return; + vb = { + x: x0, + y: fm * x0 + fb + }; + } + } + } + edge.a = va; + edge.b = vb; + return true; + } + function d3_geom_voronoiEdge(lSite, rSite) { + this.l = lSite; + this.r = rSite; + this.a = this.b = null; + } + function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, rSite); + d3_geom_voronoiEdges.push(edge); + if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va); + if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb); + d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite)); + d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite)); + return edge; + } + function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, null); + edge.a = va; + edge.b = vb; + d3_geom_voronoiEdges.push(edge); + return edge; + } + function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) { + if (!edge.a && !edge.b) { + edge.a = vertex; + edge.l = lSite; + edge.r = rSite; + } else if (edge.l === rSite) { + edge.b = vertex; + } else { + edge.a = vertex; + } + } + function d3_geom_voronoiHalfEdge(edge, lSite, rSite) { + var va = edge.a, vb = edge.b; + this.edge = edge; + this.site = lSite; + this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y); + } + d3_geom_voronoiHalfEdge.prototype = { + start: function() { + return this.edge.l === this.site ? this.edge.a : this.edge.b; + }, + end: function() { + return this.edge.l === this.site ? this.edge.b : this.edge.a; + } + }; + function d3_geom_voronoiRedBlackTree() { + this._ = null; + } + function d3_geom_voronoiRedBlackNode(node) { + node.U = node.C = node.L = node.R = node.P = node.N = null; + } + d3_geom_voronoiRedBlackTree.prototype = { + insert: function(after, node) { + var parent, grandpa, uncle; + if (after) { + node.P = after; + node.N = after.N; + if (after.N) after.N.P = node; + after.N = node; + if (after.R) { + after = after.R; + while (after.L) after = after.L; + after.L = node; + } else { + after.R = node; + } + parent = after; + } else if (this._) { + after = d3_geom_voronoiRedBlackFirst(this._); + node.P = null; + node.N = after; + after.P = after.L = node; + parent = after; + } else { + node.P = node.N = null; + this._ = node; + parent = null; + } + node.L = node.R = null; + node.U = parent; + node.C = true; + after = node; + while (parent && parent.C) { + grandpa = parent.U; + if (parent === grandpa.L) { + uncle = grandpa.R; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.R) { + d3_geom_voronoiRedBlackRotateLeft(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateRight(this, grandpa); + } + } else { + uncle = grandpa.L; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.L) { + d3_geom_voronoiRedBlackRotateRight(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, grandpa); + } + } + parent = after.U; + } + this._.C = false; + }, + remove: function(node) { + if (node.N) node.N.P = node.P; + if (node.P) node.P.N = node.N; + node.N = node.P = null; + var parent = node.U, sibling, left = node.L, right = node.R, next, red; + if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right); + if (parent) { + if (parent.L === node) parent.L = next; else parent.R = next; + } else { + this._ = next; + } + if (left && right) { + red = next.C; + next.C = node.C; + next.L = left; + left.U = next; + if (next !== right) { + parent = next.U; + next.U = node.U; + node = next.R; + parent.L = node; + next.R = right; + right.U = next; + } else { + next.U = parent; + parent = next; + node = next.R; + } + } else { + red = node.C; + node = next; + } + if (node) node.U = parent; + if (red) return; + if (node && node.C) { + node.C = false; + return; + } + do { + if (node === this._) break; + if (node === parent.L) { + sibling = parent.R; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + sibling = parent.R; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.R || !sibling.R.C) { + sibling.L.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateRight(this, sibling); + sibling = parent.R; + } + sibling.C = parent.C; + parent.C = sibling.R.C = false; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + node = this._; + break; + } + } else { + sibling = parent.L; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateRight(this, parent); + sibling = parent.L; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.L || !sibling.L.C) { + sibling.R.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, sibling); + sibling = parent.L; + } + sibling.C = parent.C; + parent.C = sibling.L.C = false; + d3_geom_voronoiRedBlackRotateRight(this, parent); + node = this._; + break; + } + } + sibling.C = true; + node = parent; + parent = parent.U; + } while (!node.C); + if (node) node.C = false; + } + }; + function d3_geom_voronoiRedBlackRotateLeft(tree, node) { + var p = node, q = node.R, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.R = q.L; + if (p.R) p.R.U = p; + q.L = p; + } + function d3_geom_voronoiRedBlackRotateRight(tree, node) { + var p = node, q = node.L, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.L = q.R; + if (p.L) p.L.U = p; + q.R = p; + } + function d3_geom_voronoiRedBlackFirst(node) { + while (node.L) node = node.L; + return node; + } + function d3_geom_voronoi(sites, bbox) { + var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle; + d3_geom_voronoiEdges = []; + d3_geom_voronoiCells = new Array(sites.length); + d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree(); + d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree(); + while (true) { + circle = d3_geom_voronoiFirstCircle; + if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) { + if (site.x !== x0 || site.y !== y0) { + d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site); + d3_geom_voronoiAddBeach(site); + x0 = site.x, y0 = site.y; + } + site = sites.pop(); + } else if (circle) { + d3_geom_voronoiRemoveBeach(circle.arc); + } else { + break; + } + } + if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox); + var diagram = { + cells: d3_geom_voronoiCells, + edges: d3_geom_voronoiEdges + }; + d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null; + return diagram; + } + function d3_geom_voronoiVertexOrder(a, b) { + return b.y - a.y || b.x - a.x; + } + d3.geom.voronoi = function(points) { + var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent; + if (points) return voronoi(points); + function voronoi(data) { + var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1]; + d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) { + var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) { + var s = e.start(); + return [ s.x, s.y ]; + }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : []; + polygon.point = data[i]; + }); + return polygons; + } + function sites(data) { + return data.map(function(d, i) { + return { + x: Math.round(fx(d, i) / ε) * ε, + y: Math.round(fy(d, i) / ε) * ε, + i: i + }; + }); + } + voronoi.links = function(data) { + return d3_geom_voronoi(sites(data)).edges.filter(function(edge) { + return edge.l && edge.r; + }).map(function(edge) { + return { + source: data[edge.l.i], + target: data[edge.r.i] + }; + }); + }; + voronoi.triangles = function(data) { + var triangles = []; + d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) { + var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l; + while (++j < m) { + e0 = e1; + s0 = s1; + e1 = edges[j].edge; + s1 = e1.l === site ? e1.r : e1.l; + if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) { + triangles.push([ data[i], data[s0.i], data[s1.i] ]); + } + } + }); + return triangles; + }; + voronoi.x = function(_) { + return arguments.length ? (fx = d3_functor(x = _), voronoi) : x; + }; + voronoi.y = function(_) { + return arguments.length ? (fy = d3_functor(y = _), voronoi) : y; + }; + voronoi.clipExtent = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent; + clipExtent = _ == null ? d3_geom_voronoiClipExtent : _; + return voronoi; + }; + voronoi.size = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1]; + return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]); + }; + return voronoi; + }; + var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ]; + function d3_geom_voronoiTriangleArea(a, b, c) { + return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y); + } + d3.geom.delaunay = function(vertices) { + return d3.geom.voronoi().triangles(vertices); + }; + d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var x = d3_geom_pointX, y = d3_geom_pointY, compat; + if (compat = arguments.length) { + x = d3_geom_quadtreeCompatX; + y = d3_geom_quadtreeCompatY; + if (compat === 3) { + y2 = y1; + x2 = x1; + y1 = x1 = 0; + } + return quadtree(points); + } + function quadtree(data) { + var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_; + if (x1 != null) { + x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2; + } else { + x2_ = y2_ = -(x1_ = y1_ = Infinity); + xs = [], ys = []; + n = data.length; + if (compat) for (i = 0; i < n; ++i) { + d = data[i]; + if (d.x < x1_) x1_ = d.x; + if (d.y < y1_) y1_ = d.y; + if (d.x > x2_) x2_ = d.x; + if (d.y > y2_) y2_ = d.y; + xs.push(d.x); + ys.push(d.y); + } else for (i = 0; i < n; ++i) { + var x_ = +fx(d = data[i], i), y_ = +fy(d, i); + if (x_ < x1_) x1_ = x_; + if (y_ < y1_) y1_ = y_; + if (x_ > x2_) x2_ = x_; + if (y_ > y2_) y2_ = y_; + xs.push(x_); + ys.push(y_); + } + } + var dx = x2_ - x1_, dy = y2_ - y1_; + if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy; + function insert(n, d, x, y, x1, y1, x2, y2) { + if (isNaN(x) || isNaN(y)) return; + if (n.leaf) { + var nx = n.x, ny = n.y; + if (nx != null) { + if (abs(nx - x) + abs(ny - y) < .01) { + insertChild(n, d, x, y, x1, y1, x2, y2); + } else { + var nPoint = n.point; + n.x = n.y = n.point = null; + insertChild(n, nPoint, nx, ny, x1, y1, x2, y2); + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } else { + n.x = x, n.y = y, n.point = d; + } + } else { + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } + function insertChild(n, d, x, y, x1, y1, x2, y2) { + var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right; + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + if (right) x1 = xm; else x2 = xm; + if (below) y1 = ym; else y2 = ym; + insert(n, d, x, y, x1, y1, x2, y2); + } + var root = d3_geom_quadtreeNode(); + root.add = function(d) { + insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_); + }; + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_); + }; + root.find = function(point) { + return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_); + }; + i = -1; + if (x1 == null) { + while (++i < n) { + insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_); + } + --i; + } else data.forEach(root.add); + xs = ys = data = d = null; + return root; + } + quadtree.x = function(_) { + return arguments.length ? (x = _, quadtree) : x; + }; + quadtree.y = function(_) { + return arguments.length ? (y = _, quadtree) : y; + }; + quadtree.extent = function(_) { + if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], + y2 = +_[1][1]; + return quadtree; + }; + quadtree.size = function(_) { + if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1]; + return quadtree; + }; + return quadtree; + }; + function d3_geom_quadtreeCompatX(d) { + return d.x; + } + function d3_geom_quadtreeCompatY(d) { + return d.y; + } + function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null, + x: null, + y: null + }; + } + function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } + } + function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) { + var minDistance2 = Infinity, closestPoint; + (function find(node, x1, y1, x2, y2) { + if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return; + if (point = node.point) { + var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy; + if (distance2 < minDistance2) { + var distance = Math.sqrt(minDistance2 = distance2); + x0 = x - distance, y0 = y - distance; + x3 = x + distance, y3 = y + distance; + closestPoint = point; + } + } + var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym; + for (var i = below << 1 | right, j = i + 4; i < j; ++i) { + if (node = children[i & 3]) switch (i & 3) { + case 0: + find(node, x1, y1, xm, ym); + break; + + case 1: + find(node, xm, y1, x2, ym); + break; + + case 2: + find(node, x1, ym, xm, y2); + break; + + case 3: + find(node, xm, ym, x2, y2); + break; + } + } + })(root, x0, y0, x3, y3); + return closestPoint; + } + d3.interpolateRgb = d3_interpolateRgb; + function d3_interpolateRgb(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; + return function(t) { + return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); + }; + } + d3.interpolateObject = d3_interpolateObject; + function d3_interpolateObject(a, b) { + var i = {}, c = {}, k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolate(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; + } + d3.interpolateNumber = d3_interpolateNumber; + function d3_interpolateNumber(a, b) { + a = +a, b = +b; + return function(t) { + return a * (1 - t) + b * t; + }; + } + d3.interpolateString = d3_interpolateString; + function d3_interpolateString(a, b) { + var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = []; + a = a + "", b = b + ""; + while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) { + if ((bs = bm.index) > bi) { + bs = b.slice(bi, bs); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + if ((am = am[0]) === (bm = bm[0])) { + if (s[i]) s[i] += bm; else s[++i] = bm; + } else { + s[++i] = null; + q.push({ + i: i, + x: d3_interpolateNumber(am, bm) + }); + } + bi = d3_interpolate_numberB.lastIndex; + } + if (bi < b.length) { + bs = b.slice(bi); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + return s.length < 2 ? q[0] ? (b = q[0].x, function(t) { + return b(t) + ""; + }) : function() { + return b; + } : (b = q.length, function(t) { + for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }); + } + var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g"); + d3.interpolate = d3_interpolate; + function d3_interpolate(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; + return f; + } + d3.interpolators = [ function(a, b) { + var t = typeof b; + return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b); + } ]; + d3.interpolateArray = d3_interpolateArray; + function d3_interpolateArray(a, b) { + var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; + for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i])); + for (;i < na; ++i) c[i] = a[i]; + for (;i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; + } + var d3_ease_default = function() { + return d3_identity; + }; + var d3_ease = d3.map({ + linear: d3_ease_default, + poly: d3_ease_poly, + quad: function() { + return d3_ease_quad; + }, + cubic: function() { + return d3_ease_cubic; + }, + sin: function() { + return d3_ease_sin; + }, + exp: function() { + return d3_ease_exp; + }, + circle: function() { + return d3_ease_circle; + }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { + return d3_ease_bounce; + } + }); + var d3_ease_mode = d3.map({ + "in": d3_identity, + out: d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { + return d3_ease_reflect(d3_ease_reverse(f)); + } + }); + d3.ease = function(name) { + var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in"; + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_identity; + return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1)))); + }; + function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; + } + function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; + } + function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); + }; + } + function d3_ease_quad(t) { + return t * t; + } + function d3_ease_cubic(t) { + return t * t * t; + } + function d3_ease_cubicInOut(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t, t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + } + function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + }; + } + function d3_ease_sin(t) { + return 1 - Math.cos(t * halfπ); + } + function d3_ease_exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = .45; + if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4; + return function(t) { + return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p); + }; + } + function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } + function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; + } + d3.interpolateHcl = d3_interpolateHcl; + function d3_interpolateHcl(a, b) { + a = d3.hcl(a); + b = d3.hcl(b); + var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; + if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; + }; + } + d3.interpolateHsl = d3_interpolateHsl; + function d3_interpolateHsl(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al; + if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + ""; + }; + } + d3.interpolateLab = d3_interpolateLab; + function d3_interpolateLab(a, b) { + a = d3.lab(a); + b = d3.lab(b); + var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; + return function(t) { + return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + } + d3.interpolateRound = d3_interpolateRound; + function d3_interpolateRound(a, b) { + b -= a; + return function(t) { + return Math.round(a + b * t); + }; + } + d3.transform = function(string) { + var g = d3_document.createElementNS(d3.ns.prefix.svg, "g"); + return (d3.transform = function(string) { + if (string != null) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + } + return new d3_transform(t ? t.matrix : d3_transformIdentity); + })(string); + }; + function d3_transform(m) { + var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees; + this.translate = [ m.e, m.f ]; + this.scale = [ kx, ky ]; + this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0; + } + d3_transform.prototype.toString = function() { + return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; + }; + function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; + } + function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; + } + var d3_transformIdentity = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0 + }; + d3.interpolateTransform = d3_interpolateTransform; + function d3_interpolateTransformPop(s) { + return s.length ? s.pop() + "," : ""; + } + function d3_interpolateTranslate(ta, tb, s, q) { + if (ta[0] !== tb[0] || ta[1] !== tb[1]) { + var i = s.push("translate(", null, ",", null, ")"); + q.push({ + i: i - 4, + x: d3_interpolateNumber(ta[0], tb[0]) + }, { + i: i - 2, + x: d3_interpolateNumber(ta[1], tb[1]) + }); + } else if (tb[0] || tb[1]) { + s.push("translate(" + tb + ")"); + } + } + function d3_interpolateRotate(ra, rb, s, q) { + if (ra !== rb) { + if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; + q.push({ + i: s.push(d3_interpolateTransformPop(s) + "rotate(", null, ")") - 2, + x: d3_interpolateNumber(ra, rb) + }); + } else if (rb) { + s.push(d3_interpolateTransformPop(s) + "rotate(" + rb + ")"); + } + } + function d3_interpolateSkew(wa, wb, s, q) { + if (wa !== wb) { + q.push({ + i: s.push(d3_interpolateTransformPop(s) + "skewX(", null, ")") - 2, + x: d3_interpolateNumber(wa, wb) + }); + } else if (wb) { + s.push(d3_interpolateTransformPop(s) + "skewX(" + wb + ")"); + } + } + function d3_interpolateScale(ka, kb, s, q) { + if (ka[0] !== kb[0] || ka[1] !== kb[1]) { + var i = s.push(d3_interpolateTransformPop(s) + "scale(", null, ",", null, ")"); + q.push({ + i: i - 4, + x: d3_interpolateNumber(ka[0], kb[0]) + }, { + i: i - 2, + x: d3_interpolateNumber(ka[1], kb[1]) + }); + } else if (kb[0] !== 1 || kb[1] !== 1) { + s.push(d3_interpolateTransformPop(s) + "scale(" + kb + ")"); + } + } + function d3_interpolateTransform(a, b) { + var s = [], q = []; + a = d3.transform(a), b = d3.transform(b); + d3_interpolateTranslate(a.translate, b.translate, s, q); + d3_interpolateRotate(a.rotate, b.rotate, s, q); + d3_interpolateSkew(a.skew, b.skew, s, q); + d3_interpolateScale(a.scale, b.scale, s, q); + a = b = null; + return function(t) { + var i = -1, n = q.length, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + } + function d3_uninterpolateNumber(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return (x - a) / b; + }; + } + function d3_uninterpolateClamp(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return Math.max(0, Math.min(1, (x - a) / b)); + }; + } + d3.layout = {}; + d3.layout.bundle = function() { + return function(links) { + var paths = [], i = -1, n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; + }; + function d3_layout_bundlePath(link) { + var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; + } + function d3_layout_bundleAncestors(node) { + var ancestors = [], parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; + } + function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; + } + d3.layout.chord = function() { + var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; + function relayout() { + var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; + chords = []; + groups = []; + k = 0, i = -1; + while (++i < n) { + x = 0, j = -1; + while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; + } + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + k = (τ - padding * n) / k; + x = 0, i = -1; + while (++i < n) { + x0 = x, j = -1; + while (++j < n) { + var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: a0, + endAngle: a1, + value: v + }; + } + groups[di] = { + index: di, + startAngle: x0, + endAngle: x, + value: (x - x0) / k + }; + x += padding; + } + i = -1; + while (++i < n) { + j = i - 1; + while (++j < n) { + var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value ? { + source: target, + target: source + } : { + source: source, + target: target + }); + } + } + } + if (sortChords) resort(); + } + function resort() { + chords.sort(function(a, b) { + return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); + }); + } + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + return chord; + }; + d3.layout.force = function() { + var force = {}, event = d3.dispatch("start", "tick", "end"), timer, size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges; + function repulse(node) { + return function(quad, x1, _, x2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy; + if (dw * dw / theta2 < dn) { + if (dn < chargeDistance2) { + var k = quad.charge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + return true; + } + if (quad.point && dn && dn < chargeDistance2) { + var k = quad.pointCharge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + } + return !quad.charge; + }; + } + force.tick = function() { + if ((alpha *= .99) < .005) { + timer = null; + event.end({ + type: "end", + alpha: alpha = 0 + }); + return true; + } + var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = x * x + y * y) { + l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; + x *= l; + y *= l; + t.x -= x * (k = s.weight + t.weight ? s.weight / (s.weight + t.weight) : .5); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; + } + } + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; + if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } + } + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; + while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } + } + i = -1; + while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + event.tick({ + type: "tick", + alpha: alpha + }); + }; + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + force.linkDistance = function(x) { + if (!arguments.length) return linkDistance; + linkDistance = typeof x === "function" ? x : +x; + return force; + }; + force.distance = force.linkDistance; + force.linkStrength = function(x) { + if (!arguments.length) return linkStrength; + linkStrength = typeof x === "function" ? x : +x; + return force; + }; + force.friction = function(x) { + if (!arguments.length) return friction; + friction = +x; + return force; + }; + force.charge = function(x) { + if (!arguments.length) return charge; + charge = typeof x === "function" ? x : +x; + return force; + }; + force.chargeDistance = function(x) { + if (!arguments.length) return Math.sqrt(chargeDistance2); + chargeDistance2 = x * x; + return force; + }; + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = +x; + return force; + }; + force.theta = function(x) { + if (!arguments.length) return Math.sqrt(theta2); + theta2 = x * x; + return force; + }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + x = +x; + if (alpha) { + if (x > 0) { + alpha = x; + } else { + timer.c = null, timer.t = NaN, timer = null; + event.start({ + type: "end", + alpha: alpha = 0 + }); + } + } else if (x > 0) { + event.start({ + type: "start", + alpha: alpha = x + }); + timer = d3_timer(force.tick); + } + return force; + }; + force.start = function() { + var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + o.weight = 0; + } + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + ++o.source.weight; + ++o.target.weight; + } + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + distances = []; + if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance; + strengths = []; + if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength; + charges = []; + if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge; + function position(dimension, size) { + if (!neighbors) { + neighbors = new Array(n); + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + var candidates = neighbors[i], j = -1, l = candidates.length, x; + while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x; + return Math.random() * size; + } + return force.resume(); + }; + force.resume = function() { + return force.alpha(.1); + }; + force.stop = function() { + return force.alpha(0); + }; + force.drag = function() { + if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend); + if (!arguments.length) return drag; + this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); + }; + function dragmove(d) { + d.px = d3.event.x, d.py = d3.event.y; + force.resume(); + } + return d3.rebind(force, event, "on"); + }; + function d3_layout_forceDragstart(d) { + d.fixed |= 2; + } + function d3_layout_forceDragend(d) { + d.fixed &= ~6; + } + function d3_layout_forceMouseover(d) { + d.fixed |= 4; + d.px = d.x, d.py = d.y; + } + function d3_layout_forceMouseout(d) { + d.fixed &= ~4; + } + function d3_layout_forceAccumulate(quad, alpha, charges) { + var cx = 0, cy = 0; + quad.charge = 0; + if (!quad.leaf) { + var nodes = quad.nodes, n = nodes.length, i = -1, c; + while (++i < n) { + c = nodes[i]; + if (c == null) continue; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; + } + } + if (quad.point) { + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; + } + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; + } + var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity; + d3.layout.hierarchy = function() { + var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; + function hierarchy(root) { + var stack = [ root ], nodes = [], node; + root.depth = 0; + while ((node = stack.pop()) != null) { + nodes.push(node); + if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) { + var n, childs, child; + while (--n >= 0) { + stack.push(child = childs[n]); + child.parent = node; + child.depth = node.depth + 1; + } + if (value) node.value = 0; + node.children = childs; + } else { + if (value) node.value = +value.call(hierarchy, node, node.depth) || 0; + delete node.children; + } + } + d3_layout_hierarchyVisitAfter(root, function(node) { + var childs, parent; + if (sort && (childs = node.children)) childs.sort(sort); + if (value && (parent = node.parent)) parent.value += node.value; + }); + return nodes; + } + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + hierarchy.revalue = function(root) { + if (value) { + d3_layout_hierarchyVisitBefore(root, function(node) { + if (node.children) node.value = 0; + }); + d3_layout_hierarchyVisitAfter(root, function(node) { + var parent; + if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0; + if (parent = node.parent) parent.value += node.value; + }); + } + return root; + }; + return hierarchy; + }; + function d3_layout_hierarchyRebind(object, hierarchy) { + d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; + object.links = d3_layout_hierarchyLinks; + return object; + } + function d3_layout_hierarchyVisitBefore(node, callback) { + var nodes = [ node ]; + while ((node = nodes.pop()) != null) { + callback(node); + if ((children = node.children) && (n = children.length)) { + var n, children; + while (--n >= 0) nodes.push(children[n]); + } + } + } + function d3_layout_hierarchyVisitAfter(node, callback) { + var nodes = [ node ], nodes2 = []; + while ((node = nodes.pop()) != null) { + nodes2.push(node); + if ((children = node.children) && (n = children.length)) { + var i = -1, n, children; + while (++i < n) nodes.push(children[i]); + } + } + while ((node = nodes2.pop()) != null) { + callback(node); + } + } + function d3_layout_hierarchyChildren(d) { + return d.children; + } + function d3_layout_hierarchyValue(d) { + return d.value; + } + function d3_layout_hierarchySort(a, b) { + return b.value - a.value; + } + function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return { + source: parent, + target: child + }; + }); + })); + } + d3.layout.partition = function() { + var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children && (n = children.length)) { + var i = -1, n, c, d; + dx = node.value ? dx / node.value : 0; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + function depth(node) { + var children = node.children, d = 0; + if (children && (n = children.length)) { + var i = -1, n; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + return d3_layout_hierarchyRebind(partition, hierarchy); + }; + d3.layout.pie = function() { + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0; + function pie(data) { + var n = data.length, values = data.map(function(d, i) { + return +value.call(pie, d, i); + }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), sum = d3.sum(values), k = sum ? (da - n * pa) / sum : 0, index = d3.range(n), arcs = [], v; + if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { + return values[j] - values[i]; + } : function(i, j) { + return sort(data[i], data[j]); + }); + index.forEach(function(i) { + arcs[i] = { + data: data[i], + value: v = values[i], + startAngle: a, + endAngle: a += v * k + pa, + padAngle: p + }; + }); + return arcs; + } + pie.value = function(_) { + if (!arguments.length) return value; + value = _; + return pie; + }; + pie.sort = function(_) { + if (!arguments.length) return sort; + sort = _; + return pie; + }; + pie.startAngle = function(_) { + if (!arguments.length) return startAngle; + startAngle = _; + return pie; + }; + pie.endAngle = function(_) { + if (!arguments.length) return endAngle; + endAngle = _; + return pie; + }; + pie.padAngle = function(_) { + if (!arguments.length) return padAngle; + padAngle = _; + return pie; + }; + return pie; + }; + var d3_layout_pieSortByValue = {}; + d3.layout.stack = function() { + var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; + function stack(data, index) { + if (!(n = data.length)) return data; + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + var points = series.map(function(d) { + return d.map(function(v, i) { + return [ x.call(stack, v, i), y.call(stack, v, i) ]; + }); + }); + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + var offsets = offset.call(stack, points, index); + var m = series[0].length, n, i, j, o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + return data; + } + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; + return stack; + }; + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; + return stack; + }; + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + return stack; + }; + function d3_layout_stackX(d) { + return d.x; + } + function d3_layout_stackY(d) { + return d.y; + } + function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; + } + var d3_layout_stackOrders = d3.map({ + "inside-out": function(data) { + var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { + return max[a] - max[b]; + }), top = 0, bottom = 0, tops = [], bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + reverse: function(data) { + return d3.range(data.length).reverse(); + }, + "default": d3_layout_stackOrderDefault + }); + var d3_layout_stackOffsets = d3.map({ + silhouette: function(data) { + var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + wiggle: function(data) { + var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + expand: function(data) { + var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + zero: d3_layout_stackOffsetZero + }); + function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); + } + function d3_layout_stackOffsetZero(data) { + var j = -1, m = data[0].length, y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + function d3_layout_stackMaxIndex(array) { + var i = 1, j = 0, v = array[0][1], k, n = array.length; + for (;i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; + } + function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); + } + function d3_layout_stackSum(p, d) { + return p + d[1]; + } + d3.layout.histogram = function() { + var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; + function histogram(data, i) { + var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + if (m > 0) { + i = -1; + while (++i < n) { + x = values[i]; + if (x >= range[0] && x <= range[1]) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + } + return bins; + } + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3_functor(x); + return histogram; + }; + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" ? function(range) { + return d3_layout_histogramBinFixed(range, x); + } : d3_functor(x); + return histogram; + }; + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + return histogram; + }; + function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); + } + function d3_layout_histogramBinFixed(range, n) { + var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; + while (++x <= n) f[x] = m * x + b; + return f; + } + function d3_layout_histogramRange(values) { + return [ d3.min(values), d3.max(values) ]; + } + d3.layout.pack = function() { + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius; + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() { + return radius; + }; + root.x = root.y = 0; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r = +r(d.value); + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + if (padding) { + var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r += dr; + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r -= dr; + }); + } + d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h)); + return nodes; + } + pack.size = function(_) { + if (!arguments.length) return size; + size = _; + return pack; + }; + pack.radius = function(_) { + if (!arguments.length) return radius; + radius = _ == null || typeof _ === "function" ? _ : +_; + return pack; + }; + pack.padding = function(_) { + if (!arguments.length) return padding; + padding = +_; + return pack; + }; + return d3_layout_hierarchyRebind(pack, hierarchy); + }; + function d3_layout_packSort(a, b) { + return a.value - b.value; + } + function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; + } + function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; + } + function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; + return .999 * dr * dr > dx * dx + dy * dy; + } + function d3_layout_packSiblings(node) { + if (!(nodes = node.children) || !(n = nodes.length)) return; + var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + nodes.forEach(d3_layout_packLink); + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + for (i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + break; + } + } + } + if (isect) { + if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); + i--; + } else { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } + } + } + } + var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; + for (i = 0; i < n; i++) { + c = nodes[i]; + c.x -= cx; + c.y -= cy; + cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); + } + node.r = cr; + nodes.forEach(d3_layout_packUnlink); + } + function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; + } + function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; + } + function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = x += k * node.x; + node.y = y += k * node.y; + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } + } + function d3_layout_packPlace(a, b, c) { + var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, dc = dx * dx + dy * dy; + da *= da; + db *= db; + var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } + } + d3.layout.tree = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null; + function tree(d, i) { + var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0); + d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z; + d3_layout_hierarchyVisitBefore(root1, secondWalk); + if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else { + var left = root0, right = root0, bottom = root0; + d3_layout_hierarchyVisitBefore(root0, function(node) { + if (node.x < left.x) left = node; + if (node.x > right.x) right = node; + if (node.depth > bottom.depth) bottom = node; + }); + var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1); + d3_layout_hierarchyVisitBefore(root0, function(node) { + node.x = (node.x + tx) * kx; + node.y = node.depth * ky; + }); + } + return nodes; + } + function wrapTree(root0) { + var root1 = { + A: null, + children: [ root0 ] + }, queue = [ root1 ], node1; + while ((node1 = queue.pop()) != null) { + for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) { + queue.push((children[i] = child = { + _: children[i], + parent: node1, + children: (child = children[i].children) && child.slice() || [], + A: null, + a: null, + z: 0, + m: 0, + c: 0, + s: 0, + t: null, + i: i + }).a = child); + } + } + return root1.children[0]; + } + function firstWalk(v) { + var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null; + if (children.length) { + d3_layout_treeShift(v); + var midpoint = (children[0].z + children[children.length - 1].z) / 2; + if (w) { + v.z = w.z + separation(v._, w._); + v.m = v.z - midpoint; + } else { + v.z = midpoint; + } + } else if (w) { + v.z = w.z + separation(v._, w._); + } + v.parent.A = apportion(v, w, v.parent.A || siblings[0]); + } + function secondWalk(v) { + v._.x = v.z + v.parent.m; + v.m += v.parent.m; + } + function apportion(v, w, ancestor) { + if (w) { + var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop.a = v; + shift = vim.z + sim - vip.z - sip + separation(vim._, vip._); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift); + sip += shift; + sop += shift; + } + sim += vim.m; + sip += vip.m; + som += vom.m; + sop += vop.m; + } + if (vim && !d3_layout_treeRight(vop)) { + vop.t = vim; + vop.m += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom.t = vip; + vom.m += sip - som; + ancestor = v; + } + } + return ancestor; + } + function sizeNode(node) { + node.x *= size[0]; + node.y = node.depth * size[1]; + } + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null ? sizeNode : null; + return tree; + }; + tree.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) == null ? null : sizeNode; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(v) { + var children = v.children; + return children.length ? children[0] : v.t; + } + function d3_layout_treeRight(v) { + var children = v.children, n; + return (n = children.length) ? children[n - 1] : v.t; + } + function d3_layout_treeMove(wm, wp, shift) { + var change = shift / (wp.i - wm.i); + wp.c -= change; + wp.s += shift; + wm.c += change; + wp.z += shift; + wp.m += shift; + } + function d3_layout_treeShift(v) { + var shift = 0, change = 0, children = v.children, i = children.length, w; + while (--i >= 0) { + w = children[i]; + w.z += shift; + w.m += shift; + shift += w.s + (change += w.c); + } + } + function d3_layout_treeAncestor(vim, v, ancestor) { + return vim.a.parent === v.parent ? vim.a : ancestor; + } + d3.layout.cluster = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false; + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0; + d3_layout_hierarchyVisitAfter(root, function(node) { + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; + d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) { + node.x = (node.x - root.x) * size[0]; + node.y = (root.y - node.y) * size[1]; + } : function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; + }); + return nodes; + } + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + cluster.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null; + return cluster; + }; + cluster.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) != null; + return cluster; + }; + return d3_layout_hierarchyRebind(cluster, hierarchy); + }; + function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); + } + function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; + } + function d3_layout_clusterLeft(node) { + var children = node.children; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; + } + function d3_layout_clusterRight(node) { + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; + } + d3.layout.treemap = function() { + var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5)); + function scale(children, k) { + var i = -1, n = children.length, child, area; + while (++i < n) { + area = (child = children[i]).value * (k < 0 ? 0 : k); + child.area = isNaN(area) || area <= 0 ? 0 : area; + } + } + function squarify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if (mode !== "squarify" || (score = worst(row, u)) <= best) { + remaining.pop(); + best = score; + } else { + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + children.forEach(squarify); + } + } + function stickify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), remaining = children.slice(), child, row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } + } + children.forEach(stickify); + } + } + function worst(row, u) { + var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; + while (++i < n) { + if (!(r = row[i].area)) continue; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; + } + function position(row, u, rect, flush) { + var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; + if (u == rect.dx) { + if (flush || v > rect.dy) v = rect.dy; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); + } + o.z = true; + o.dx += rect.x + rect.dx - x; + rect.y += v; + rect.dy -= v; + } else { + if (flush || v > rect.dx) v = rect.dx; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); + } + o.z = false; + o.dy += rect.y + rect.dy - y; + rect.x += v; + rect.dx -= v; + } + } + function treemap(d) { + var nodes = stickies || hierarchy(d), root = nodes[0]; + root.x = root.y = 0; + if (root.value) root.dx = size[0], root.dy = size[1]; else root.dx = root.dy = 0; + if (stickies) hierarchy.revalue(root); + scale([ root ], root.dx * root.dy / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + treemap.padding = function(x) { + if (!arguments.length) return padding; + function padFunction(node) { + var p = x.call(treemap, node, node.depth); + return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); + } + function padConstant(node) { + return d3_layout_treemapPad(node, x); + } + var type; + pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], + padConstant) : padConstant; + return treemap; + }; + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + treemap.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return treemap; + }; + return d3_layout_hierarchyRebind(treemap, hierarchy); + }; + function d3_layout_treemapPadNull(node) { + return { + x: node.x, + y: node.y, + dx: node.dx, + dy: node.dy + }; + } + function d3_layout_treemapPad(node, padding) { + var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; + if (dx < 0) { + x += dx / 2; + dx = 0; + } + if (dy < 0) { + y += dy / 2; + dy = 0; + } + return { + x: x, + y: y, + dx: dx, + dy: dy + }; + } + d3.random = { + normal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); + }; + }, + logNormal: function() { + var random = d3.random.normal.apply(d3, arguments); + return function() { + return Math.exp(random()); + }; + }, + bates: function(m) { + var random = d3.random.irwinHall(m); + return function() { + return random() / m; + }; + }, + irwinHall: function(m) { + return function() { + for (var s = 0, j = 0; j < m; j++) s += Math.random(); + return s; + }; + } + }; + d3.scale = {}; + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; + } + function d3_scale_nice(domain, nice) { + var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; + if (x1 < x0) { + dx = i0, i0 = i1, i1 = dx; + dx = x0, x0 = x1, x1 = dx; + } + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + return domain; + } + function d3_scale_niceStep(step) { + return step ? { + floor: function(x) { + return Math.floor(x / step) * step; + }, + ceil: function(x) { + return Math.ceil(x / step) * step; + } + } : d3_scale_niceIdentity; + } + var d3_scale_niceIdentity = { + floor: d3_identity, + ceil: d3_identity + }; + function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; + if (domain[k] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + while (++j <= k) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + return function(x) { + var j = d3.bisect(domain, x, 1, k) - 1; + return i[j](u[j](x)); + }; + } + d3.scale.linear = function() { + return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false); + }; + function d3_scale_linear(domain, range, interpolate, clamp) { + var output, input; + function rescale() { + var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3_interpolate); + return scale; + } + function scale(x) { + return output(x); + } + scale.invert = function(y) { + return input(y); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3_interpolateRound); + }; + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + d3_scale_linearNice(domain, m); + return rescale(); + }; + scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); + } + function d3_scale_linearRebind(scale, linear) { + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_scale_linearNice(domain, m) { + return d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2])); + } + function d3_scale_linearTickRange(domain, m) { + if (m == null) m = 10; + var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; + if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; + extent[2] = step; + return extent; + } + function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); + } + function d3_scale_linearTickFormat(domain, m, format) { + var range = d3_scale_linearTickRange(domain, m); + if (format) { + var match = d3_format_re.exec(format); + match.shift(); + if (match[8] === "s") { + var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1]))); + if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2])); + match[8] = "f"; + format = d3.format(match.join("")); + return function(d) { + return format(prefix.scale(d)) + prefix.symbol; + }; + } + if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range); + format = match.join(""); + } else { + format = ",." + d3_scale_linearPrecision(range[2]) + "f"; + } + return d3.format(format); + } + var d3_scale_linearFormatSignificant = { + s: 1, + g: 1, + p: 1, + r: 1, + e: 1 + }; + function d3_scale_linearPrecision(value) { + return -Math.floor(Math.log(value) / Math.LN10 + .01); + } + function d3_scale_linearFormatPrecision(type, range) { + var p = d3_scale_linearPrecision(range[2]); + return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2; + } + d3.scale.log = function() { + return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]); + }; + function d3_scale_log(linear, base, positive, domain) { + function log(x) { + return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base); + } + function pow(x) { + return positive ? Math.pow(base, x) : -Math.pow(base, -x); + } + function scale(x) { + return linear(log(x)); + } + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + positive = x[0] >= 0; + linear.domain((domain = x.map(Number)).map(log)); + return scale; + }; + scale.base = function(_) { + if (!arguments.length) return base; + base = +_; + linear.domain(domain.map(log)); + return scale; + }; + scale.nice = function() { + var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative); + linear.domain(niced); + domain = niced.map(pow); + return scale; + }; + scale.ticks = function() { + var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base; + if (isFinite(j - i)) { + if (positive) { + for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k); + ticks.push(pow(i)); + } else { + ticks.push(pow(i)); + for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k); + } + for (i = 0; ticks[i] < u; i++) {} + for (j = ticks.length; ticks[j - 1] > v; j--) {} + ticks = ticks.slice(i, j); + } + return ticks; + }; + scale.tickFormat = function(n, format) { + if (!arguments.length) return d3_scale_logFormat; + if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format); + var k = Math.max(.1, n / scale.ticks().length), f = positive ? (e = 1e-12, Math.ceil) : (e = -1e-12, + Math.floor), e; + return function(d) { + return d / pow(f(log(d) + e)) <= k ? format(d) : ""; + }; + }; + scale.copy = function() { + return d3_scale_log(linear.copy(), base, positive, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = { + floor: function(x) { + return -Math.ceil(-x); + }, + ceil: function(x) { + return -Math.floor(-x); + } + }; + d3.scale.pow = function() { + return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]); + }; + function d3_scale_pow(linear, exponent, domain) { + var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); + function scale(x) { + return linear(powp(x)); + } + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + linear.domain((domain = x.map(Number)).map(powp)); + return scale; + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + return scale.domain(d3_scale_linearNice(domain, m)); + }; + scale.exponent = function(x) { + if (!arguments.length) return exponent; + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); + linear.domain(domain.map(powp)); + return scale; + }; + scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_powPow(e) { + return function(x) { + return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); + }; + } + d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); + }; + d3.scale.ordinal = function() { + return d3_scale_ordinal([], { + t: "range", + a: [ [] ] + }); + }; + function d3_scale_ordinal(domain, ranger) { + var index, range, rangeBand; + function scale(x) { + return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length]; + } + function steps(start, step) { + return d3.range(domain.length).map(function(i) { + return start + step * i; + }); + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = []; + index = new d3_Map(); + var i = -1, n = x.length, xi; + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); + return scale[ranger.t].apply(scale, ranger.a); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rangeBand = 0; + ranger = { + t: "range", + a: arguments + }; + return scale; + }; + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, + 0) : (stop - start) / (domain.length - 1 + padding); + range = steps(start + step * padding / 2, step); + rangeBand = 0; + ranger = { + t: "rangePoints", + a: arguments + }; + return scale; + }; + scale.rangeRoundPoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), + 0) : (stop - start) / (domain.length - 1 + padding) | 0; + range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step); + rangeBand = 0; + ranger = { + t: "rangeRoundPoints", + a: arguments + }; + return scale; + }; + scale.rangeBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); + range = steps(start + step * outerPadding, step); + if (reverse) range.reverse(); + rangeBand = step * (1 - padding); + ranger = { + t: "rangeBands", + a: arguments + }; + return scale; + }; + scale.rangeRoundBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)); + range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step); + if (reverse) range.reverse(); + rangeBand = Math.round(step * (1 - padding)); + ranger = { + t: "rangeRoundBands", + a: arguments + }; + return scale; + }; + scale.rangeBand = function() { + return rangeBand; + }; + scale.rangeExtent = function() { + return d3_scaleExtent(ranger.a[0]); + }; + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + return scale.domain(domain); + } + d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); + }; + d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); + }; + d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); + }; + d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); + }; + var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString); + var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString); + var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString); + var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString); + d3.scale.quantile = function() { + return d3_scale_quantile([], []); + }; + function d3_scale_quantile(domain, range) { + var thresholds; + function rescale() { + var k = 0, q = range.length; + thresholds = []; + while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; + } + function scale(x) { + if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)]; + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.quantiles = function() { + return thresholds; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ]; + }; + scale.copy = function() { + return d3_scale_quantile(domain, range); + }; + return rescale(); + } + d3.scale.quantize = function() { + return d3_scale_quantize(0, 1, [ 0, 1 ]); + }; + function d3_scale_quantize(x0, x1, range) { + var kx, i; + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + function rescale() { + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + } + scale.domain = function(x) { + if (!arguments.length) return [ x0, x1 ]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + y = y < 0 ? NaN : y / kx + x0; + return [ y, y + 1 / kx ]; + }; + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); + }; + return rescale(); + } + d3.scale.threshold = function() { + return d3_scale_threshold([ .5 ], [ 0, 1 ]); + }; + function d3_scale_threshold(domain, range) { + function scale(x) { + if (x <= x) return range[d3.bisect(domain, x)]; + } + scale.domain = function(_) { + if (!arguments.length) return domain; + domain = _; + return scale; + }; + scale.range = function(_) { + if (!arguments.length) return range; + range = _; + return scale; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return [ domain[y - 1], domain[y] ]; + }; + scale.copy = function() { + return d3_scale_threshold(domain, range); + }; + return scale; + } + d3.scale.identity = function() { + return d3_scale_identity([ 0, 1 ]); + }; + function d3_scale_identity(domain) { + function identity(x) { + return +x; + } + identity.invert = identity; + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + identity.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + identity.copy = function() { + return d3_scale_identity(domain); + }; + return identity; + } + d3.svg = {}; + function d3_zero() { + return 0; + } + d3.svg.arc = function() { + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle; + function arc() { + var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1; + if (r1 < r0) rc = r1, r1 = r0, r0 = rc; + if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z"; + var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = []; + if (ap = (+padAngle.apply(this, arguments) || 0) / 2) { + rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments); + if (!cw) p1 *= -1; + if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap)); + if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap)); + } + if (r1) { + x0 = r1 * Math.cos(a0 + p1); + y0 = r1 * Math.sin(a0 + p1); + x1 = r1 * Math.cos(a1 - p1); + y1 = r1 * Math.sin(a1 - p1); + var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1; + if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) { + var h1 = (a0 + a1) / 2; + x0 = r1 * Math.cos(h1); + y0 = r1 * Math.sin(h1); + x1 = y1 = null; + } + } else { + x0 = y0 = 0; + } + if (r0) { + x2 = r0 * Math.cos(a1 - p0); + y2 = r0 * Math.sin(a1 - p0); + x3 = r0 * Math.cos(a0 + p0); + y3 = r0 * Math.sin(a0 + p0); + var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1; + if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) { + var h0 = (a0 + a1) / 2; + x2 = r0 * Math.cos(h0); + y2 = r0 * Math.sin(h0); + x3 = y3 = null; + } + } else { + x2 = y2 = 0; + } + if (da > ε && (rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) { + cr = r0 < r1 ^ cw ? 0 : 1; + var rc1 = rc, rc0 = rc; + if (da < π) { + var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]); + rc0 = Math.min(rc, (r0 - lc) / (kc - 1)); + rc1 = Math.min(rc, (r1 - lc) / (kc + 1)); + } + if (x1 != null) { + var t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw); + if (rc === rc1) { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]); + } else { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]); + } + } else { + path.push("M", x0, ",", y0); + } + if (x3 != null) { + var t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw); + if (rc === rc0) { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } else { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } + } else { + path.push("L", x2, ",", y2); + } + } else { + path.push("M", x0, ",", y0); + if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1); + path.push("L", x2, ",", y2); + if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3); + } + path.push("Z"); + return path.join(""); + } + function circleSegment(r1, cw) { + return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1; + } + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3_functor(v); + return arc; + }; + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3_functor(v); + return arc; + }; + arc.cornerRadius = function(v) { + if (!arguments.length) return cornerRadius; + cornerRadius = d3_functor(v); + return arc; + }; + arc.padRadius = function(v) { + if (!arguments.length) return padRadius; + padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v); + return arc; + }; + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return arc; + }; + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return arc; + }; + arc.padAngle = function(v) { + if (!arguments.length) return padAngle; + padAngle = d3_functor(v); + return arc; + }; + arc.centroid = function() { + var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ; + return [ Math.cos(a) * r, Math.sin(a) * r ]; + }; + return arc; + }; + var d3_svg_arcAuto = "auto"; + function d3_svg_arcInnerRadius(d) { + return d.innerRadius; + } + function d3_svg_arcOuterRadius(d) { + return d.outerRadius; + } + function d3_svg_arcStartAngle(d) { + return d.startAngle; + } + function d3_svg_arcEndAngle(d) { + return d.endAngle; + } + function d3_svg_arcPadAngle(d) { + return d && d.padAngle; + } + function d3_svg_arcSweep(x0, y0, x1, y1) { + return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1; + } + function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) { + var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3; + if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; + return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ]; + } + function d3_svg_line(projection) { + var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; + function line(data) { + var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); + function segment() { + segments.push("M", interpolate(projection(points), tension)); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); + } else if (points.length) { + segment(); + points = []; + } + } + if (points.length) segment(); + return segments.length ? segments.join("") : null; + } + line.x = function(_) { + if (!arguments.length) return x; + x = _; + return line; + }; + line.y = function(_) { + if (!arguments.length) return y; + y = _; + return line; + }; + line.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return line; + }; + line.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + return line; + }; + line.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return line; + }; + return line; + } + d3.svg.line = function() { + return d3_svg_line(d3_identity); + }; + var d3_svg_lineInterpolators = d3.map({ + linear: d3_svg_lineLinear, + "linear-closed": d3_svg_lineLinearClosed, + step: d3_svg_lineStep, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + basis: d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + bundle: d3_svg_lineBundle, + cardinal: d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + monotone: d3_svg_lineMonotone + }); + d3_svg_lineInterpolators.forEach(function(key, value) { + value.key = key; + value.closed = /-closed$/.test(key); + }); + function d3_svg_lineLinear(points) { + return points.length > 1 ? points.join("L") : points + "Z"; + } + function d3_svg_lineLinearClosed(points) { + return points.join("L") + "Z"; + } + function d3_svg_lineStep(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]); + if (n > 1) path.push("H", p[0]); + return path.join(""); + } + function d3_svg_lineStepBefore(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); + } + function d3_svg_lineStepAfter(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); + } + function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), + points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); + } + function d3_svg_lineCardinal(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { + return d3_svg_lineLinear(points); + } + var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + } + } + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; + } + return path; + } + function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); + } + return tangents; + } + function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + points.push(points[n - 1]); + while (++i <= n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + points.pop(); + path.push("L", pi); + return path.join(""); + } + function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisClosed(points) { + var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + --i; + while (++i < m) { + pi = points[i % n]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBundle(points, tension) { + var n = points.length - 1; + if (n) { + var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + } + return d3_svg_lineBasis(points); + } + function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; + function d3_svg_lineBasisBezier(path, x, y) { + path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); + } + function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); + } + function d3_svg_lineFiniteDifferences(points) { + var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; + } + m[i] = d; + return m; + } + function d3_svg_lineMonotoneTangents(points) { + var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + if (abs(d) < ε) { + m[i] = m[i + 1] = 0; + } else { + a = m[i] / d; + b = m[i + 1] / d; + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + i = -1; + while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); + tangents.push([ s || 0, m[i] * s || 0 ]); + } + return tangents; + } + function d3_svg_lineMonotone(points) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); + } + d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; + }; + function d3_svg_lineRadial(points) { + var point, i = -1, n = points.length, r, a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] - halfπ; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; + } + function d3_svg_area(projection) { + var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; + function area(data) { + var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { + return x; + } : d3_functor(x1), fy1 = y0 === y1 ? function() { + return y; + } : d3_functor(y1), x, y; + function segment() { + segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); + points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); + } else if (points0.length) { + segment(); + points0 = []; + points1 = []; + } + } + if (points0.length) segment(); + return segments.length ? segments.join("") : null; + } + area.x = function(_) { + if (!arguments.length) return x1; + x0 = x1 = _; + return area; + }; + area.x0 = function(_) { + if (!arguments.length) return x0; + x0 = _; + return area; + }; + area.x1 = function(_) { + if (!arguments.length) return x1; + x1 = _; + return area; + }; + area.y = function(_) { + if (!arguments.length) return y1; + y0 = y1 = _; + return area; + }; + area.y0 = function(_) { + if (!arguments.length) return y0; + y0 = _; + return area; + }; + area.y1 = function(_) { + if (!arguments.length) return y1; + y1 = _; + return area; + }; + area.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return area; + }; + area.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + interpolateReverse = interpolate.reverse || interpolate; + L = interpolate.closed ? "M" : "L"; + return area; + }; + area.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return area; + }; + return area; + } + d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; + d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { + return d3_svg_area(d3_identity); + }; + d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; + }; + d3.svg.chord = function() { + var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + function chord(d, i) { + var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); + return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; + } + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ; + return { + r: r, + a0: a0, + a1: a1, + p0: [ r * Math.cos(a0), r * Math.sin(a0) ], + p1: [ r * Math.cos(a1), r * Math.sin(a1) ] + }; + } + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p; + } + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3_functor(v); + return chord; + }; + chord.source = function(v) { + if (!arguments.length) return source; + source = d3_functor(v); + return chord; + }; + chord.target = function(v) { + if (!arguments.length) return target; + target = d3_functor(v); + return chord; + }; + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return chord; + }; + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return chord; + }; + return chord; + }; + function d3_svg_chordRadius(d) { + return d.radius; + } + d3.svg.diagonal = function() { + var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection; + function diagonal(d, i) { + var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { + x: p0.x, + y: m + }, { + x: p3.x, + y: m + }, p3 ]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3_functor(x); + return diagonal; + }; + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3_functor(x); + return diagonal; + }; + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + return diagonal; + }; + function d3_svg_diagonalProjection(d) { + return [ d.x, d.y ]; + } + d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; + diagonal.projection = function(x) { + return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; + }; + return diagonal; + }; + function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ; + return [ r * Math.cos(a), r * Math.sin(a) ]; + }; + } + d3.svg.symbol = function() { + var type = d3_svg_symbolType, size = d3_svg_symbolSize; + function symbol(d, i) { + return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); + } + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3_functor(x); + return symbol; + }; + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3_functor(x); + return symbol; + }; + return symbol; + }; + function d3_svg_symbolSize() { + return 64; + } + function d3_svg_symbolType() { + return "circle"; + } + function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / π); + return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; + } + var d3_svg_symbols = d3.map({ + circle: d3_svg_symbolCircle, + cross: function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; + }, + diamond: function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; + }, + square: function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; + } + }); + d3.svg.symbolTypes = d3_svg_symbols.keys(); + var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians); + d3_selectionPrototype.transition = function(name) { + var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || { + time: Date.now(), + ease: d3_ease_cubicInOut, + delay: 0, + duration: 250 + }; + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) d3_transitionNode(node, i, ns, id, transition); + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id); + }; + d3_selectionPrototype.interrupt = function(name) { + return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name))); + }; + var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace()); + function d3_selection_interruptNS(ns) { + return function() { + var lock, activeId, active; + if ((lock = this[ns]) && (active = lock[activeId = lock.active])) { + active.timer.c = null; + active.timer.t = NaN; + if (--lock.count) delete lock[activeId]; else delete this[ns]; + lock.active += .5; + active.event && active.event.interrupt.call(this, this.__data__, active.index); + } + }; + } + function d3_transition(groups, ns, id) { + d3_subclass(groups, d3_transitionPrototype); + groups.namespace = ns; + groups.id = id; + return groups; + } + var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit; + d3_transitionPrototype.call = d3_selectionPrototype.call; + d3_transitionPrototype.empty = d3_selectionPrototype.empty; + d3_transitionPrototype.node = d3_selectionPrototype.node; + d3_transitionPrototype.size = d3_selectionPrototype.size; + d3.transition = function(selection, name) { + return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection); + }; + d3.transition.prototype = d3_transitionPrototype; + d3_transitionPrototype.select = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + d3_transitionNode(subnode, i, ns, id, node[ns][id]); + subgroup.push(subnode); + } else { + subgroup.push(null); + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.selectAll = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + transition = node[ns][id]; + subnodes = selector.call(node, node.__data__, i, j); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o; ) { + if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition); + subgroup.push(subnode); + } + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_transition(subgroups, this.namespace, this.id); + }; + d3_transitionPrototype.tween = function(name, tween) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) return this.node()[ns][id].tween.get(name); + return d3_selection_each(this, tween == null ? function(node) { + node[ns][id].tween.remove(name); + } : function(node) { + node[ns][id].tween.set(name, tween); + }); + }; + function d3_transition_tween(groups, name, value, tween) { + var id = groups.id, ns = groups.namespace; + return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) { + node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j))); + } : (value = tween(value), function(node) { + node[ns][id].tween.set(name, value); + })); + } + d3_transitionPrototype.attr = function(nameNS, value) { + if (arguments.length < 2) { + for (value in nameNS) this.attr(value, nameNS[value]); + return this; + } + var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrTween(b) { + return b == null ? attrNull : (b += "", function() { + var a = this.getAttribute(name), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttribute(name, i(t)); + }); + }); + } + function attrTweenNS(b) { + return b == null ? attrNullNS : (b += "", function() { + var a = this.getAttributeNS(name.space, name.local), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttributeNS(name.space, name.local, i(t)); + }); + }); + } + return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.attrTween = function(nameNS, tween) { + var name = d3.ns.qualify(nameNS); + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f && function(t) { + this.setAttribute(name, f(t)); + }; + } + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.style(priority, name[priority], value); + return this; + } + priority = ""; + } + function styleNull() { + this.style.removeProperty(name); + } + function styleString(b) { + return b == null ? styleNull : (b += "", function() { + var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i; + return a !== b && (i = d3_interpolate(a, b), function(t) { + this.style.setProperty(name, i(t), priority); + }); + }); + } + return d3_transition_tween(this, "style." + name, value, styleString); + }; + d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + function styleTween(d, i) { + var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name)); + return f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + } + return this.tween("style." + name, styleTween); + }; + d3_transitionPrototype.text = function(value) { + return d3_transition_tween(this, "text", value, d3_transition_text); + }; + function d3_transition_text(b) { + if (b == null) b = ""; + return function() { + this.textContent = b; + }; + } + d3_transitionPrototype.remove = function() { + var ns = this.namespace; + return this.each("end.transition", function() { + var p; + if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this); + }); + }; + d3_transitionPrototype.ease = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].ease; + if (typeof value !== "function") value = d3.ease.apply(d3, arguments); + return d3_selection_each(this, function(node) { + node[ns][id].ease = value; + }); + }; + d3_transitionPrototype.delay = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].delay; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].delay = +value.call(node, node.__data__, i, j); + } : (value = +value, function(node) { + node[ns][id].delay = value; + })); + }; + d3_transitionPrototype.duration = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].duration; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j)); + } : (value = Math.max(1, value), function(node) { + node[ns][id].duration = value; + })); + }; + d3_transitionPrototype.each = function(type, listener) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) { + var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId; + try { + d3_transitionInheritId = id; + d3_selection_each(this, function(node, i, j) { + d3_transitionInherit = node[ns][id]; + type.call(node, node.__data__, i, j); + }); + } finally { + d3_transitionInherit = inherit; + d3_transitionInheritId = inheritId; + } + } else { + d3_selection_each(this, function(node) { + var transition = node[ns][id]; + (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener); + }); + } + return this; + }; + d3_transitionPrototype.transition = function() { + var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition; + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + transition = node[ns][id0]; + d3_transitionNode(node, i, ns, id1, { + time: transition.time, + ease: transition.ease, + delay: transition.delay + transition.duration, + duration: transition.duration + }); + } + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id1); + }; + function d3_transitionNamespace(name) { + return name == null ? "__transition__" : "__transition_" + name + "__"; + } + function d3_transitionNode(node, i, ns, id, inherit) { + var lock = node[ns] || (node[ns] = { + active: 0, + count: 0 + }), transition = lock[id], time, timer, duration, ease, tweens; + function schedule(elapsed) { + var delay = transition.delay; + timer.t = delay + time; + if (delay <= elapsed) return start(elapsed - delay); + timer.c = start; + } + function start(elapsed) { + var activeId = lock.active, active = lock[activeId]; + if (active) { + active.timer.c = null; + active.timer.t = NaN; + --lock.count; + delete lock[activeId]; + active.event && active.event.interrupt.call(node, node.__data__, active.index); + } + for (var cancelId in lock) { + if (+cancelId < id) { + var cancel = lock[cancelId]; + cancel.timer.c = null; + cancel.timer.t = NaN; + --lock.count; + delete lock[cancelId]; + } + } + timer.c = tick; + d3_timer(function() { + if (timer.c && tick(elapsed || 1)) { + timer.c = null; + timer.t = NaN; + } + return 1; + }, 0, time); + lock.active = id; + transition.event && transition.event.start.call(node, node.__data__, i); + tweens = []; + transition.tween.forEach(function(key, value) { + if (value = value.call(node, node.__data__, i)) { + tweens.push(value); + } + }); + ease = transition.ease; + duration = transition.duration; + } + function tick(elapsed) { + var t = elapsed / duration, e = ease(t), n = tweens.length; + while (n > 0) { + tweens[--n].call(node, e); + } + if (t >= 1) { + transition.event && transition.event.end.call(node, node.__data__, i); + if (--lock.count) delete lock[id]; else delete node[ns]; + return 1; + } + } + if (!transition) { + time = inherit.time; + timer = d3_timer(schedule, 0, time); + transition = lock[id] = { + tween: new d3_Map(), + time: time, + timer: timer, + delay: inherit.delay, + duration: inherit.duration, + ease: inherit.ease, + index: i + }; + inherit = null; + ++lock.count; + } + } + d3.svg.axis = function() { + var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_; + function axis(g) { + g.each(function() { + var g = d3.select(this); + var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy(); + var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform; + var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), + d3.transition(path)); + tickEnter.append("line"); + tickEnter.append("text"); + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2; + if (orient === "bottom" || orient === "top") { + tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2"; + text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize); + } else { + tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2"; + text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start"); + pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize); + } + lineEnter.attr(y2, sign * innerTickSize); + textEnter.attr(y1, sign * tickSpacing); + lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize); + textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing); + if (scale1.rangeBand) { + var x = scale1, dx = x.rangeBand() / 2; + scale0 = scale1 = function(d) { + return x(d) + dx; + }; + } else if (scale0.rangeBand) { + scale0 = scale1; + } else { + tickExit.call(tickTransform, scale1, scale0); + } + tickEnter.call(tickTransform, scale0, scale1); + tickUpdate.call(tickTransform, scale1, scale1); + }); + } + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient; + return axis; + }; + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = d3_array(arguments); + return axis; + }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + axis.tickSize = function(x) { + var n = arguments.length; + if (!n) return innerTickSize; + innerTickSize = +x; + outerTickSize = +arguments[n - 1]; + return axis; + }; + axis.innerTickSize = function(x) { + if (!arguments.length) return innerTickSize; + innerTickSize = +x; + return axis; + }; + axis.outerTickSize = function(x) { + if (!arguments.length) return outerTickSize; + outerTickSize = +x; + return axis; + }; + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + axis.tickSubdivide = function() { + return arguments.length && axis; + }; + return axis; + }; + var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = { + top: 1, + right: 1, + bottom: 1, + left: 1 + }; + function d3_svg_axisX(selection, x0, x1) { + selection.attr("transform", function(d) { + var v0 = x0(d); + return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)"; + }); + } + function d3_svg_axisY(selection, y0, y1) { + selection.attr("transform", function(d) { + var v0 = y0(d); + return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")"; + }); + } + d3.svg.brush = function() { + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0]; + function brush(g) { + g.each(function() { + var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); + var background = g.selectAll(".background").data([ 0 ]); + background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); + g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move"); + var resize = g.selectAll(".resize").data(resizes, d3_identity); + resize.exit().remove(); + resize.enter().append("g").attr("class", function(d) { + return "resize " + d; + }).style("cursor", function(d) { + return d3_svg_brushCursor[d]; + }).append("rect").attr("x", function(d) { + return /[ew]$/.test(d) ? -3 : null; + }).attr("y", function(d) { + return /^[ns]/.test(d) ? -3 : null; + }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); + resize.style("display", brush.empty() ? "none" : null); + var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range; + if (x) { + range = d3_scaleRange(x); + backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]); + redrawX(gUpdate); + } + if (y) { + range = d3_scaleRange(y); + backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]); + redrawY(gUpdate); + } + redraw(gUpdate); + }); + } + brush.event = function(g) { + g.each(function() { + var event_ = event.of(this, arguments), extent1 = { + x: xExtent, + y: yExtent, + i: xExtentDomain, + j: yExtentDomain + }, extent0 = this.__chart__ || extent1; + this.__chart__ = extent1; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.brush", function() { + xExtentDomain = extent0.i; + yExtentDomain = extent0.j; + xExtent = extent0.x; + yExtent = extent0.y; + event_({ + type: "brushstart" + }); + }).tween("brush:brush", function() { + var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y); + xExtentDomain = yExtentDomain = null; + return function(t) { + xExtent = extent1.x = xi(t); + yExtent = extent1.y = yi(t); + event_({ + type: "brush", + mode: "resize" + }); + }; + }).each("end.brush", function() { + xExtentDomain = extent1.i; + yExtentDomain = extent1.j; + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + }); + } else { + event_({ + type: "brushstart" + }); + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + } + }); + }; + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")"; + }); + } + function redrawX(g) { + g.select(".extent").attr("x", xExtent[0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]); + } + function redrawY(g) { + g.select(".extent").attr("y", yExtent[0]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]); + } + function brushstart() { + var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset; + var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup); + if (d3.event.changedTouches) { + w.on("touchmove.brush", brushmove).on("touchend.brush", brushend); + } else { + w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend); + } + g.interrupt().selectAll("*").interrupt(); + if (dragging) { + origin[0] = xExtent[0] - origin[0]; + origin[1] = yExtent[0] - origin[1]; + } else if (resizing) { + var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); + offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ]; + origin[0] = xExtent[ex]; + origin[1] = yExtent[ey]; + } else if (d3.event.altKey) center = origin.slice(); + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); + event_({ + type: "brushstart" + }); + brushmove(); + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= xExtent[1]; + origin[1] -= yExtent[1]; + dragging = 2; + } + d3_eventPreventDefault(); + } + } + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += xExtent[1]; + origin[1] += yExtent[1]; + dragging = 0; + d3_eventPreventDefault(); + } + } + function brushmove() { + var point = d3.mouse(target), moved = false; + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + if (!dragging) { + if (d3.event.altKey) { + if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ]; + origin[0] = xExtent[+(point[0] < center[0])]; + origin[1] = yExtent[+(point[1] < center[1])]; + } else center = null; + } + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + if (moved) { + redraw(g); + event_({ + type: "brush", + mode: dragging ? "move" : "resize" + }); + } + } + function move1(point, scale, i) { + var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max; + if (dragging) { + r0 -= position; + r1 -= size + position; + } + min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i]; + if (dragging) { + max = (min += position) + size; + } else { + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + if (extent[0] != min || extent[1] != max) { + if (i) yExtentDomain = null; else xExtentDomain = null; + extent[0] = min; + extent[1] = max; + return true; + } + } + function brushend() { + brushmove(); + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); + dragRestore(); + event_({ + type: "brushend" + }); + } + } + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.clamp = function(z) { + if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null; + if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z; + return brush; + }; + brush.extent = function(z) { + var x0, x1, y0, y1, t; + if (!arguments.length) { + if (x) { + if (xExtentDomain) { + x0 = xExtentDomain[0], x1 = xExtentDomain[1]; + } else { + x0 = xExtent[0], x1 = xExtent[1]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + } + if (y) { + if (yExtentDomain) { + y0 = yExtentDomain[0], y1 = yExtentDomain[1]; + } else { + y0 = yExtent[0], y1 = yExtent[1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + } + return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; + } + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + xExtentDomain = [ x0, x1 ]; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ]; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + yExtentDomain = [ y0, y1 ]; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ]; + } + return brush; + }; + brush.clear = function() { + if (!brush.empty()) { + xExtent = [ 0, 0 ], yExtent = [ 0, 0 ]; + xExtentDomain = yExtentDomain = null; + } + return brush; + }; + brush.empty = function() { + return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1]; + }; + return d3.rebind(brush, event, "on"); + }; + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; + var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat; + var d3_time_formatUtc = d3_time_format.utc; + var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ"); + d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso; + function d3_time_formatIsoNative(date) { + return date.toISOString(); + } + d3_time_formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + d3_time_formatIsoNative.toString = d3_time_formatIso.toString; + d3_time.second = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 1e3) * 1e3); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); + }, function(date) { + return date.getSeconds(); + }); + d3_time.seconds = d3_time.second.range; + d3_time.seconds.utc = d3_time.second.utc.range; + d3_time.minute = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 6e4) * 6e4); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); + }, function(date) { + return date.getMinutes(); + }); + d3_time.minutes = d3_time.minute.range; + d3_time.minutes.utc = d3_time.minute.utc.range; + d3_time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); + }, function(date) { + return date.getHours(); + }); + d3_time.hours = d3_time.hour.range; + d3_time.hours.utc = d3_time.hour.utc.range; + d3_time.month = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setDate(1); + return date; + }, function(date, offset) { + date.setMonth(date.getMonth() + offset); + }, function(date) { + return date.getMonth(); + }); + d3_time.months = d3_time.month.range; + d3_time.months.utc = d3_time.month.utc.range; + function d3_time_scale(linear, methods, format) { + function scale(x) { + return linear(x); + } + scale.invert = function(x) { + return d3_time_scaleDate(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(d3_time_scaleDate); + linear.domain(x); + return scale; + }; + function tickMethod(extent, count) { + var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target); + return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) { + return d / 31536e6; + }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i]; + } + scale.nice = function(interval, skip) { + var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval); + if (method) interval = method[0], skip = method[1]; + function skipped(date) { + return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length; + } + return scale.domain(d3_scale_nice(domain, skip > 1 ? { + floor: function(date) { + while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1); + return date; + }, + ceil: function(date) { + while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1); + return date; + } + } : interval)); + }; + scale.ticks = function(interval, skip) { + var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ { + range: interval + }, skip ]; + if (method) interval = method[0], skip = method[1]; + return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip); + }; + scale.tickFormat = function() { + return format; + }; + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_time_scaleDate(t) { + return new Date(t); + } + var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; + var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ]; + var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) { + return d.getMilliseconds(); + } ], [ ":%S", function(d) { + return d.getSeconds(); + } ], [ "%I:%M", function(d) { + return d.getMinutes(); + } ], [ "%I %p", function(d) { + return d.getHours(); + } ], [ "%a %d", function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ "%b %d", function(d) { + return d.getDate() != 1; + } ], [ "%B", function(d) { + return d.getMonth(); + } ], [ "%Y", d3_true ] ]); + var d3_time_scaleMilliseconds = { + range: function(start, stop, step) { + return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate); + }, + floor: d3_identity, + ceil: d3_identity + }; + d3_time_scaleLocalMethods.year = d3_time.year; + d3_time.scale = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + }; + var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) { + return [ m[0].utc, m[1] ]; + }); + var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) { + return d.getUTCMilliseconds(); + } ], [ ":%S", function(d) { + return d.getUTCSeconds(); + } ], [ "%I:%M", function(d) { + return d.getUTCMinutes(); + } ], [ "%I %p", function(d) { + return d.getUTCHours(); + } ], [ "%a %d", function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ "%b %d", function(d) { + return d.getUTCDate() != 1; + } ], [ "%B", function(d) { + return d.getUTCMonth(); + } ], [ "%Y", d3_true ] ]); + d3_time_scaleUtcMethods.year = d3_time.year.utc; + d3_time.scale.utc = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat); + }; + d3.text = d3_xhrType(function(request) { + return request.responseText; + }); + d3.json = function(url, callback) { + return d3_xhr(url, "application/json", d3_json, callback); + }; + function d3_json(request) { + return JSON.parse(request.responseText); + } + d3.html = function(url, callback) { + return d3_xhr(url, "text/html", d3_html, callback); + }; + function d3_html(request) { + var range = d3_document.createRange(); + range.selectNode(d3_document.body); + return range.createContextualFragment(request.responseText); + } + d3.xml = d3_xhrType(function(request) { + return request.responseXML; + }); + if (typeof define === "function" && define.amd) this.d3 = d3, define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; else this.d3 = d3; +}(); \ No newline at end of file diff --git a/vendor/assets/components/d3/d3.min.js b/vendor/assets/components/d3/d3.min.js new file mode 100644 index 000000000..e3aa5c60d --- /dev/null +++ b/vendor/assets/components/d3/d3.min.js @@ -0,0 +1,5 @@ +!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function u(n){return!isNaN(n)}function i(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function a(n){return n.length}function o(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function s(n){return(n+="")===xa||n[0]===ba?ba+n:n}function f(n){return(n+="")[0]===ba?n.slice(1):n}function h(n){return s(n)in this._}function g(n){return(n=s(n))in this._&&delete this._[n]}function p(){var n=[];for(var t in this._)n.push(f(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function m(){this._=Object.create(null)}function y(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=_a.length;r>e;++e){var u=_a[e]+t;if(u in n)return u}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],a=0,o=i.length;o>a;a++)(u=i[a])&&t(u,a,e);return n}function Z(n){return Sa(n,za),n}function V(n){var t,e;return function(r,u,i){var a,o=n[i].update,l=o.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(a=o[t])&&++t0&&(n=n.slice(0,o));var c=La.get(n);return c&&(n=c,l=B),o?t?u:r:t?b:i}function $(n,t){return function(e){var r=oa.event;oa.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{oa.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Ta,u="click"+r,i=oa.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==qa&&(qa="onselectstart"in e?!1:x(e.style,"userSelect")),qa){var a=n(e).style,o=a[qa];a[qa]="none"}return function(n){if(i.on(r,null),qa&&(a[qa]=o),n){var t=function(){i.on(u,null)};i.on(u,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var u=r.createSVGPoint();if(0>Ra){var i=t(n);if(i.scrollX||i.scrollY){r=oa.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var a=r[0][0].getScreenCTM();Ra=!(a.f||a.e),r.remove()}}return Ra?(u.x=e.pageX,u.y=e.pageY):(u.x=e.clientX,u.y=e.clientY),u=u.matrixTransform(n.getScreenCTM().inverse()),[u.x,u.y]}var o=n.getBoundingClientRect();return[e.clientX-o.left-n.clientLeft,e.clientY-o.top-n.clientTop]}function G(){return oa.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?ja:Math.acos(n)}function tn(n){return n>1?Ha:-1>n?-Ha:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function an(n){return(n=Math.sin(n/2))*n}function on(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(a-i)*n/60:180>n?a:240>n?i+(a-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,a;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,a=.5>=e?e*(1+t):e+t-e*t,i=2*e-a,new yn(u(n+120),u(n),u(n-120))}function sn(n,t,e){return this instanceof sn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof sn?new sn(n.h,n.c,n.l):n instanceof hn?pn(n.l,n.a,n.b):pn((n=Sn((n=oa.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new sn(n,t,e)}function fn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Oa)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof sn?fn(n.h,n.c,n.l):Sn((n=yn(n)).r,n.g,n.b):new hn(n,t,e)}function gn(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=vn(u)*Ka,r=vn(r)*Qa,i=vn(i)*no,new yn(mn(3.2404542*u-1.5371385*r-.4985314*i),mn(-.969266*u+1.8760108*r+.041556*i),mn(.0556434*u-.2040259*r+1.0572252*i))}function pn(n,t,e){return n>0?new sn(Math.atan2(e,t)*Ia,Math.sqrt(t*t+e*e),n):new sn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function mn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function yn(n,t,e){return this instanceof yn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof yn?new yn(n.r,n.g,n.b):_n(""+n,yn,cn):new yn(n,t,e)}function Mn(n){return new yn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,u,i,a=0,o=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(Nn(u[0]),Nn(u[1]),Nn(u[2]))}return(i=ro.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(a=(3840&i)>>4,a=a>>4|a,o=240&i,o=o>>4|o,l=15&i,l=l<<4|l):7===n.length&&(a=(16711680&i)>>16,o=(65280&i)>>8,l=255&i)),t(a,o,l))}function wn(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),a=Math.max(n,t,e),o=a-i,l=(a+i)/2;return o?(u=.5>l?o/(a+i):o/(2-a-i),r=n==a?(t-e)/o+(e>t?6:0):t==a?(e-n)/o+2:(n-t)/o+4,r*=60):(r=NaN,u=l>0&&1>l?0:r),new ln(r,u,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/Ka),u=dn((.2126729*n+.7151522*t+.072175*e)/Qa),i=dn((.0193339*n+.119192*t+.9503041*e)/no);return hn(116*u-16,500*(r-u),200*(u-i))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function u(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(i,l)}catch(r){return void a.error.call(i,r)}a.load.call(i,n)}else a.error.call(i,l)}var i={},a=oa.dispatch("beforesend","progress","load","error"),o={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=u:l.onreadystatechange=function(){l.readyState>3&&u()},l.onprogress=function(n){var t=oa.event;oa.event=n;try{a.progress.call(i,l)}finally{oa.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?o[n]:(null==t?delete o[n]:o[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(c=n,i):c},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ca(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),l.open(e,n,!0),null==t||"accept"in o||(o.accept=t+",*/*"),l.setRequestHeader)for(var s in o)l.setRequestHeader(s,o[s]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),a.beforesend.call(i,l),l.send(null==r?null:r),i},i.abort=function(){return l.abort(),i},oa.rebind(i,a,"on"),null==r?i:i.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,n:null};return io?io.n=i:uo=i,io=i,ao||(oo=clearTimeout(oo),ao=1,lo(Tn)),i}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(oo),oo=setTimeout(Tn,t)),ao=0):(ao=1,lo(Tn))}function Rn(){for(var n=Date.now(),t=uo;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=uo,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function Un(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],a=0,o=r[0],l=0;u>0&&o>0&&(l+o+1>t&&(o=Math.max(1,t-l)),i.push(n.substring(u-=o,u+o)),!((l+=o+1)>t));)o=r[a=(a+1)%r.length];return i.reverse().join(e)}:y;return function(n){var e=so.exec(n),r=e[1]||" ",a=e[2]||">",o=e[3]||"-",l=e[4]||"",c=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===a)&&(c=r="0",a="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===l&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=fo.get(g)||Fn;var M=c&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===o?"":o;if(0>p){var l=oa.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===a?u+n+k:">"===a?k+u+n:"^"===a?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new go(e-1)),1),e}function i(n,e){return t(n=new go(+n),e),n}function a(n,r,i){var a=u(n),o=[];if(i>1)for(;r>a;)e(a)%i||o.push(new Date(+a)),t(a,1);else for(;r>a;)o.push(new Date(+a)),t(a,1);return o}function o(n,t,e){try{go=Hn;var r=new Hn;return r._=n,a(r,t,e)}finally{go=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=a;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(u),l.offset=In(i),l.range=o,n}function In(n){return function(t,e){try{go=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{go=Date}}}function Yn(n){function t(n){function t(t){for(var e,u,i,a=[],o=-1,l=0;++oo;){if(r>=c)return-1;if(u=t.charCodeAt(o++),37===u){if(a=t.charAt(o++),i=C[a in vo?t.charAt(o++):a],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{go=Hn;var t=new go;return t._=n,r(t)}finally{go=Date}}var r=t(n);return e.parse=function(n){try{go=Hn;var t=r.parse(n);return t&&t._}finally{go=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=oa.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(m),k=Xn(m),N=Vn(y),E=Xn(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ho.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ho.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ho.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:ot,"%":function(){return"%"}},C={a:r,A:u,b:i,B:a,c:o,d:tt,e:tt,H:rt,I:rt,j:et,L:at,m:nt,M:ut,p:s,S:it,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function Vn(n){return new RegExp("^(?:"+n.map(oa.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ut(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function it(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function at(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ot(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=Ma(t)/60|0,u=Ma(t)%60;return e+Zn(r,"0",2)+Zn(u,"0",2)}function lt(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,o=a*e,l=Math.cos(t),c=Math.sin(t),s=i*c,f=u*l+s*Math.cos(o),h=s*a*Math.sin(o);So.add(Math.atan2(h,f)),r=n,u=l,i=c}var t,e,r,u,i;ko.point=function(a,o){ko.point=n,r=(t=a)*Oa,u=Math.cos(o=(e=o)*Oa/2+ja/4),i=Math.sin(o)},ko.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function mt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function yt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return Ma(n[0]-t[0])o;++o)u.point((e=n[o])[0],e[1]);return void u.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,i.push(l),a.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,i.push(l),a.push(c)}}),a.sort(t),qt(i),qt(a),i.length){for(var o=0,l=e,c=a.length;c>o;++o)a[o].e=l=!l;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var o=0,c=s.length;c>o;++o)u.point((f=s[o])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var o=s.length-1;o>=0;--o)u.point((f=s[o])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r0){for(b||(i.polygonStart(),b=!0),i.lineStart();++a1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Dt))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:a,lineStart:l,lineEnd:c,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=a,y.lineStart=l,y.lineEnd=c,g=oa.merge(g);var n=Ot(m,p);g.length?(b||(i.polygonStart(),b=!0),Lt(g,jt,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=Pt(),x=t(M),b=!1;return y}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function jt(n,t){return((n=n.x)[0]<0?n[1]-Ha-Da:Ha-n[1])-((t=t.x)[0]<0?t[1]-Ha-Da:Ha-t[1])}function Ut(n){var t,e=NaN,r=NaN,u=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(i,a){var o=i>0?ja:-ja,l=Ma(i-e);Ma(l-ja)0?Ha:-Ha),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(o,r),n.point(i,r),t=0):u!==o&&l>=ja&&(Ma(e-u)Da?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*a)):(t+r)/2}function Ht(n,t,e,r){var u;if(null==n)u=e*Ha,r.point(-ja,u),r.point(0,u),r.point(ja,u),r.point(ja,0),r.point(ja,-u),r.point(0,-u),r.point(-ja,-u),r.point(-ja,0),r.point(-ja,u);else if(Ma(n[0]-t[0])>Da){var i=n[0]o;++o){var c=t[o],s=c.length;if(s)for(var f=c[0],h=f[0],g=f[1]/2+ja/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=c[d];var m=n[0],y=n[1]/2+ja/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>ja,k=p*M;if(So.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*Ua:b,S^h>=e^m>=e){var N=yt(dt(f),dt(n));bt(N);var E=yt(u,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(a+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Da>i||Da>i&&0>So)^1&a}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,l,c,s;return{lineStart:function(){c=l=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=a?v?0:u(f,h):v?u(f+(0>f?ja:-ja),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(g=r(e,p),(wt(e,g)||wt(p,g))&&(p[0]+=Da,p[1]+=Da,v=t(p[0],p[1]))),v!==l)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(o&&e&&a^v){var m;d&i||!(m=r(p,e,!0))||(s=0,a?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&wt(e,p)||n.point(p[0],p[1]),e=p,l=v,i=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return s|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),u=dt(t),a=[1,0,0],o=yt(r,u),l=mt(o,o),c=o[0],s=l-c*c;if(!s)return!e&&n;var f=i*l/s,h=-i*c/s,g=yt(a,o),p=xt(a,f),v=xt(o,h);Mt(p,v);var d=g,m=mt(p,d),y=mt(d,d),M=m*m-y*(mt(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-m-x)/y);if(Mt(b,p),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=Ma(E-ja)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(Ma(b[0]-w)ja^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-m+x)/y);return Mt(z,p),[b,_t(z)]}}}function u(t,e){var r=a?n:ja-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),a=i>0,o=Ma(i)>Da,l=ve(n,6*Oa);return Rt(t,e,l,a?[0,-n]:[-ja,n-ja])}function Yt(n,t,e,r){return function(u){var i,a=u.a,o=u.b,l=a.x,c=a.y,s=o.x,f=o.y,h=0,g=1,p=s-l,v=f-c;if(i=n-l,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-l,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-c,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-c,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:l+h*p,y:c+h*v}),1>g&&(u.b={x:l+g*p,y:c+g*v}),u}}}}}}function Zt(n,t,e,r){function u(r,u){return Ma(r[0]-n)0?0:3:Ma(r[0]-e)0?2:1:Ma(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return a(n.x,t.x)}function a(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(o){function l(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,a=1,o=d[u],l=o.length,c=o[0];l>a;++a)i=o[a],c[1]<=r?i[1]>r&&Q(c,i,n)>0&&++t:i[1]<=r&&Q(c,i,n)<0&&--t,c=i;return 0!==t}function c(i,o,l,c){var s=0,f=0;if(null==i||(s=u(i,l))!==(f=u(o,l))||a(i,o)<0^l>0){do c.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+l+4)%4)!==f)}else c.point(o[0],o[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&o.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=NaN}function g(){v&&(p(y,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=f,w&&o.lineEnd()}function p(n,t){n=Math.max(-Fo,Math.min(Fo,n)),t=Math.max(-Fo,Math.min(Fo,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(o.lineStart(),o.point(n,t));else if(e&&w)o.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(o.lineStart(),o.point(r.a.x,r.a.y)),o.point(r.b.x,r.b.y),e||o.lineEnd(),k=!1):e&&(o.lineStart(),o.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,N=o,E=Pt(),A=Yt(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){o=E,v=[],d=[],k=!0},polygonEnd:function(){o=N,v=oa.merge(v);var t=l([n,r]),e=k&&t,u=v.length;(e||u)&&(o.polygonStart(),e&&(o.lineStart(),c(null,null,1,o),o.lineEnd()),u&&Lt(v,i,t,c,o),o.polygonEnd()),v=d=m=null}};return C}}function Vt(n){var t=0,e=ja/3,r=oe(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*ja/180,e=n[1]*ja/180):[t/ja*180,e/ja*180]},u}function Xt(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),a-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),a=Math.sqrt(i)/u;return e.invert=function(n,t){var e=a-t;return[Math.atan2(n,e)/u,tn((i-(n*n+e*e)*u*u)/(2*u))]},e}function $t(){function n(n,t){Oo+=u*n-r*t,r=n,u=t}var t,e,r,u;Xo.point=function(i,a){Xo.point=n,t=r=i,e=u=a},Xo.lineEnd=function(){n(t,e)}}function Bt(n,t){Io>n&&(Io=n),n>Zo&&(Zo=n),Yo>t&&(Yo=t),t>Vo&&(Vo=t)}function Wt(){function n(n,t){a.push("M",n,",",t,i)}function t(n,t){a.push("M",n,",",t),o.point=e}function e(n,t){a.push("L",n,",",t)}function r(){o.point=n}function u(){a.push("Z")}var i=Jt(4.5),a=[],o={point:n,lineStart:function(){o.point=t},lineEnd:r,polygonStart:function(){o.lineEnd=u},polygonEnd:function(){o.lineEnd=r,o.point=n},pointRadius:function(n){return i=Jt(n),o},result:function(){if(a.length){var n=a.join("");return a=[],n}}};return o}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ao+=n,Co+=t,++zo}function Kt(){function n(n,r){var u=n-t,i=r-e,a=Math.sqrt(u*u+i*i);Lo+=a*(t+n)/2,qo+=a*(e+r)/2,To+=a,Gt(t=n,e=r)}var t,e;Bo.point=function(r,u){Bo.point=n,Gt(t=r,e=u)}}function Qt(){Bo.point=Gt}function ne(){function n(n,t){var e=n-r,i=t-u,a=Math.sqrt(e*e+i*i);Lo+=a*(r+n)/2,qo+=a*(u+t)/2,To+=a,a=u*n-r*t,Ro+=a*(r+n),Do+=a*(u+t),Po+=3*a,Gt(r=n,u=t)}var t,e,r,u;Bo.point=function(i,a){Bo.point=n,Gt(t=r=i,e=u=a)},Bo.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+a,e),n.arc(t,e,a,0,Ua)}function e(t,e){n.moveTo(t,e),o.point=r}function r(t,e){n.lineTo(t,e)}function u(){o.point=t}function i(){n.closePath()}var a=4.5,o={point:t,lineStart:function(){o.point=e},lineEnd:u,polygonStart:function(){o.lineEnd=i},polygonEnd:function(){o.lineEnd=u,o.point=t},pointRadius:function(n){return a=n,o},result:b};return o}function ee(n){function t(n){return(o?r:e)(n)}function e(t){return ie(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=i,t.lineStart()}function i(e,r){var i=dt([e,r]),a=n(e,r);u(M,x,y,b,_,w,M=a[0],x=a[1],y=e,b=i[0],_=i[1],w=i[2],o,t),t.point(M,x)}function a(){S.point=e,t.lineEnd()}function l(){r(),S.point=c,S.lineEnd=s}function c(n,t){ +i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,o,t),S.lineEnd=a,a()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:a,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,o,l,c,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=o+g,_=l+p,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=Ma(Ma(w)-1)i||Ma((y*z+M*L)/x-.5)>.3||a>o*g+l*p+c*v)&&(u(t,e,r,o,l,c,A,C,N,b/=S,_/=S,w,d,m),m.point(A,C),u(A,C,N,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,a=Math.cos(30*Oa),o=16;return t.precision=function(n){return arguments.length?(o=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function re(n){var t=ee(function(t,e){return n([t*Ia,e*Ia])});return function(n){return le(t(n))}}function ue(n){this.stream=n}function ie(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ae(n){return oe(function(){return n})()}function oe(n){function t(n){return n=o(n[0]*Oa,n[1]*Oa),[n[0]*h+l,c-n[1]*h]}function e(n){return n=o.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Ia,n[1]*Ia]}function r(){o=Ct(a=fe(m,M,x),i);var n=i(v,d);return l=g-n[0]*h,c=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,a,o,l,c,s,f=ee(function(n,t){return n=i(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,M=0,x=0,b=Uo,_=y,w=null,S=null;return t.stream=function(n){return s&&(s.valid=!1),s=le(b(a,f(_(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Uo):It((w=+n)*Oa),u()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):y,u()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Oa,d=n[1]%360*Oa,r()):[v*Ia,d*Ia]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Oa,M=n[1]%360*Oa,x=n.length>2?n[2]%360*Oa:0,r()):[m*Ia,M*Ia,x*Ia]},oa.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function le(n){return ie(n,function(t,e){n.point(t*Oa,e*Oa)})}function ce(n,t){return[n,t]}function se(n,t){return[n>ja?n-Ua:-ja>n?n+Ua:n,t]}function fe(n,t,e){return n?t||e?Ct(ge(n),pe(t,e)):ge(n):t||e?pe(t,e):se}function he(n){return function(t,e){return t+=n,[t>ja?t-Ua:-ja>t?t+Ua:t,e]}}function ge(n){var t=he(n);return t.invert=he(-n),t}function pe(n,t){function e(n,t){var e=Math.cos(t),o=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*r+o*u;return[Math.atan2(l*i-s*a,o*r-c*u),tn(s*i+l*a)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),a=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),o=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*i-l*a;return[Math.atan2(l*i+c*a,o*r+s*u),tn(s*r-o*u)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,a,o){var l=a*t;null!=u?(u=de(e,u),i=de(e,i),(a>0?i>u:u>i)&&(u+=a*Ua)):(u=n+a*Ua,i=n-.5*l);for(var c,s=u;a>0?s>i:i>s;s-=l)o.point((c=_t([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Da)%(2*Math.PI)}function me(n,t,e){var r=oa.range(n,t-Da,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function ye(n,t,e){var r=oa.range(n,t-Da,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),a=Math.cos(r),o=Math.sin(r),l=u*Math.cos(n),c=u*Math.sin(n),s=a*Math.cos(e),f=a*Math.sin(e),h=2*Math.asin(Math.sqrt(an(r-t)+u*a*an(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*l+t*s,u=e*c+t*f,a=e*i+t*o;return[Math.atan2(u,r)*Ia,Math.atan2(a,Math.sqrt(r*r+u*u))*Ia]}:function(){return[n*Ia,t*Ia]};return p.distance=h,p}function _e(){function n(n,u){var i=Math.sin(u*=Oa),a=Math.cos(u),o=Ma((n*=Oa)-t),l=Math.cos(o);Wo+=Math.atan2(Math.sqrt((o=a*Math.sin(o))*o+(o=r*i-e*a*l)*o),e*i+r*a*l),t=n,e=i,r=a}var t,e,r;Jo.point=function(u,i){t=u*Oa,e=Math.sin(i*=Oa),r=Math.cos(i),Jo.point=n},Jo.lineEnd=function(){Jo.point=Jo.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),a=Math.cos(u);return[Math.atan2(n*i,r*a),Math.asin(r&&e*i/r)]},e}function Se(n,t){function e(n,t){a>0?-Ha+Da>t&&(t=-Ha+Da):t>Ha-Da&&(t=Ha-Da);var e=a/Math.pow(u(t),i);return[e*Math.sin(i*n),a-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(ja/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),a=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=a-t,r=K(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(a/r,1/i))-Ha]},e):Ne}function ke(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return Ma(u)u;u++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var u=n[0],i=e[0],a=t[0]-u,o=r[0]-i,l=n[1],c=e[1],s=t[1]-l,f=r[1]-c,h=(o*(l-c)-f*(u-i))/(f*a-o*s);return[u+h*a,l+h*s]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function je(n){var t=ll.pop()||new Pe;return t.site=n,t}function Ue(n){Be(n),il.remove(n),ll.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,a=n.N,o=[n];Ue(n);for(var l=i;l.circle&&Ma(e-l.circle.x)s;++s)c=o[s],l=o[s-1],nr(c.edge,l.site,c.site,u);l=o[0],c=o[f-1],c.edge=Ke(l.site,c.site,null,u),$e(l),$e(c)}function He(n){for(var t,e,r,u,i=n.x,a=n.y,o=il._;o;)if(r=Oe(o,a)-i,r>Da)o=o.L;else{if(u=i-Ie(o,a),!(u>Da)){r>-Da?(t=o.P,e=o):u>-Da?(t=o,e=o.N):t=e=o;break}if(!o.R){t=o;break}o=o.R}var l=je(n);if(il.insert(t,l),t||e){if(t===e)return Be(t),e=je(t.site),il.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,s=c.x,f=c.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};nr(e.edge,c,p,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,p,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var a=n.P;if(!a)return-(1/0);e=a.site;var o=e.x,l=e.y,c=l-t;if(!c)return o;var s=o-r,f=1/i-1/c,h=s/c;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*c)-l+c/2+u-i/2)))/f+r:(r+o)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,u,i,a,o,l,c,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=ul,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(o=i.edges,l=o.length,a=0;l>a;)s=o[a].end(),r=s.x,u=s.y,c=o[++a%l].start(),t=c.x,e=c.y,(Ma(r-t)>Da||Ma(u-e)>Da)&&(o.splice(a,0,new tr(Qe(i.site,s,Ma(r-f)Da?{x:f,y:Ma(t-f)Da?{x:Ma(e-p)Da?{x:h,y:Ma(t-h)Da?{x:Ma(e-g)=-Pa)){var g=l*l+c*c,p=s*s+f*f,v=(f*g-c*p)/h,d=(l*p-s*g)/h,f=d+o,m=cl.pop()||new Xe;m.arc=n,m.site=u,m.x=v+a,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=ol._;M;)if(m.yd||d>=o)return;if(h>p){if(i){if(i.y>=c)return}else i={x:d,y:l};e={x:d,y:c}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=c)return}else i={x:(l-u)/r,y:l};e={x:(c-u)/r,y:c}}else{if(i){if(i.yg){if(i){if(i.x>=o)return}else i={x:a,y:r*a+u};e={x:o,y:r*o+u}}else{if(i){if(i.xi||f>a||r>h||u>g)){if(p=n.point){var p,v=t-n.x,d=e-n.y,m=v*v+d*d;if(l>m){var y=Math.sqrt(l=m);r=t-y,u=e-y,i=t+y,a=e+y,o=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,s,f,x,b);break;case 1:c(n,x,f,h,b);break;case 2:c(n,s,b,x,g);break;case 3:c(n,x,b,h,g)}}}(n,r,u,i,a),o}function vr(n,t){n=oa.rgb(n),t=oa.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,a=t.g-r,o=t.b-u;return function(n){return"#"+bn(Math.round(e+i*n))+bn(Math.round(r+a*n))+bn(Math.round(u+o*n))}}function dr(n,t){var e,r={},u={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function mr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function yr(n,t){var e,r,u,i=fl.lastIndex=hl.lastIndex=0,a=-1,o=[],l=[];for(n+="",t+="";(e=fl.exec(n))&&(r=hl.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),o[a]?o[a]+=u:o[++a]=u),(e=e[0])===(r=r[0])?o[a]?o[a]+=r:o[++a]=r:(o[++a]=null,l.push({i:a,x:mr(e,r)})),i=hl.lastIndex;return ir;++r)o[(e=l[r]).i]=e.x(n);return o.join("")})}function Mr(n,t){for(var e,r=oa.interpolators.length;--r>=0&&!(e=oa.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],u=[],i=n.length,a=t.length,o=Math.min(n.length,t.length);for(e=0;o>e;++e)r.push(Mr(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;a>e;++e)u[e]=t[e];return function(n){for(e=0;o>e;++e)u[e]=r[e](n);return u}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Ha)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ua*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ua/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=oa.hcl(n),t=oa.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,a=t.c-r,o=t.l-u;return isNaN(a)&&(a=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return fn(e+i*n,r+a*n,u+o*n)+""}}function Dr(n,t){n=oa.hsl(n),t=oa.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,a=t.s-r,o=t.l-u;return isNaN(a)&&(a=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return cn(e+i*n,r+a*n,u+o*n)+""}}function Pr(n,t){n=oa.lab(n),t=oa.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,a=t.a-r,o=t.b-u;return function(n){return gn(e+i*n,r+a*n,u+o*n)+""}}function jr(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Ur(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),u=Fr(t,e),i=Hr(Or(e,t,-u))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:mr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:mr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var u=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:u-4,x:mr(n[0],t[0])},{i:u-2,x:mr(n[1],t[1])})}else(1!==t[0]||1!==t[1])&&e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=oa.transform(n),t=oa.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,u=-1,i=r.length;++u=0;)e.push(u[r])}function au(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,a=-1;++ae;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function mu(n){return n.reduce(yu,0)}function yu(n,t){return n+t[1]}function Mu(n,t){return xu(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xu(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function bu(n){return[oa.min(n),oa.max(n)]}function _u(n,t){return n.value-t.value}function wu(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Su(n,t){n._pack_next=t,t._pack_prev=n}function ku(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Nu(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(c=e.length)){var e,r,u,i,a,o,l,c,s=1/0,f=-(1/0),h=1/0,g=-(1/0);if(e.forEach(Eu),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(u=e[1],u.x=u.r,u.y=0,t(u),c>2))for(i=e[2],zu(r,u,i),t(i),wu(r,i),r._pack_prev=i,wu(i,u),u=r._pack_next,a=3;c>a;a++){zu(r,u,i=e[a]);var p=0,v=1,d=1;for(o=u._pack_next;o!==u;o=o._pack_next,v++)if(ku(o,i)){p=1;break}if(1==p)for(l=r._pack_prev;l!==o._pack_prev&&!ku(l,i);l=l._pack_prev,d++);p?(d>v||v==d&&u.ra;a++)i=e[a],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(Au)}}function Eu(n){n._pack_next=n._pack_prev=n}function Au(n){delete n._pack_next,delete n._pack_prev}function Cu(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,a=u.length;++i=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pu(n,t,e){return n.a.parent===t.parent?n.a:e}function ju(n){return 1+oa.max(n,function(n){return n.y})}function Uu(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fu(n){var t=n.children;return t&&t.length?Fu(t[0]):n}function Hu(n){var t,e=n.children;return e&&(t=e.length)?Hu(e[t-1]):n}function Ou(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Iu(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Yu(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zu(n){return n.rangeExtent?n.rangeExtent():Yu(n.range())}function Vu(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Xu(n,t){var e,r=0,u=n.length-1,i=n[r],a=n[u];return i>a&&(e=r,r=u,u=e,e=i,i=a,a=e),n[r]=t.floor(i),n[u]=t.ceil(a),n}function $u(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:wl}function Bu(n,t,e,r){var u=[],i=[],a=0,o=Math.min(n.length,t.length)-1;for(n[o]2?Bu:Vu,l=r?Wr:Br;return a=u(n,t,l,e),o=u(t,n,l,Mr),i}function i(n){return a(n)}var a,o;return i.invert=function(n){return o(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(jr)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Qu(n,t)},i.tickFormat=function(t,e){return ni(n,t,e)},i.nice=function(t){return Gu(n,t),u()},i.copy=function(){return Wu(n,t,e,r)},u()}function Ju(n,t){return oa.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gu(n,t){return Xu(n,$u(Ku(n,t)[2]))}function Ku(n,t){null==t&&(t=10);var e=Yu(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Qu(n,t){return oa.range.apply(oa,Ku(n,t))}function ni(n,t,e){var r=Ku(n,t);if(e){var u=so.exec(e);if(u.shift(),"s"===u[8]){var i=oa.formatPrefix(Math.max(Ma(r[0]),Ma(r[1])));return u[7]||(u[7]="."+ti(i.scale(r[2]))),u[8]="f",e=oa.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+ei(u[8],r)),e=u.join("")}else e=",."+ti(r[2])+"f";return oa.format(e)}function ti(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function ei(n,t){var e=ti(t[2]);return n in Sl?Math.abs(e-ti(Math.max(Ma(t[0]),Ma(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ri(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function a(t){return n(u(t))}return a.invert=function(t){return i(n.invert(t))},a.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),a):r},a.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),a):t},a.nice=function(){var t=Xu(r.map(u),e?Math:Nl);return n.domain(t),r=t.map(i),a},a.ticks=function(){var n=Yu(r),a=[],o=n[0],l=n[1],c=Math.floor(u(o)),s=Math.ceil(u(l)),f=t%1?2:t;if(isFinite(s-c)){if(e){for(;s>c;c++)for(var h=1;f>h;h++)a.push(i(c)*h);a.push(i(c))}else for(a.push(i(c));c++0;h--)a.push(i(c)*h);for(c=0;a[c]l;s--);a=a.slice(c,s)}return a},a.tickFormat=function(n,t){if(!arguments.length)return kl;arguments.length<2?t=kl:"function"!=typeof t&&(t=oa.format(t));var r,o=Math.max(.1,n/a.ticks().length),l=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(l(u(n)+r))<=o?t(n):""}},a.copy=function(){return ri(n.copy(),t,e,r)},Ju(a,n)}function ui(n,t,e){function r(t){return n(u(t))}var u=ii(t),i=ii(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Qu(e,n)},r.tickFormat=function(n,t){return ni(e,n,t)},r.nice=function(n){return r.domain(Gu(e,n))},r.exponent=function(a){return arguments.length?(u=ii(t=a),i=ii(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return ui(n.copy(),t,e)},Ju(r,n)}function ii(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ai(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):NaN))-1)%i.length]}function r(t,e){return oa.range(n.length).map(function(n){return t+e*n})}var u,i,a;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new c;for(var i,a=-1,o=r.length;++ae?[NaN,NaN]:[e>0?o[e-1]:n[0],et?NaN:t/i+n,[t,t+1/i]},r.copy=function(){return li(n,t,e)},u()}function ci(n,t){function e(e){return e>=e?t[oa.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return ci(n,t)},e}function si(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qu(n,t)},t.tickFormat=function(t,e){return ni(n,t,e)},t.copy=function(){return si(n)},t}function fi(){return 0}function hi(n){return n.innerRadius}function gi(n){return n.outerRadius}function pi(n){return n.startAngle}function vi(n){return n.endAngle}function di(n){return n&&n.padAngle}function mi(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function yi(n,t,e,r,u){var i=n[0]-t[0],a=n[1]-t[1],o=(u?r:-r)/Math.sqrt(i*i+a*a),l=o*a,c=-o*i,s=n[0]+l,f=n[1]+c,h=t[0]+l,g=t[1]+c,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(Math.max(0,M*M*y-x*x)),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,N=_-p,E=w-v,A=S-p,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mi(n){function t(t){function a(){c.push("M",i(n(s),o))}for(var l,c=[],s=[],f=-1,h=t.length,g=En(e),p=En(r);++f1?n.join("L"):n+"Z"}function bi(n){return n.join("L")+"Z"}function _i(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1&&u.push("H",r[0]),u.join("")}function wi(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){o=t[1],i=n[l],l++,r+="C"+(u[0]+a[0])+","+(u[1]+a[1])+","+(i[0]-o[0])+","+(i[1]-o[1])+","+i[0]+","+i[1];for(var c=2;c9&&(u=3*t/Math.sqrt(u),a[o]=u*e,a[o+1]=u*r));for(o=-1;++o<=l;)u=(n[Math.min(l,o+1)][0]-n[Math.max(0,o-1)][0])/(6*(1+a[o]*a[o])),i.push([u||0,a[o]*u||0]);return i}function Fi(n){return n.length<3?xi(n):n[0]+Ai(n,Ui(n))}function Hi(n){for(var t,e,r,u=-1,i=n.length;++u=t?a(n-t):void(s.c=a)}function a(e){var u=p.active,i=p[u];i&&(i.timer.c=null,i.timer.t=NaN,--p.count,delete p[u],i.event&&i.event.interrupt.call(n,n.__data__,i.index));for(var a in p)if(r>+a){var c=p[a];c.timer.c=null,c.timer.t=NaN,--p.count,delete p[a]}s.c=o,qn(function(){return s.c&&o(e||1)&&(s.c=null,s.t=NaN),1},0,l),p.active=r,v.event&&v.event.start.call(n,n.__data__,t),g=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&g.push(r)}),h=v.ease,f=v.duration}function o(u){for(var i=u/f,a=h(i),o=g.length;o>0;)g[--o].call(n,a);return i>=1?(v.event&&v.event.end.call(n,n.__data__,t),--p.count?delete p[r]:delete n[e],1):void 0}var l,s,f,h,g,p=n[e]||(n[e]={active:0,count:0}),v=p[r];v||(l=u.time,s=qn(i,0,l),v=p[r]={tween:new c,time:l,timer:s,delay:u.delay,duration:u.duration,ease:u.ease,index:t},u=null,++p.count)}function na(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function ta(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function ea(n){return n.toISOString()}function ra(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=oa.bisect(Gl,u);return i==Gl.length?[t.year,Ku(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Gl[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=ua(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=ua(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yu(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],ua(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ra(n.copy(),t,e)},Ju(r,n)}function ua(n){return new Date(n)}function ia(n){return JSON.parse(n.responseText)}function aa(n){var t=sa.createRange();return t.selectNode(sa.body),t.createContextualFragment(n.responseText)}var oa={version:"3.5.9"},la=[].slice,ca=function(n){return la.call(n)},sa=this.document;if(sa)try{ca(sa.documentElement.childNodes)[0].nodeType}catch(fa){ca=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),sa)try{sa.createElement("DIV").style.setProperty("opacity",0,"")}catch(ha){var ga=this.Element.prototype,pa=ga.setAttribute,va=ga.setAttributeNS,da=this.CSSStyleDeclaration.prototype,ma=da.setProperty;ga.setAttribute=function(n,t){pa.call(this,n,t+"")},ga.setAttributeNS=function(n,t,e){va.call(this,n,t,e+"")},da.setProperty=function(n,t,e){ma.call(this,n,t+"",e)}}oa.ascending=e,oa.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},oa.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ur&&(e=r)}else{for(;++u=r){e=r;break}for(;++ur&&(e=r)}return e},oa.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ue&&(e=r)}else{for(;++u=r){e=r;break}for(;++ue&&(e=r)}return e},oa.extent=function(n,t){var e,r,u,i=-1,a=n.length;if(1===arguments.length){for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},oa.sum=function(n,t){var e,r=0,i=n.length,a=-1;if(1===arguments.length)for(;++a1?l/(s-1):void 0},oa.deviation=function(){var n=oa.variance.apply(this,arguments);return n?Math.sqrt(n):n};var ya=i(e);oa.bisectLeft=ya.left,oa.bisect=oa.bisectRight=ya.right,oa.bisector=function(n){return i(1===n.length?function(t,r){return e(n(t),r)}:n)},oa.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=Math.random()*i--|0,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},oa.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},oa.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},oa.zip=function(){if(!(r=arguments.length))return[];for(var n=-1,t=oa.min(arguments,a),e=new Array(t);++n=0;)for(r=n[u],t=r.length;--t>=0;)e[--a]=r[t];return e};var Ma=Math.abs;oa.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,u=[],i=o(Ma(e)),a=-1;if(n*=i,t*=i,e*=i,0>e)for(;(r=n+e*++a)>t;)u.push(r/i);else for(;(r=n+e*++a)=i.length)return r?r.call(u,a):e?a.sort(e):a;for(var l,s,f,h,g=-1,p=a.length,v=i[o++],d=new c;++g=i.length)return n;var r=[],u=a[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],a=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(oa.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return a[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},oa.set=function(n){var t=new m;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(m,{has:h,add:function(n){return this._[s(n+="")]=!0,n},remove:g,values:p,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t))}}),oa.behavior={},oa.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},oa.event=null,oa.requote=function(n){return n.replace(wa,"\\$&")};var wa=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,Sa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ka=function(n,t){return t.querySelector(n)},Na=function(n,t){return t.querySelectorAll(n)},Ea=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ea=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(ka=function(n,t){return Sizzle(n,t)[0]||null},Na=Sizzle,Ea=Sizzle.matchesSelector),oa.selection=function(){return oa.select(sa.documentElement)};var Aa=oa.selection.prototype=[];Aa.select=function(n){var t,e,r,u,i=[];n=A(n);for(var a=-1,o=this.length;++a=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Ca.hasOwnProperty(e)?{space:Ca[e],local:n}:n}},Aa.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=oa.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Aa.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,u=-1;if(t=e.classList){for(;++uu){if("string"!=typeof n){2>u&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>u){var i=this.node();return t(i).getComputedStyle(i,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Aa.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(j(t,n[t]));return this}return this.each(j(n,t))},Aa.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Aa.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Aa.append=function(n){return n=U(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Aa.insert=function(n,t){return n=U(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Aa.remove=function(){return this.each(F)},Aa.data=function(n,t){function e(n,e){var r,u,i,a=n.length,f=e.length,h=Math.min(a,f),g=new Array(f),p=new Array(f),v=new Array(a);if(t){var d,m=new c,y=new Array(a);for(r=-1;++rr;++r)p[r]=H(e[r]);for(;a>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,o.push(p),l.push(g),s.push(v)}var r,u,i=-1,a=this.length;if(!arguments.length){for(n=new Array(a=(r=this[0]).length);++ii;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var o=0,l=e.length;l>o;o++)(r=e[o])&&n.call(r,r.__data__,o,i)&&t.push(r)}return E(u)},Aa.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},Aa.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},Aa.size=function(){var n=0;return Y(this,function(){++n}),n};var za=[];oa.selection.enter=Z,oa.selection.enter.prototype=za,za.append=Aa.append,za.empty=Aa.empty,za.node=Aa.node,za.call=Aa.call,za.size=Aa.size,za.select=function(n){for(var t,e,r,u,i,a=[],o=-1,l=this.length;++or){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var La=oa.map({mouseenter:"mouseover",mouseleave:"mouseout"});sa&&La.forEach(function(n){"on"+n in sa&&La.remove(n)});var qa,Ta=0;oa.mouse=function(n){return J(n,k())};var Ra=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;oa.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return J(n,r)},oa.behavior.drag=function(){function n(){this.on("mousedown.drag",i).on("touchstart.drag",a)}function e(n,t,e,i,a){return function(){function o(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(m.on(i+d,null).on(a+d,null),y(p),g({type:"dragend"}))}var c,s=this,f=oa.event.target,h=s.parentNode,g=r.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=oa.select(e(f)).on(i+d,o).on(a+d,l),y=W(f),M=t(h,v);u?(c=u.apply(s,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],g({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),u=null,i=e(b,oa.mouse,t,"mousemove","mouseup"),a=e(G,oa.touch,y,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},oa.rebind(n,r,"on")},oa.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?ca(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Da=1e-6,Pa=Da*Da,ja=Math.PI,Ua=2*ja,Fa=Ua-Da,Ha=ja/2,Oa=ja/180,Ia=180/ja,Ya=Math.SQRT2,Za=2,Va=4;oa.interpolateZoom=function(n,t){var e,r,u=n[0],i=n[1],a=n[2],o=t[0],l=t[1],c=t[2],s=o-u,f=l-i,h=s*s+f*f;if(Pa>h)r=Math.log(c/a)/Ya,e=function(n){return[u+n*s,i+n*f,a*Math.exp(Ya*n*r)]};else{var g=Math.sqrt(h),p=(c*c-a*a+Va*h)/(2*a*Za*g),v=(c*c-a*a-Va*h)/(2*c*Za*g),d=Math.log(Math.sqrt(p*p+1)-p),m=Math.log(Math.sqrt(v*v+1)-v);r=(m-d)/Ya,e=function(n){var t=n*r,e=rn(d),o=a/(Za*g)*(e*un(Ya*t+d)-en(d));return[u+o*s,i+o*f,a*e/rn(Ya*t+d)]}}return e.duration=1e3*r,e},oa.behavior.zoom=function(){function n(n){n.on(L,f).on($a+".zoom",g).on("dblclick.zoom",p).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function u(n){k.k=Math.max(A[0],Math.min(A[1],n))}function i(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function a(t,e,r,a){t.__chart__={x:k.x,y:k.y,k:k.k},u(Math.pow(2,a)),i(d=e,r),t=oa.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function o(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){o(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function s(n){--z||(n({type:"zoomend"}),d=null)}function f(){function n(){o=1,i(oa.mouse(u),h),c(a)}function r(){f.on(q,null).on(T,null),g(o),s(a)}var u=this,a=D.of(u,arguments),o=0,f=oa.select(t(u)).on(q,n).on(T,r),h=e(oa.mouse(u)),g=W(u);Ol.call(u),l(a)}function h(){function n(){var n=oa.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=oa.event.target;oa.select(t).on(x,r).on(b,o),_.push(t);for(var e=oa.event.changedTouches,u=0,i=e.length;i>u;++u)d[e[u].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var s=l[0];a(p,s,d[s.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var s=l[0],f=l[1],h=s[0]-f[0],g=s[1]-f[1];m=h*h+g*g}}function r(){var n,t,e,r,a=oa.touches(p);Ol.call(p);for(var o=0,l=a.length;l>o;++o,r=null)if(e=a[o],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],u(f*g)}M=null,i(n,t),c(v)}function o(){if(oa.event.touches.length){for(var t=oa.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}oa.selectAll(_).on(y,null),w.on(L,f).on(R,h),N(),s(v)}var g,p=this,v=D.of(p,arguments),d={},m=0,y=".zoom-"+oa.event.changedTouches[0].identifier,x="touchmove"+y,b="touchend"+y,_=[],w=oa.select(p),N=W(p);t(),l(v),w.on(L,null).on(R,t)}function g(){var n=D.of(this,arguments);y?clearTimeout(y):(Ol.call(this),v=e(d=m||oa.mouse(this)),l(n)),y=setTimeout(function(){y=null,s(n)},50),S(),u(Math.pow(2,.002*Xa())*k.k),i(d,v),c(n)}function p(){var n=oa.mouse(this),t=Math.log(k.k)/Math.LN2;a(this,n,e(n),oa.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,m,y,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Ba,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return $a||($a="onwheel"in sa?(Xa=function(){return-oa.event.deltaY*(oa.event.deltaMode?120:1)},"wheel"):"onmousewheel"in sa?(Xa=function(){return oa.event.wheelDelta},"mousewheel"):(Xa=function(){return-oa.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Fl?oa.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],u=d?d[0]:e/2,i=d?d[1]:r/2,a=oa.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=a(t),o=e/r[2];this.__chart__=k={x:u-r[0]*o,y:i-r[1]*o,k:o},c(n)}}).each("interrupt.zoom",function(){s(n)}).each("end.zoom",function(){s(n)}):(this.__chart__=k,l(n),c(n),s(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},o(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},u(+t),o(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Ba:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},oa.rebind(n,D,"on")};var Xa,$a,Ba=[0,1/0];oa.color=on,on.prototype.toString=function(){return this.rgb()+""},oa.hsl=ln;var Wa=ln.prototype=new on;Wa.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Wa.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Wa.rgb=function(){return cn(this.h,this.s,this.l)},oa.hcl=sn;var Ja=sn.prototype=new on;Ja.brighter=function(n){return new sn(this.h,this.c,Math.min(100,this.l+Ga*(arguments.length?n:1)))},Ja.darker=function(n){return new sn(this.h,this.c,Math.max(0,this.l-Ga*(arguments.length?n:1)))},Ja.rgb=function(){return fn(this.h,this.c,this.l).rgb()},oa.lab=hn;var Ga=18,Ka=.95047,Qa=1,no=1.08883,to=hn.prototype=new on;to.brighter=function(n){return new hn(Math.min(100,this.l+Ga*(arguments.length?n:1)),this.a,this.b)},to.darker=function(n){return new hn(Math.max(0,this.l-Ga*(arguments.length?n:1)),this.a,this.b)},to.rgb=function(){return gn(this.l,this.a,this.b)},oa.rgb=yn;var eo=yn.prototype=new on;eo.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new yn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new yn(u,u,u)},eo.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new yn(n*this.r,n*this.g,n*this.b)},eo.hsl=function(){return wn(this.r,this.g,this.b)},eo.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ro=oa.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ro.forEach(function(n,t){ro.set(n,Mn(t))}),oa.functor=En,oa.xhr=An(y),oa.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var a=Cn(n,t,null==e?r:u(e),i);return a.row=function(n){return arguments.length?a.response(null==(e=n)?r:u(n)):e},a}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(a).join(n)}function a(n){return o.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var o=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=c)return a;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++s;){var r=n.charCodeAt(s++),o=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++o);else if(r!==l)continue;return n.slice(t,s-o)}return n.slice(t)}for(var r,u,i={},a={},o=[],c=n.length,s=0,f=0;(r=e())!==a;){for(var h=[];r!==i&&r!==a;)h.push(r),r=e();t&&null==(h=t(h,f++))||o.push(h)}return o},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new m,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(a).join(n)].concat(t.map(function(t){return u.map(function(n){return a(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},oa.csv=oa.dsv(",","text/csv"),oa.tsv=oa.dsv(" ","text/tab-separated-values");var uo,io,ao,oo,lo=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};oa.timer=function(){qn.apply(this,arguments)},oa.timer.flush=function(){Rn(),Dn()},oa.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var co=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(jn);oa.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=oa.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),co[8+e/3]};var so=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,fo=oa.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=oa.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ho=oa.time={},go=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){po.setUTCDate.apply(this._,arguments)},setDay:function(){po.setUTCDay.apply(this._,arguments)},setFullYear:function(){po.setUTCFullYear.apply(this._,arguments)},setHours:function(){po.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){po.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){po.setUTCMinutes.apply(this._,arguments)},setMonth:function(){po.setUTCMonth.apply(this._,arguments)},setSeconds:function(){po.setUTCSeconds.apply(this._,arguments)},setTime:function(){po.setTime.apply(this._,arguments)}};var po=Date.prototype;ho.year=On(function(n){return n=ho.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ho.years=ho.year.range,ho.years.utc=ho.year.utc.range,ho.day=On(function(n){var t=new go(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ho.days=ho.day.range,ho.days.utc=ho.day.utc.range,ho.dayOfYear=function(n){var t=ho.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ho[n]=On(function(n){return(n=ho.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ho.year(n).getDay();return Math.floor((ho.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ho[n+"s"]=e.range,ho[n+"s"].utc=e.utc.range,ho[n+"OfYear"]=function(n){var e=ho.year(n).getDay();return Math.floor((ho.dayOfYear(n)+(e+t)%7)/7)}}),ho.week=ho.sunday,ho.weeks=ho.sunday.range,ho.weeks.utc=ho.sunday.utc.range,ho.weekOfYear=ho.sundayOfYear;var vo={"-":"",_:" ",0:"0"},mo=/^\s*\d+/,yo=/^%/;oa.locale=function(n){return{numberFormat:Un(n),timeFormat:Yn(n)}};var Mo=oa.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], +shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});oa.format=Mo.numberFormat,oa.geo={},st.prototype={s:0,t:0,add:function(n){ft(n,this.t,xo),ft(xo.s,this.s,this),this.s?this.t+=xo.t:this.s=xo.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var xo=new st;oa.geo.stream=function(n,t){n&&bo.hasOwnProperty(n.type)?bo[n.type](n,t):ht(n,t)};var bo={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*ja+n:n,ko.lineStart=ko.lineEnd=ko.point=b}};oa.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=dt([t*Oa,e*Oa]);if(m){var u=yt(m,r),i=[u[1],-u[0],0],a=yt(i,u);bt(a),a=_t(a);var l=t-p,c=l>0?1:-1,v=a[0]*Ia*c,d=Ma(l)>180;if(d^(v>c*p&&c*t>v)){var y=a[1]*Ia;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>c*p&&c*t>v)){var y=-a[1]*Ia;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=Ma(r)>180?r+(r>0?360:-360):r}else v=n,d=e;ko.point(n,e),t(n,e)}function i(){ko.lineStart()}function a(){u(v,d),ko.lineEnd(),Ma(y)>Da&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function o(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nSo?(s=-(h=180),f=-(g=90)):y>Da?g=90:-Da>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],oa.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],c(e[0],u)||c(e[1],u)?(o(u[0],e[1])>o(u[0],u[1])&&(u[1]=e[1]),o(e[0],u[1])>o(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var a,e,p=-(1/0),t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(a=o(u[1],e[0]))>p&&(p=a,s=e[0],h=u[1])}return M=x=null,s===1/0||f===1/0?[[NaN,NaN],[NaN,NaN]]:[[s,f],[h,g]]}}(),oa.geo.centroid=function(n){No=Eo=Ao=Co=zo=Lo=qo=To=Ro=Do=Po=0,oa.geo.stream(n,jo);var t=Ro,e=Do,r=Po,u=t*t+e*e+r*r;return Pa>u&&(t=Lo,e=qo,r=To,Da>Eo&&(t=Ao,e=Co,r=zo),u=t*t+e*e+r*r,Pa>u)?[NaN,NaN]:[Math.atan2(e,t)*Ia,tn(r/Math.sqrt(u))*Ia]};var No,Eo,Ao,Co,zo,Lo,qo,To,Ro,Do,Po,jo={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){jo.lineStart=At},polygonEnd:function(){jo.lineStart=Nt}},Uo=Rt(zt,Ut,Ht,[-ja,-ja/2]),Fo=1e9;oa.geo.clipExtent=function(){var n,t,e,r,u,i,a={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(o){return arguments.length?(i=Zt(n=+o[0][0],t=+o[0][1],e=+o[1][0],r=+o[1][1]),u&&(u.valid=!1,u=null),a):[[n,t],[e,r]]}};return a.extent([[0,0],[960,500]])},(oa.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,oa.geo.albers=function(){return oa.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},oa.geo.albersUsa=function(){function n(n){var i=n[0],a=n[1];return t=null,e(i,a),t||(r(i,a),t)||u(i,a),t}var t,e,r,u,i=oa.geo.albers(),a=oa.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),o=oa.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?a:u>=.166&&.234>u&&r>=-.214&&-.115>r?o:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=a.stream(n),r=o.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),a.precision(t),o.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),a.scale(.35*t),o.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var c=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*c,f-.238*c],[s+.455*c,f+.238*c]]).stream(l).point,r=a.translate([s-.307*c,f+.201*c]).clipExtent([[s-.425*c+Da,f+.12*c+Da],[s-.214*c-Da,f+.234*c-Da]]).stream(l).point,u=o.translate([s-.205*c,f+.212*c]).clipExtent([[s-.214*c+Da,f+.166*c+Da],[s-.115*c-Da,f+.234*c-Da]]).stream(l).point,n},n.scale(1070)};var Ho,Oo,Io,Yo,Zo,Vo,Xo={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Oo=0,Xo.lineStart=$t},polygonEnd:function(){Xo.lineStart=Xo.lineEnd=Xo.point=b,Ho+=Ma(Oo/2)}},$o={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Bo={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Bo.lineStart=ne},polygonEnd:function(){Bo.point=Gt,Bo.lineStart=Kt,Bo.lineEnd=Qt}};oa.geo.path=function(){function n(n){return n&&("function"==typeof o&&i.pointRadius(+o.apply(this,arguments)),a&&a.valid||(a=u(i)),oa.geo.stream(n,a)),i.result()}function t(){return a=null,n}var e,r,u,i,a,o=4.5;return n.area=function(n){return Ho=0,oa.geo.stream(n,u(Xo)),Ho},n.centroid=function(n){return Ao=Co=zo=Lo=qo=To=Ro=Do=Po=0,oa.geo.stream(n,u(Bo)),Po?[Ro/Po,Do/Po]:To?[Lo/To,qo/To]:zo?[Ao/zo,Co/zo]:[NaN,NaN]},n.bounds=function(n){return Zo=Vo=-(Io=Yo=1/0),oa.geo.stream(n,u($o)),[[Io,Yo],[Zo,Vo]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||re(n):y,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new Wt:new te(n),"function"!=typeof o&&i.pointRadius(o),t()):r},n.pointRadius=function(t){return arguments.length?(o="function"==typeof t?t:(i.pointRadius(+t),+t),n):o},n.projection(oa.geo.albersUsa()).context(null)},oa.geo.transform=function(n){return{stream:function(t){var e=new ue(t);for(var r in n)e[r]=n[r];return e}}},ue.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},oa.geo.projection=ae,oa.geo.projectionMutator=oe,(oa.geo.equirectangular=function(){return ae(ce)}).raw=ce.invert=ce,oa.geo.rotation=function(n){function t(t){return t=n(t[0]*Oa,t[1]*Oa),t[0]*=Ia,t[1]*=Ia,t}return n=fe(n[0]%360*Oa,n[1]*Oa,n.length>2?n[2]*Oa:0),t.invert=function(t){return t=n.invert(t[0]*Oa,t[1]*Oa),t[0]*=Ia,t[1]*=Ia,t},t},se.invert=ce,oa.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=fe(-n[0]*Oa,-n[1]*Oa,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ia,n[1]*=Ia}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Oa,u*Oa),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Oa,(u=+r)*Oa),n):u},n.angle(90)},oa.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Oa,u=n[1]*Oa,i=t[1]*Oa,a=Math.sin(r),o=Math.cos(r),l=Math.sin(u),c=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*a)*e+(e=c*s-l*f*o)*e),l*s+c*f*o)},oa.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return oa.range(Math.ceil(i/d)*d,u,d).map(h).concat(oa.range(Math.ceil(c/m)*m,l,m).map(g)).concat(oa.range(Math.ceil(r/p)*p,e,p).filter(function(n){return Ma(n%d)>Da}).map(s)).concat(oa.range(Math.ceil(o/v)*v,a,v).filter(function(n){return Ma(n%m)>Da}).map(f))}var e,r,u,i,a,o,l,c,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(l).slice(1),h(u).reverse().slice(1),g(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],c=+t[0][1],l=+t[1][1],i>u&&(t=i,i=u,u=t),c>l&&(t=c,c=l,l=t),n.precision(y)):[[i,c],[u,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],o=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),o>a&&(t=o,o=a,a=t),n.precision(y)):[[r,o],[e,a]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=me(o,a,90),f=ye(r,e,y),h=me(c,l,90),g=ye(i,u,y),n):y},n.majorExtent([[-180,-90+Da],[180,90-Da]]).minorExtent([[-180,-80-Da],[180,80+Da]])},oa.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=Me,u=xe;return n.distance=function(){return oa.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},oa.geo.interpolate=function(n,t){return be(n[0]*Oa,n[1]*Oa,t[0]*Oa,t[1]*Oa)},oa.geo.length=function(n){return Wo=0,oa.geo.stream(n,Jo),Wo};var Wo,Jo={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Go=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(oa.geo.azimuthalEqualArea=function(){return ae(Go)}).raw=Go;var Ko=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},y);(oa.geo.azimuthalEquidistant=function(){return ae(Ko)}).raw=Ko,(oa.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(oa.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var Qo=we(function(n){return 1/n},Math.atan);(oa.geo.gnomonic=function(){return ae(Qo)}).raw=Qo,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Ha]},(oa.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var nl=we(function(){return 1},Math.asin);(oa.geo.orthographic=function(){return ae(nl)}).raw=nl;var tl=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(oa.geo.stereographic=function(){return ae(tl)}).raw=tl,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Ha]},(oa.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,oa.geom={},oa.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=En(e),i=En(r),a=n.length,o=[],l=[];for(t=0;a>t;t++)o.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(o.sort(qe),t=0;a>t;t++)l.push([o[t][0],-o[t][1]]);var c=Le(o),s=Le(l),f=s[0]===c[0],h=s[s.length-1]===c[c.length-1],g=[];for(t=c.length-1;t>=0;--t)g.push(n[o[c[t]][2]]);for(t=+f;t=r&&c.x<=i&&c.y>=u&&c.y<=a?[[r,a],[i,a],[i,u],[r,u]]:[];s.point=n[o]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Da)*Da,y:Math.round(a(n,t)/Da)*Da,i:t}})}var r=Ce,u=ze,i=r,a=u,o=sl;return n?t(n):(t.links=function(n){return or(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return or(e(n)).cells.forEach(function(e,r){for(var u,i,a=e.site,o=e.edges.sort(Ve),l=-1,c=o.length,s=o[c-1].edge,f=s.l===a?s.r:s.l;++l=c,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=hr()),f?u=c:o=c,h?a=s:l=s,i(n,t,e,r,u,a,o,l)}var s,f,h,g,p,v,d,m,y,M=En(o),x=En(l);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,a)for(g=0;p>g;++g)s=n[g],s.xm&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=hr();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){gr(n,k,v,d,m,y)},k.find=function(n){return pr(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=pl.get(e)||gl,r=vl.get(r)||y,br(r(e.apply(null,la.call(arguments,1))))},oa.interpolateHcl=Rr,oa.interpolateHsl=Dr,oa.interpolateLab=Pr,oa.interpolateRound=jr,oa.transform=function(n){var t=sa.createElementNS(oa.ns.prefix.svg,"g");return(oa.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Ur(e?e.matrix:dl)})(n)},Ur.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var dl={a:1,b:0,c:0,d:1,e:0,f:0};oa.interpolateTransform=$r,oa.layout={},oa.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++eo*o/m){if(v>l){var c=t.charge/l;n.px-=i*c,n.py-=a*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=i*c,n.py-=a*c}}return!t.charge}}function t(n){n.px=oa.event.x,n.py=oa.event.y,l.resume()}var e,r,u,i,a,o,l={},c=oa.dispatch("start","tick","end"),s=[1,1],f=.9,h=ml,g=yl,p=-30,v=Ml,d=.1,m=.64,M=[],x=[];return l.tick=function(){if((u*=.99)<.005)return e=null,c.end({type:"end",alpha:u=0}),!0;var t,r,l,h,g,v,m,y,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,g=l.target,y=g.x-h.x,b=g.y-h.y,(v=y*y+b*b)&&(v=u*a[r]*((v=Math.sqrt(v))-i[r])/v,y*=v,b*=v,g.x-=y*(m=h.weight+g.weight?h.weight/(h.weight+g.weight):.5),g.y-=b*m,h.x+=y*(m=1-m),h.y+=b*m);if((m=u*d)&&(y=s[0]/2,b=s[1]/2,r=-1,m))for(;++r<_;)l=M[r],l.x+=(y-l.x)*m,l.y+=(b-l.y)*m;if(p)for(ru(t=oa.geom.quadtree(M),u,o),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*f,l.y-=(l.py-(l.py=l.y))*f);c.tick({type:"tick",alpha:u})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(s=n,l):s},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.friction=function(n){return arguments.length?(f=+n,l):f},l.charge=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(m=n*n,l):Math.sqrt(m)},l.alpha=function(n){return arguments.length?(n=+n,u?n>0?u=n:(e.c=null,e.t=NaN,e=null,c.start({type:"end",alpha:u=0})):n>0&&(c.start({type:"start",alpha:u=n}),e=qn(l.tick)),l):u},l.start=function(){function n(n,r){if(!e){for(e=new Array(u),l=0;u>l;++l)e[l]=[];for(l=0;c>l;++l){var i=x[l];e[i.source.index].push(i.target),e[i.target.index].push(i.source)}}for(var a,o=e[t],l=-1,s=o.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;u>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",f)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(i=[],"function"==typeof h)for(t=0;c>t;++t)i[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)i[t]=h;if(a=[],"function"==typeof g)for(t=0;c>t;++t)a[t]=+g.call(this,x[t],t);else for(t=0;c>t;++t)a[t]=g;if(o=[],"function"==typeof p)for(t=0;u>t;++t)o[t]=+p.call(this,M[t],t);else for(t=0;u>t;++t)o[t]=p;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=oa.behavior.drag().origin(y).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",nu)),arguments.length?void this.on("mouseover.force",tu).on("mouseout.force",eu).call(r):r},oa.rebind(l,c,"on")};var ml=20,yl=1,Ml=1/0;oa.layout.hierarchy=function(){function n(u){var i,a=[u],o=[];for(u.depth=0;null!=(i=a.pop());)if(o.push(i),(c=e.call(n,i,i.depth))&&(l=c.length)){for(var l,c,s;--l>=0;)a.push(s=c[l]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=c}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return au(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),o}var t=cu,e=ou,r=lu;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(iu(t,function(n){n.children&&(n.value=0)}),au(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},oa.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(a=i.length)){var a,o,l,c=-1;for(r=t.value?r/t.value:0;++cf?-1:1),p=oa.sum(c),v=p?(f-l*g)/p:0,d=oa.range(l),m=[];return null!=e&&d.sort(e===xl?function(n,t){return c[t]-c[n]}:function(n,t){return e(a[n],a[t])}),d.forEach(function(n){m[n]={data:a[n],value:o=c[n],startAngle:s,endAngle:s+=o*v+g,padAngle:h}}),m}var t=Number,e=xl,r=0,u=Ua,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var xl={};oa.layout.stack=function(){function n(o,l){if(!(h=o.length))return o;var c=o.map(function(e,r){return t.call(n,e,r)}),s=c.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),a.call(n,t,e)]})}),f=e.call(n,s,l);c=oa.permute(c,f),s=oa.permute(s,f);var h,g,p,v,d=r.call(n,s,l),m=c[0].length;for(p=0;m>p;++p)for(u.call(n,c[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,c[g][p],v+=s[g-1][p][1],s[g][p][1]);return o}var t=y,e=pu,r=vu,u=gu,i=fu,a=hu;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:bl.get(t)||pu,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:_l.get(t)||vu,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(a=t,n):a},n.out=function(t){return arguments.length?(u=t,n):u},n};var bl=oa.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(du),i=n.map(mu),a=oa.range(r).sort(function(n,t){return u[n]-u[t]}),o=0,l=0,c=[],s=[];for(t=0;r>t;++t)e=a[t],l>o?(o+=i[e],c.push(e)):(l+=i[e],s.push(e));return s.reverse().concat(c)},reverse:function(n){return oa.range(n.length).reverse()},"default":pu}),_l=oa.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,a=[],o=0,l=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>o&&(o=r),a.push(r)}for(e=0;i>e;++e)l[e]=(o-a[e])/2;return l},wiggle:function(n){var t,e,r,u,i,a,o,l,c,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=l=c=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,o=f[e][0]-f[e-1][0];s>t;++t){for(r=0,a=(n[t][e][1]-n[t][e-1][1])/(2*o);t>r;++r)a+=(n[r][e][1]-n[r][e-1][1])/o;i+=a*n[t][e][1]}g[e]=l-=u?i/u*o:0,c>l&&(c=l)}for(e=0;h>e;++e)g[e]-=c;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,a=1/u,o=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=a}for(e=0;i>e;++e)o[e]=0;return o},zero:vu});oa.layout.histogram=function(){function n(n,i){for(var a,o,l=[],c=n.map(e,this),s=r.call(this,c,i),f=u.call(this,s,c,i),i=-1,h=c.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=s[0]&&o<=s[1]&&(a=l[oa.bisect(f,o,1,g)-1],a.y+=p,a.push(n[i]));return l}var t=!0,e=Number,r=bu,u=Mu;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return xu(n,t)}:En(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},oa.layout.pack=function(){function n(n,i){var a=e.call(this,n,i),o=a[0],l=u[0],c=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(o.x=o.y=0,au(o,function(n){n.r=+s(n.value)}),au(o,Nu),r){var f=r*(t?1:Math.max(2*o.r/l,2*o.r/c))/2;au(o,function(n){n.r+=f}),au(o,Nu),au(o,function(n){n.r-=f})}return Cu(o,l/2,c/2,t?1:1/Math.max(2*o.r/l,2*o.r/c)),a}var t,e=oa.layout.hierarchy().sort(_u),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},uu(n,e)},oa.layout.tree=function(){function n(n,u){var s=a.call(this,n,u),f=s[0],h=t(f);if(au(h,e),h.parent.m=-h.z,iu(h,r),c)iu(f,i);else{var g=f,p=f,v=f;iu(f,function(n){n.xp.x&&(p=n),n.depth>v.depth&&(v=n)});var d=o(g,p)/2-g.x,m=l[0]/(p.x+o(p,g)/2+d),y=l[1]/(v.depth||1);iu(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,a=0,o=i.length;o>a;++a)r.push((i[a]=u={_:i[a],parent:t,children:(u=i[a].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:a}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Du(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+o(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+o(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,a=t,l=u.parent.children[0],c=u.m,s=i.m,f=a.m,h=l.m;a=Tu(a),u=qu(u),a&&u;)l=qu(l),i=Tu(i),i.a=n,r=a.z+f-u.z-c+o(a._,u._),r>0&&(Ru(Pu(a,n,e),n,r),c+=r,s+=r),f+=a.m,c+=u.m,h+=l.m,s+=i.m;a&&!Tu(i)&&(i.t=a,i.m+=f-s),u&&!qu(l)&&(l.t=u,l.m+=c-h,e=n)}return e}function i(n){n.x*=l[0],n.y=n.depth*l[1]}var a=oa.layout.hierarchy().sort(null).value(null),o=Lu,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(o=t,n):o},n.size=function(t){return arguments.length?(c=null==(l=t)?i:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:i,n):c?l:null},uu(n,a)},oa.layout.cluster=function(){function n(n,i){var a,o=t.call(this,n,i),l=o[0],c=0;au(l,function(n){var t=n.children;t&&t.length?(n.x=Uu(t),n.y=ju(t)):(n.x=a?c+=e(n,a):0,n.y=0,a=n)});var s=Fu(l),f=Hu(l),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return au(l,u?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),o}var t=oa.layout.hierarchy().sort(null).value(null),e=Lu,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},uu(n,t)},oa.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var a,o,l,c=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?c.dx:"dice"===g?c.dy:"slice-dice"===g?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),s.area=0;(l=h.length)>0;)s.push(a=h[l-1]),s.area+=a.area,"squarify"!==g||(o=r(s,v))<=p?(h.pop(),p=o):(s.area-=s.pop().area,u(s,v,c,!1),v=Math.min(c.dx,c.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,c,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,a=f(t),o=r.slice(),l=[];for(n(o,a.dx*a.dy/t.value),l.area=0;i=o.pop();)l.push(i),l.area+=i.area,null!=i.z&&(u(l,i.z?a.dx:a.dy,a,!o.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,a=-1,o=n.length;++ae&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,a=n.length,o=e.x,c=e.y,s=t?l(n.area/t):0; +if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++ie.dx)&&(s=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=oa.random.normal.apply(oa,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=oa.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},oa.scale={};var wl={floor:y,ceil:y};oa.scale.linear=function(){return Wu([0,1],[0,1],Mr,!1)};var Sl={s:1,g:1,p:1,r:1,e:1};oa.scale.log=function(){return ri(oa.scale.linear().domain([0,1]),10,!0,[1,10])};var kl=oa.format(".0e"),Nl={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};oa.scale.pow=function(){return ui(oa.scale.linear(),1,[0,1])},oa.scale.sqrt=function(){return oa.scale.pow().exponent(.5)},oa.scale.ordinal=function(){return ai([],{t:"range",a:[[]]})},oa.scale.category10=function(){return oa.scale.ordinal().range(El)},oa.scale.category20=function(){return oa.scale.ordinal().range(Al)},oa.scale.category20b=function(){return oa.scale.ordinal().range(Cl)},oa.scale.category20c=function(){return oa.scale.ordinal().range(zl)};var El=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Al=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),Cl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),zl=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);oa.scale.quantile=function(){return oi([],[])},oa.scale.quantize=function(){return li(0,1,[0,1])},oa.scale.threshold=function(){return ci([.5],[0,1])},oa.scale.identity=function(){return si([0,1])},oa.svg={},oa.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),s=a.apply(this,arguments)-Ha,f=o.apply(this,arguments)-Ha,h=Math.abs(f-s),g=s>f?0:1;if(n>c&&(p=c,c=n,n=p),h>=Fa)return t(c,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,N=0,E=0,A=[];if((m=(+l.apply(this,arguments)||0)/2)&&(d=i===Ll?Math.sqrt(n*n+c*c):+i.apply(this,arguments),g||(E*=-1),c&&(E=tn(d/c*Math.sin(m))),n&&(N=tn(d/n*Math.sin(m)))),c){y=c*Math.cos(s+E),M=c*Math.sin(s+E),x=c*Math.cos(f-E),b=c*Math.sin(f-E);var C=Math.abs(f-s-2*E)<=ja?0:1;if(E&&mi(y,M,x,b)===g^C){var z=(s+f)/2;y=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-N),w=n*Math.sin(f-N),S=n*Math.cos(s+N),k=n*Math.sin(s+N);var L=Math.abs(s-f+2*N)<=ja?0:1;if(N&&mi(_,w,S,k)===1-g^L){var q=(s+f)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Da&&(p=Math.min(Math.abs(c-n)/2,+u.apply(this,arguments)))>.001){v=c>n^g?0:1;var T=p,R=p;if(ja>h){var D=null==S?[_,w]:null==x?[y,M]:Re([y,M],[S,k],[x,b],[_,w]),P=y-D[0],j=M-D[1],U=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*U+j*F)/(Math.sqrt(P*P+j*j)*Math.sqrt(U*U+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(p,(n-O)/(H-1)),T=Math.min(p,(c-O)/(H+1))}if(null!=x){var I=yi(null==S?[_,w]:[S,k],[y,M],c,T,g),Y=yi([x,b],[_,w],c,T,g);p===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-g^mi(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",g," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",y,",",M);if(null!=S){var Z=yi([y,M],[S,k],n,-R,g),V=yi([_,w],null==x?[y,M]:[x,b],n,-R,g);p===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^mi(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",y,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",g," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-g," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hi,r=gi,u=fi,i=Ll,a=pi,o=vi,l=di;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=En(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==Ll?Ll:En(t),n):i},n.startAngle=function(t){return arguments.length?(a=En(t),n):a},n.endAngle=function(t){return arguments.length?(o=En(t),n):o},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+a.apply(this,arguments)+ +o.apply(this,arguments))/2-Ha;return[Math.cos(t)*n,Math.sin(t)*n]},n};var Ll="auto";oa.svg.line=function(){return Mi(y)};var ql=oa.map({linear:xi,"linear-closed":bi,step:_i,"step-before":wi,"step-after":Si,basis:zi,"basis-open":Li,"basis-closed":qi,bundle:Ti,cardinal:Ei,"cardinal-open":ki,"cardinal-closed":Ni,monotone:Fi});ql.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Tl=[0,2/3,1/3,0],Rl=[0,1/3,2/3,0],Dl=[0,1/6,2/3,1/6];oa.svg.line.radial=function(){var n=Mi(Hi);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wi.reverse=Si,Si.reverse=wi,oa.svg.area=function(){return Oi(y)},oa.svg.area.radial=function(){var n=Oi(Hi);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},oa.svg.chord=function(){function n(n,o){var l=t(this,i,n,o),c=t(this,a,n,o);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?u(l.r,l.p1,l.r,l.p0):u(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+u(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=o.call(n,u,r),a=l.call(n,u,r)-Ha,s=c.call(n,u,r)-Ha;return{r:i,a0:a,a1:s,p0:[i*Math.cos(a),i*Math.sin(a)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>ja)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=Me,a=xe,o=Ii,l=pi,c=vi;return n.radius=function(t){return arguments.length?(o=En(t),n):o},n.source=function(t){return arguments.length?(i=En(t),n):i},n.target=function(t){return arguments.length?(a=En(t),n):a},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},oa.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),a=e.call(this,n,u),o=(i.y+a.y)/2,l=[i,{x:i.x,y:o},{x:a.x,y:o},a];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yi;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},oa.svg.diagonal.radial=function(){var n=oa.svg.diagonal(),t=Yi,e=n.projection;return n.projection=function(n){return arguments.length?e(Zi(t=n)):t},n},oa.svg.symbol=function(){function n(n,r){return(Pl.get(t.call(this,n,r))||$i)(e.call(this,n,r))}var t=Xi,e=Vi;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Pl=oa.map({circle:$i,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Ul)),e=t*Ul;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});oa.svg.symbolTypes=Pl.keys();var jl=Math.sqrt(3),Ul=Math.tan(30*Oa);Aa.transition=function(n){for(var t,e,r=Fl||++Yl,u=Ki(n),i=[],a=Hl||{time:Date.now(),ease:Nr,delay:0,duration:250},o=-1,l=this.length;++oi;i++){u.push(t=[]);for(var e=this[i],o=0,l=e.length;l>o;o++)(r=e[o])&&n.call(r,r.__data__,o,i)&&t.push(r)}return Wi(u,this.namespace,this.id)},Il.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Il.attr=function(n,t){function e(){this.removeAttribute(o)}function r(){this.removeAttributeNS(o.space,o.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(o);return e!==n&&(t=a(e,n),function(n){this.setAttribute(o,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(o.space,o.local);return e!==n&&(t=a(e,n),function(n){this.setAttributeNS(o.space,o.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var a="transform"==n?$r:Mr,o=oa.ns.qualify(n);return Ji(this,"attr."+n,t,o.local?i:u)},Il.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=oa.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Il.style=function(n,e,r){function u(){this.style.removeProperty(n)}function i(e){return null==e?u:(e+="",function(){var u,i=t(this).getComputedStyle(this,null).getPropertyValue(n);return i!==e&&(u=Mr(i,e),function(t){this.style.setProperty(n,u(t),r)})})}var a=arguments.length;if(3>a){if("string"!=typeof n){2>a&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ji(this,"style."+n,e,i)},Il.styleTween=function(n,e,r){function u(u,i){var a=e.call(this,u,i,t(this).getComputedStyle(this,null).getPropertyValue(n));return a&&function(t){this.style.setProperty(n,a(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,u)},Il.text=function(n){return Ji(this,"text",n,Gi)},Il.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Il.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=oa.ease.apply(oa,arguments)),Y(this,function(r){r[e][t].ease=n}))},Il.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Il.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Il.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=Hl,i=Fl;try{Fl=e,Y(this,function(t,u,i){Hl=t[r][e],n.call(t,t.__data__,u,i)})}finally{Hl=u,Fl=i}}else Y(this,function(u){var i=u[r][e];(i.event||(i.event=oa.dispatch("start","end","interrupt"))).on(n,t)});return this},Il.transition=function(){for(var n,t,e,r,u=this.id,i=++Yl,a=this.namespace,o=[],l=0,c=this.length;c>l;l++){o.push(n=[]);for(var t=this[l],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[a][u],Qi(e,s,a,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wi(o,a,i)},oa.svg.axis=function(){function n(n){n.each(function(){var n,c=oa.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==l?f.ticks?f.ticks.apply(f,o):f.domain():l,g=null==t?f.tickFormat?f.tickFormat.apply(f,o):y:t,p=c.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Da),d=oa.transition(p.exit()).style("opacity",Da).remove(),m=oa.transition(p.order()).style("opacity",1),M=Math.max(u,0)+a,x=Zu(f),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),oa.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=m.select("line"),C=p.select("text").text(g),z=v.select("text"),L=m.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=na,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*i+"V0H"+x[1]+"V"+q*i)):(n=ta,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*i+","+x[0]+"H0V"+x[1]+"H"+q*i)),E.attr(N,q*u),z.attr(k,q*M),A.attr(S,0).attr(N,q*u),L.attr(w,0).attr(k,q*M),f.rangeBand){var T=f,R=T.rangeBand()/2;s=f=function(n){return T(n)+R}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=oa.scale.linear(),r=Zl,u=6,i=6,a=3,o=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Vl?t+"":Zl,n):r},n.ticks=function(){return arguments.length?(o=ca(arguments),n):o},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(a=+t,n):a},n.tickSubdivide=function(){return arguments.length&&n},n};var Zl="bottom",Vl={top:1,right:1,bottom:1,left:1};oa.svg.brush=function(){function n(t){t.each(function(){var t=oa.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",i).on("touchstart.brush",i),a=t.selectAll(".background").data([0]);a.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var o=t.selectAll(".resize").data(v,y);o.exit().remove(),o.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Xl[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),o.style("display",n.empty()?"none":null);var l,f=oa.transition(t),h=oa.transition(a);c&&(l=Zu(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(f)),s&&(l=Zu(s),h.attr("y",l[0]).attr("height",l[1]-l[0]),u(f)),e(f)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+f[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",f[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1]-f[0])}function u(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function i(){function i(){32==oa.event.keyCode&&(C||(M=null,L[0]-=f[1],L[1]-=h[1],C=2),S())}function v(){32==oa.event.keyCode&&2==C&&(L[0]+=f[1],L[1]+=h[1],C=0,S())}function d(){var n=oa.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(oa.event.altKey?(M||(M=[(f[0]+f[1])/2,(h[0]+h[1])/2]),L[0]=f[+(n[0]s?(u=r,r=s):u=s),v[0]!=r||v[1]!=u?(e?o=null:a=null,v[0]=r,v[1]=u,!0):void 0}function y(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),oa.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=oa.select(oa.event.target),w=l.of(b,arguments),k=oa.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&s,C=_.classed("extent"),z=W(b),L=oa.mouse(b),q=oa.select(t(b)).on("keydown.brush",i).on("keyup.brush",v);if(oa.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",y):q.on("mousemove.brush",d).on("mouseup.brush",y),k.interrupt().selectAll("*").interrupt(),C)L[0]=f[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[f[1-T]-L[0],h[1-R]-L[1]],L[0]=f[T],L[1]=h[R]}else oa.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),oa.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var a,o,l=N(n,"brushstart","brush","brushend"),c=null,s=null,f=[0,0],h=[0,0],g=!0,p=!0,v=$l[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:f,y:h,i:a,j:o},e=this.__chart__||t;this.__chart__=t,Fl?oa.select(this).transition().each("start.brush",function(){a=e.i,o=e.j,f=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(f,t.x),r=xr(h,t.y);return a=o=null,function(u){f=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){a=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=$l[!c<<1|!s],n):c},n.y=function(t){return arguments.length?(s=t,v=$l[!c<<1|!s],n):s},n.clamp=function(t){return arguments.length?(c&&s?(g=!!t[0],p=!!t[1]):c?g=!!t:s&&(p=!!t),n):c&&s?[g,p]:c?g:s?p:null},n.extent=function(t){var e,r,u,i,l;return arguments.length?(c&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),a=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),(e!=f[0]||r!=f[1])&&(f=[e,r])),s&&(u=t[0],i=t[1],c&&(u=u[1],i=i[1]),o=[u,i],s.invert&&(u=s(u),i=s(i)),u>i&&(l=u,u=i,i=l),(u!=h[0]||i!=h[1])&&(h=[u,i])),n):(c&&(a?(e=a[0],r=a[1]):(e=f[0],r=f[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),s&&(o?(u=o[0],i=o[1]):(u=h[0],i=h[1],s.invert&&(u=s.invert(u),i=s.invert(i)),u>i&&(l=u,u=i,i=l))),c&&s?[[e,u],[r,i]]:c?[e,r]:s&&[u,i])},n.clear=function(){return n.empty()||(f=[0,0],h=[0,0],a=o=null),n},n.empty=function(){return!!c&&f[0]==f[1]||!!s&&h[0]==h[1]},oa.rebind(n,l,"on")};var Xl={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},$l=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Bl=ho.format=Mo.timeFormat,Wl=Bl.utc,Jl=Wl("%Y-%m-%dT%H:%M:%S.%LZ");Bl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?ea:Jl,ea.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},ea.toString=Jl.toString,ho.second=On(function(n){return new go(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ho.seconds=ho.second.range,ho.seconds.utc=ho.second.utc.range,ho.minute=On(function(n){return new go(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ho.minutes=ho.minute.range,ho.minutes.utc=ho.minute.utc.range,ho.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new go(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ho.hours=ho.hour.range,ho.hours.utc=ho.hour.utc.range,ho.month=On(function(n){return n=ho.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ho.months=ho.month.range,ho.months.utc=ho.month.utc.range;var Gl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Kl=[[ho.second,1],[ho.second,5],[ho.second,15],[ho.second,30],[ho.minute,1],[ho.minute,5],[ho.minute,15],[ho.minute,30],[ho.hour,1],[ho.hour,3],[ho.hour,6],[ho.hour,12],[ho.day,1],[ho.day,2],[ho.week,1],[ho.month,1],[ho.month,3],[ho.year,1]],Ql=Bl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),nc={range:function(n,t,e){return oa.range(Math.ceil(n/e)*e,+t,e).map(ua)},floor:y,ceil:y};Kl.year=ho.year,ho.scale=function(){return ra(oa.scale.linear(),Kl,Ql)};var tc=Kl.map(function(n){return[n[0].utc,n[1]]}),ec=Wl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);tc.year=ho.year.utc,ho.scale.utc=function(){return ra(oa.scale.linear(),tc,ec)},oa.text=An(function(n){return n.responseText}),oa.json=function(n,t){return Cn(n,"application/json",ia,t)},oa.html=function(n,t){return Cn(n,"text/html",aa,t)},oa.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=oa,define(oa)):"object"==typeof module&&module.exports?module.exports=oa:this.d3=oa}(); \ No newline at end of file diff --git a/vendor/assets/components/d3/package.js b/vendor/assets/components/d3/package.js new file mode 100644 index 000000000..8d3aa629b --- /dev/null +++ b/vendor/assets/components/d3/package.js @@ -0,0 +1,13 @@ +// Package metadata for Meteor.js. + +Package.describe({ + name: "d3js:d3", // http://atmospherejs.com/d3js/d3 + summary: "D3 (official): A JavaScript visualization library for HTML and SVG.", + version: "3.5.9", + git: "https://github.com/mbostock/d3.git" +}); + +Package.onUse(function(api) { + api.versionsFrom(["METEOR@1.0"]); + api.addFiles("d3.js", "client"); +}); diff --git a/vendor/assets/components/elasticsearch/.bower.json b/vendor/assets/components/elasticsearch/.bower.json new file mode 100644 index 000000000..2f4687db8 --- /dev/null +++ b/vendor/assets/components/elasticsearch/.bower.json @@ -0,0 +1,24 @@ +{ + "name": "elasticsearch", + "version": "3.1.3", + "authors": [ + "Spencer Alger " + ], + "description": "The official low-level Elasticsearch client, for use in the browser.", + "main": "elasticsearch.js", + "keywords": [ + "elasticsearch", + "client" + ], + "license": "Apache 2.0", + "homepage": "https://github.com/elasticsearch/bower-elasticsearch-js", + "_release": "3.1.3", + "_resolution": { + "type": "version", + "tag": "v3.1.3", + "commit": "38573df2b4b7ceda328cbbd484fd0e9cb7283a8d" + }, + "_source": "git://github.com/elasticsearch/bower-elasticsearch-js.git", + "_target": "~3.1.1", + "_originalSource": "elasticsearch" +} \ No newline at end of file diff --git a/vendor/assets/components/elasticsearch/README.md b/vendor/assets/components/elasticsearch/README.md new file mode 100644 index 000000000..ef919c43b --- /dev/null +++ b/vendor/assets/components/elasticsearch/README.md @@ -0,0 +1,50 @@ +# elasticsearch + +Elasticsearch client builds for bower. + +# Install + +Install with `bower` +``` +bower install elasticsearch +``` + +Add a ` + +``` + +## If you are using Angular +Use `elasticsearch.angular.js` instead. This will create an `elasticsearch` module with an `esFactory` that you can use. +``` +/* + * create your app module, specify "elasticsearch" as a dependency + */ +var app = angular.module('myApp', ['elasticsearch']); + +/* + * create a service, which provides your elasticsearch client + * to other parts of your application + */ +app.service('es', function (esFactory) { + return esFactory({ + host: 'localhost:9200', + // ... + }); +}); +``` + +## If you are using jQuery +Use `elasticsearch.jquery.js` instead. Rather than a global `elasticsearch` it will create a `jQuery.es` namespace. +``` +var client = new $.es.Client({ + hosts: 'localhost:9200' +}); +``` + +# Submit Issues, Pull Requests, etc to [elasticsearch-js](https://github.com/elasticsearch/elasticsearch-js). \ No newline at end of file diff --git a/vendor/assets/components/elasticsearch/bower.json b/vendor/assets/components/elasticsearch/bower.json new file mode 100644 index 000000000..c3861dd8b --- /dev/null +++ b/vendor/assets/components/elasticsearch/bower.json @@ -0,0 +1,14 @@ +{ + "name": "elasticsearch", + "version": "3.1.3", + "authors": [ + "Spencer Alger " + ], + "description": "The official low-level Elasticsearch client, for use in the browser.", + "main": "elasticsearch.js", + "keywords": [ + "elasticsearch", + "client" + ], + "license": "Apache 2.0" +} \ No newline at end of file diff --git a/vendor/assets/components/elasticsearch/elasticsearch.angular.js b/vendor/assets/components/elasticsearch/elasticsearch.angular.js new file mode 100644 index 000000000..90be77771 --- /dev/null +++ b/vendor/assets/components/elasticsearch/elasticsearch.angular.js @@ -0,0 +1,35958 @@ +/*! elasticsearch - v3.1.3 - 2015-02-23 + * http://www.elasticsearch.org/guide/en/elasticsearch/client/javascript-api/current/index.html + * Copyright (c) 2015 Elasticsearch BV; Licensed Apache 2.0 */ +;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o i; ++i) { + if (array.hasOwnProperty(i)) { + if (isValueSet) { + value = callback(value, array[i], i, array); + } + else { + value = array[i]; + isValueSet = true; + } + } + } + + return value; +}; + +// String.prototype.substr - negative index don't work in IE8 +if ('ab'.substr(-1) !== 'b') { + exports.substr = function (str, start, length) { + // did we get a negative start, calculate how much it is from the beginning of the string + if (start < 0) start = str.length + start; + + // call the original function + return str.substr(start, length); + }; +} else { + exports.substr = function (str, start, length) { + return str.substr(start, length); + }; +} + +// String.prototype.trim is supported in IE9 +exports.trim = function (str) { + if (str.trim) return str.trim(); + return str.replace(/^\s+|\s+$/g, ''); +}; + +// Function.prototype.bind is supported in IE9 +exports.bind = function () { + var args = Array.prototype.slice.call(arguments); + var fn = args.shift(); + if (fn.bind) return fn.bind.apply(fn, args); + var self = args.shift(); + return function () { + fn.apply(self, args.concat([Array.prototype.slice.call(arguments)])); + }; +}; + +// Object.create is supported in IE9 +function create(prototype, properties) { + var object; + if (prototype === null) { + object = { '__proto__' : null }; + } + else { + if (typeof prototype !== 'object') { + throw new TypeError( + 'typeof prototype[' + (typeof prototype) + '] != \'object\'' + ); + } + var Type = function () {}; + Type.prototype = prototype; + object = new Type(); + object.__proto__ = prototype; + } + if (typeof properties !== 'undefined' && Object.defineProperties) { + Object.defineProperties(object, properties); + } + return object; +} +exports.create = typeof Object.create === 'function' ? Object.create : create; + +// Object.keys and Object.getOwnPropertyNames is supported in IE9 however +// they do show a description and number property on Error objects +function notObject(object) { + return ((typeof object != "object" && typeof object != "function") || object === null); +} + +function keysShim(object) { + if (notObject(object)) { + throw new TypeError("Object.keys called on a non-object"); + } + + var result = []; + for (var name in object) { + if (hasOwnProperty.call(object, name)) { + result.push(name); + } + } + return result; +} + +// getOwnPropertyNames is almost the same as Object.keys one key feature +// is that it returns hidden properties, since that can't be implemented, +// this feature gets reduced so it just shows the length property on arrays +function propertyShim(object) { + if (notObject(object)) { + throw new TypeError("Object.getOwnPropertyNames called on a non-object"); + } + + var result = keysShim(object); + if (exports.isArray(object) && exports.indexOf(object, 'length') === -1) { + result.push('length'); + } + return result; +} + +var keys = typeof Object.keys === 'function' ? Object.keys : keysShim; +var getOwnPropertyNames = typeof Object.getOwnPropertyNames === 'function' ? + Object.getOwnPropertyNames : propertyShim; + +if (new Error().hasOwnProperty('description')) { + var ERROR_PROPERTY_FILTER = function (obj, array) { + if (toString.call(obj) === '[object Error]') { + array = exports.filter(array, function (name) { + return name !== 'description' && name !== 'number' && name !== 'message'; + }); + } + return array; + }; + + exports.keys = function (object) { + return ERROR_PROPERTY_FILTER(object, keys(object)); + }; + exports.getOwnPropertyNames = function (object) { + return ERROR_PROPERTY_FILTER(object, getOwnPropertyNames(object)); + }; +} else { + exports.keys = keys; + exports.getOwnPropertyNames = getOwnPropertyNames; +} + +// Object.getOwnPropertyDescriptor - supported in IE8 but only on dom elements +function valueObject(value, key) { + return { value: value[key] }; +} + +if (typeof Object.getOwnPropertyDescriptor === 'function') { + try { + Object.getOwnPropertyDescriptor({'a': 1}, 'a'); + exports.getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; + } catch (e) { + // IE8 dom element issue - use a try catch and default to valueObject + exports.getOwnPropertyDescriptor = function (value, key) { + try { + return Object.getOwnPropertyDescriptor(value, key); + } catch (e) { + return valueObject(value, key); + } + }; + } +} else { + exports.getOwnPropertyDescriptor = valueObject; +} + +},{}],3:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// UTILITY +var util = require('util'); +var shims = require('_shims'); +var pSlice = Array.prototype.slice; + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = module.exports = ok; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + this.message = options.message || getMessage(this); +}; + +// assert.AssertionError instanceof Error +util.inherits(assert.AssertionError, Error); + +function replacer(key, value) { + if (util.isUndefined(value)) { + return '' + value; + } + if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) { + return value.toString(); + } + if (util.isFunction(value) || util.isRegExp(value)) { + return value.toString(); + } + return value; +} + +function truncate(s, n) { + if (util.isString(s)) { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } +} + +function getMessage(self) { + return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + + self.operator + ' ' + + truncate(JSON.stringify(self.expected, replacer), 128); +} + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, !!guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +function ok(value, message) { + if (!value) fail(value, true, message, '==', assert.ok); +} +assert.ok = ok; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (util.isBuffer(actual) && util.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (util.isDate(actual) && util.isDate(expected)) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (!util.isObject(actual) && !util.isObject(expected)) { + return actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b) { + if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + try { + var ka = shims.keys(a), + kb = shims.keys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (Object.prototype.toString.call(expected) == '[object RegExp]') { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (util.isString(expected)) { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail(actual, expected, 'Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail(actual, expected, 'Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function(err) { if (err) {throw err;}}; +},{"_shims":2,"util":8}],4:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var util = require('util'); + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!util.isNumber(n) || n < 0) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (util.isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + throw TypeError('Uncaught, unspecified "error" event.'); + } + return false; + } + } + + handler = this._events[type]; + + if (util.isUndefined(handler)) + return false; + + if (util.isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + handler.apply(this, args); + } + } else if (util.isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!util.isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + util.isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (util.isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (util.isObject(this._events[type]) && !this._events[type].warned) { + var m; + if (!util.isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + console.trace(); + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!util.isFunction(listener)) + throw TypeError('listener must be a function'); + + function g() { + this.removeListener(type, g); + listener.apply(this, arguments); + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!util.isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (util.isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (util.isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (util.isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (util.isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.listenerCount = function(emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) + ret = 0; + else if (util.isFunction(emitter._events[type])) + ret = 1; + else + ret = emitter._events[type].length; + return ret; +}; +},{"util":8}],5:[function(require,module,exports){ +var process=require("__browserify_process");// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var util = require('util'); +var shims = require('_shims'); + +// resolves . and .. elements in a path array with directory names there +// must be no slashes, empty elements, or device names (c:\) in the array +// (so also no leading and trailing slashes - it does not distinguish +// relative and absolute paths) +function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + + return parts; +} + +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); +}; + +// path.resolve([from ...], to) +// posix version +exports.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); + + // Skip empty and invalid entries + if (!util.isString(path)) { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeArray(shims.filter(resolvedPath.split('/'), function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); + + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; +}; + +// path.normalize(path) +// posix version +exports.normalize = function(path) { + var isAbsolute = exports.isAbsolute(path), + trailingSlash = shims.substr(path, -1) === '/'; + + // Normalize the path + path = normalizeArray(shims.filter(path.split('/'), function(p) { + return !!p; + }), !isAbsolute).join('/'); + + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + + return (isAbsolute ? '/' : '') + path; +}; + +// posix version +exports.isAbsolute = function(path) { + return path.charAt(0) === '/'; +}; + +// posix version +exports.join = function() { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(shims.filter(paths, function(p, index) { + if (!util.isString(p)) { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + }).join('/')); +}; + + +// path.relative(from, to) +// posix version +exports.relative = function(from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('/'); +}; + +exports.sep = '/'; +exports.delimiter = ':'; + +exports.dirname = function(path) { + var result = splitPath(path), + root = result[0], + dir = result[1]; + + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + + return root + dir; +}; + + +exports.basename = function(path, ext) { + var f = splitPath(path)[2]; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; + + +exports.extname = function(path) { + return splitPath(path)[3]; +}; + +},{"__browserify_process":13,"_shims":2,"util":8}],6:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Query String Utilities + +var QueryString = exports; +var util = require('util'); +var shims = require('_shims'); +var Buffer = require('buffer').Buffer; + +// If obj.hasOwnProperty has been overridden, then calling +// obj.hasOwnProperty(prop) will break. +// See: https://github.com/joyent/node/issues/1707 +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + + +function charCode(c) { + return c.charCodeAt(0); +} + + +// a safe fast alternative to decodeURIComponent +QueryString.unescapeBuffer = function(s, decodeSpaces) { + var out = new Buffer(s.length); + var state = 'CHAR'; // states: CHAR, HEX0, HEX1 + var n, m, hexchar; + + for (var inIndex = 0, outIndex = 0; inIndex <= s.length; inIndex++) { + var c = s.charCodeAt(inIndex); + switch (state) { + case 'CHAR': + switch (c) { + case charCode('%'): + n = 0; + m = 0; + state = 'HEX0'; + break; + case charCode('+'): + if (decodeSpaces) c = charCode(' '); + // pass thru + default: + out[outIndex++] = c; + break; + } + break; + + case 'HEX0': + state = 'HEX1'; + hexchar = c; + if (charCode('0') <= c && c <= charCode('9')) { + n = c - charCode('0'); + } else if (charCode('a') <= c && c <= charCode('f')) { + n = c - charCode('a') + 10; + } else if (charCode('A') <= c && c <= charCode('F')) { + n = c - charCode('A') + 10; + } else { + out[outIndex++] = charCode('%'); + out[outIndex++] = c; + state = 'CHAR'; + break; + } + break; + + case 'HEX1': + state = 'CHAR'; + if (charCode('0') <= c && c <= charCode('9')) { + m = c - charCode('0'); + } else if (charCode('a') <= c && c <= charCode('f')) { + m = c - charCode('a') + 10; + } else if (charCode('A') <= c && c <= charCode('F')) { + m = c - charCode('A') + 10; + } else { + out[outIndex++] = charCode('%'); + out[outIndex++] = hexchar; + out[outIndex++] = c; + break; + } + out[outIndex++] = 16 * n + m; + break; + } + } + + // TODO support returning arbitrary buffers. + + return out.slice(0, outIndex - 1); +}; + + +QueryString.unescape = function(s, decodeSpaces) { + return QueryString.unescapeBuffer(s, decodeSpaces).toString(); +}; + + +QueryString.escape = function(str) { + return encodeURIComponent(str); +}; + +var stringifyPrimitive = function(v) { + if (util.isString(v)) + return v; + if (util.isBoolean(v)) + return v ? 'true' : 'false'; + if (util.isNumber(v)) + return isFinite(v) ? v : ''; + return ''; +}; + + +QueryString.stringify = QueryString.encode = function(obj, sep, eq, name) { + sep = sep || '&'; + eq = eq || '='; + if (util.isNull(obj)) { + obj = undefined; + } + + if (util.isObject(obj)) { + return shims.map(shims.keys(obj), function(k) { + var ks = QueryString.escape(stringifyPrimitive(k)) + eq; + if (util.isArray(obj[k])) { + return shims.map(obj[k], function(v) { + return ks + QueryString.escape(stringifyPrimitive(v)); + }).join(sep); + } else { + return ks + QueryString.escape(stringifyPrimitive(obj[k])); + } + }).join(sep); + + } + + if (!name) return ''; + return QueryString.escape(stringifyPrimitive(name)) + eq + + QueryString.escape(stringifyPrimitive(obj)); +}; + +// Parse a key=val string. +QueryString.parse = QueryString.decode = function(qs, sep, eq, options) { + sep = sep || '&'; + eq = eq || '='; + var obj = {}; + + if (!util.isString(qs) || qs.length === 0) { + return obj; + } + + var regexp = /\+/g; + qs = qs.split(sep); + + var maxKeys = 1000; + if (options && util.isNumber(options.maxKeys)) { + maxKeys = options.maxKeys; + } + + var len = qs.length; + // maxKeys <= 0 means that we should not limit keys count + if (maxKeys > 0 && len > maxKeys) { + len = maxKeys; + } + + for (var i = 0; i < len; ++i) { + var x = qs[i].replace(regexp, '%20'), + idx = x.indexOf(eq), + kstr, vstr, k, v; + + if (idx >= 0) { + kstr = x.substr(0, idx); + vstr = x.substr(idx + 1); + } else { + kstr = x; + vstr = ''; + } + + try { + k = decodeURIComponent(kstr); + v = decodeURIComponent(vstr); + } catch (e) { + k = QueryString.unescape(kstr, true); + v = QueryString.unescape(vstr, true); + } + + if (!hasOwnProperty(obj, k)) { + obj[k] = v; + } else if (util.isArray(obj[k])) { + obj[k].push(v); + } else { + obj[k] = [obj[k], v]; + } + } + + return obj; +}; +},{"_shims":2,"buffer":10,"util":8}],7:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var punycode = { encode : function (s) { return s } }; +var util = require('util'); +var shims = require('_shims'); + +exports.parse = urlParse; +exports.resolve = urlResolve; +exports.resolveObject = urlResolveObject; +exports.format = urlFormat; + +exports.Url = Url; + +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.host = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.query = null; + this.pathname = null; + this.path = null; + this.href = null; +} + +// Reference: RFC 3986, RFC 1808, RFC 2396 + +// define these here so at least they only have to be +// compiled once on the first module load. +var protocolPattern = /^([a-z0-9.+-]+:)/i, + portPattern = /:[0-9]*$/, + + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. + delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'], + + // RFC 2396: characters not allowed for various reasons. + unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims), + + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = ['\''].concat(unwise), + // Characters that are never ever allowed in a hostname. + // Note that any invalid chars are also handled, but these + // are the ones that are *expected* to be seen, so we fast-path + // them. + nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape), + hostEndingChars = ['/', '?', '#'], + hostnameMaxLen = 255, + hostnamePartPattern = /^[a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + unsafeProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that never have a hostname. + hostlessProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that always contain a // bit. + slashedProtocol = { + 'http': true, + 'https': true, + 'ftp': true, + 'gopher': true, + 'file': true, + 'http:': true, + 'https:': true, + 'ftp:': true, + 'gopher:': true, + 'file:': true + }, + querystring = require('querystring'); + +function urlParse(url, parseQueryString, slashesDenoteHost) { + if (url && util.isObject(url) && url instanceof Url) return url; + + var u = new Url; + u.parse(url, parseQueryString, slashesDenoteHost); + return u; +} + +Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { + if (!util.isString(url)) { + throw new TypeError("Parameter 'url' must be a string, not " + typeof url); + } + + var rest = url; + + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = shims.trim(rest); + + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + var lowerProto = proto.toLowerCase(); + this.protocol = lowerProto; + rest = rest.substr(proto.length); + } + + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + var slashes = rest.substr(0, 2) === '//'; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } + + if (!hostlessProtocol[proto] && + (slashes || (proto && !slashedProtocol[proto]))) { + + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (var i = 0; i < hostEndingChars.length; i++) { + var hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } + + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@'); + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd); + } + + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = decodeURIComponent(auth); + } + + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (var i = 0; i < nonHostChars.length; i++) { + var hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) + hostEnd = rest.length; + + this.host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + + // pull out port. + this.parseHost(); + + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || ''; + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; + + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (var i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) continue; + if (!part.match(hostnamePartPattern)) { + var newpart = ''; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += 'x'; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = '/' + notHost.join('.') + rest; + } + this.hostname = validParts.join('.'); + break; + } + } + } + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } else { + // hostnames are always lower case. + this.hostname = this.hostname.toLowerCase(); + } + + if (!ipv6Hostname) { + // IDNA Support: Returns a puny coded representation of "domain". + // It only converts the part of the domain name that + // has non ASCII characters. I.e. it dosent matter if + // you call it with a domain that already is in ASCII. + var domainArray = this.hostname.split('.'); + var newOut = []; + for (var i = 0; i < domainArray.length; ++i) { + var s = domainArray[i]; + newOut.push(s.match(/[^A-Za-z0-9_-]/) ? + 'xn--' + punycode.encode(s) : s); + } + this.hostname = newOut.join('.'); + } + + var p = this.port ? ':' + this.port : ''; + var h = this.hostname || ''; + this.host = h + p; + this.href += this.host; + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + if (rest[0] !== '/') { + rest = '/' + rest; + } + } + } + + // now rest is set to the post-host stuff. + // chop off any delim chars. + if (!unsafeProtocol[lowerProto]) { + + // First, make 100% sure that any "autoEscape" chars get + // escaped, even if encodeURIComponent doesn't think they + // need to be. + for (var i = 0, l = autoEscape.length; i < l; i++) { + var ae = autoEscape[i]; + var esc = encodeURIComponent(ae); + if (esc === ae) { + esc = escape(ae); + } + rest = rest.split(ae).join(esc); + } + } + + + // chop off from the tail first. + var hash = rest.indexOf('#'); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf('?'); + if (qm !== -1) { + this.search = rest.substr(qm); + this.query = rest.substr(qm + 1); + if (parseQueryString) { + this.query = querystring.parse(this.query); + } + rest = rest.slice(0, qm); + } else if (parseQueryString) { + // no query string, but parseQueryString still requested + this.search = ''; + this.query = {}; + } + if (rest) this.pathname = rest; + if (slashedProtocol[lowerProto] && + this.hostname && !this.pathname) { + this.pathname = '/'; + } + + //to support http.request + if (this.pathname || this.search) { + var p = this.pathname || ''; + var s = this.search || ''; + this.path = p + s; + } + + // finally, reconstruct the href based on what has been validated. + this.href = this.format(); + return this; +}; + +// format a parsed object into a url string +function urlFormat(obj) { + // ensure it's an object, and not a string url. + // If it's an obj, this is a no-op. + // this way, you can call url_format() on strings + // to clean up potentially wonky urls. + if (util.isString(obj)) obj = urlParse(obj); + if (!(obj instanceof Url)) return Url.prototype.format.call(obj); + return obj.format(); +} + +Url.prototype.format = function() { + var auth = this.auth || ''; + if (auth) { + auth = encodeURIComponent(auth); + auth = auth.replace(/%3A/i, ':'); + auth += '@'; + } + + var protocol = this.protocol || '', + pathname = this.pathname || '', + hash = this.hash || '', + host = false, + query = ''; + + if (this.host) { + host = auth + this.host; + } else if (this.hostname) { + host = auth + (this.hostname.indexOf(':') === -1 ? + this.hostname : + '[' + this.hostname + ']'); + if (this.port) { + host += ':' + this.port; + } + } + + if (this.query && + util.isObject(this.query) && + shims.keys(this.query).length) { + query = querystring.stringify(this.query); + } + + var search = this.search || (query && ('?' + query)) || ''; + + if (protocol && shims.substr(protocol, -1) !== ':') protocol += ':'; + + // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. + // unless they had them to begin with. + if (this.slashes || + (!protocol || slashedProtocol[protocol]) && host !== false) { + host = '//' + (host || ''); + if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname; + } else if (!host) { + host = ''; + } + + if (hash && hash.charAt(0) !== '#') hash = '#' + hash; + if (search && search.charAt(0) !== '?') search = '?' + search; + + pathname = pathname.replace(/[?#]/g, function(match) { + return encodeURIComponent(match); + }); + search = search.replace('#', '%23'); + + return protocol + host + pathname + search + hash; +}; + +function urlResolve(source, relative) { + return urlParse(source, false, true).resolve(relative); +} + +Url.prototype.resolve = function(relative) { + return this.resolveObject(urlParse(relative, false, true)).format(); +}; + +function urlResolveObject(source, relative) { + if (!source) return relative; + return urlParse(source, false, true).resolveObject(relative); +} + +Url.prototype.resolveObject = function(relative) { + if (util.isString(relative)) { + var rel = new Url(); + rel.parse(relative, false, true); + relative = rel; + } + + var result = new Url(); + shims.forEach(shims.keys(this), function(k) { + result[k] = this[k]; + }, this); + + // hash is always overridden, no matter what. + // even href="" will remove it. + result.hash = relative.hash; + + // if the relative url is empty, then there's nothing left to do here. + if (relative.href === '') { + result.href = result.format(); + return result; + } + + // hrefs like //foo/bar always cut to the protocol. + if (relative.slashes && !relative.protocol) { + // take everything except the protocol from relative + shims.forEach(shims.keys(relative), function(k) { + if (k !== 'protocol') + result[k] = relative[k]; + }); + + //urlParse appends trailing / to urls like http://www.example.com + if (slashedProtocol[result.protocol] && + result.hostname && !result.pathname) { + result.path = result.pathname = '/'; + } + + result.href = result.format(); + return result; + } + + if (relative.protocol && relative.protocol !== result.protocol) { + // if it's a known url protocol, then changing + // the protocol does weird things + // first, if it's not file:, then we MUST have a host, + // and if there was a path + // to begin with, then we MUST have a path. + // if it is file:, then the host is dropped, + // because that's known to be hostless. + // anything else is assumed to be absolute. + if (!slashedProtocol[relative.protocol]) { + shims.forEach(shims.keys(relative), function(k) { + result[k] = relative[k]; + }); + result.href = result.format(); + return result; + } + + result.protocol = relative.protocol; + if (!relative.host && !hostlessProtocol[relative.protocol]) { + var relPath = (relative.pathname || '').split('/'); + while (relPath.length && !(relative.host = relPath.shift())); + if (!relative.host) relative.host = ''; + if (!relative.hostname) relative.hostname = ''; + if (relPath[0] !== '') relPath.unshift(''); + if (relPath.length < 2) relPath.unshift(''); + result.pathname = relPath.join('/'); + } else { + result.pathname = relative.pathname; + } + result.search = relative.search; + result.query = relative.query; + result.host = relative.host || ''; + result.auth = relative.auth; + result.hostname = relative.hostname || relative.host; + result.port = relative.port; + // to support http.request + if (result.pathname || result.search) { + var p = result.pathname || ''; + var s = result.search || ''; + result.path = p + s; + } + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; + } + + var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), + isRelAbs = ( + relative.host || + relative.pathname && relative.pathname.charAt(0) === '/' + ), + mustEndAbs = (isRelAbs || isSourceAbs || + (result.host && relative.pathname)), + removeAllDots = mustEndAbs, + srcPath = result.pathname && result.pathname.split('/') || [], + relPath = relative.pathname && relative.pathname.split('/') || [], + psychotic = result.protocol && !slashedProtocol[result.protocol]; + + // if the url is a non-slashed url, then relative + // links like ../.. should be able + // to crawl up to the hostname, as well. This is strange. + // result.protocol has already been set by now. + // Later on, put the first path part into the host field. + if (psychotic) { + result.hostname = ''; + result.port = null; + if (result.host) { + if (srcPath[0] === '') srcPath[0] = result.host; + else srcPath.unshift(result.host); + } + result.host = ''; + if (relative.protocol) { + relative.hostname = null; + relative.port = null; + if (relative.host) { + if (relPath[0] === '') relPath[0] = relative.host; + else relPath.unshift(relative.host); + } + relative.host = null; + } + mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); + } + + if (isRelAbs) { + // it's absolute. + result.host = (relative.host || relative.host === '') ? + relative.host : result.host; + result.hostname = (relative.hostname || relative.hostname === '') ? + relative.hostname : result.hostname; + result.search = relative.search; + result.query = relative.query; + srcPath = relPath; + // fall through to the dot-handling below. + } else if (relPath.length) { + // it's relative + // throw away the existing file, and take the new path instead. + if (!srcPath) srcPath = []; + srcPath.pop(); + srcPath = srcPath.concat(relPath); + result.search = relative.search; + result.query = relative.query; + } else if (!util.isNullOrUndefined(relative.search)) { + // just pull out the search. + // like href='?foo'. + // Put this after the other two cases because it simplifies the booleans + if (psychotic) { + result.hostname = result.host = srcPath.shift(); + //occationaly the auth can get stuck only in host + //this especialy happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); + } + } + result.search = relative.search; + result.query = relative.query; + //to support http.request + if (!util.isNull(result.pathname) || !util.isNull(result.search)) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.href = result.format(); + return result; + } + + if (!srcPath.length) { + // no path at all. easy. + // we've already handled the other stuff above. + result.pathname = null; + //to support http.request + if (result.search) { + result.path = '/' + result.search; + } else { + result.path = null; + } + result.href = result.format(); + return result; + } + + // if a url ENDs in . or .., then it must get a trailing slash. + // however, if it ends in anything else non-slashy, + // then it must NOT get a trailing slash. + var last = srcPath.slice(-1)[0]; + var hasTrailingSlash = ( + (result.host || relative.host) && (last === '.' || last === '..') || + last === ''); + + // strip single dots, resolve double dots to parent dir + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = srcPath.length; i >= 0; i--) { + last = srcPath[i]; + if (last == '.') { + srcPath.splice(i, 1); + } else if (last === '..') { + srcPath.splice(i, 1); + up++; + } else if (up) { + srcPath.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (!mustEndAbs && !removeAllDots) { + for (; up--; up) { + srcPath.unshift('..'); + } + } + + if (mustEndAbs && srcPath[0] !== '' && + (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { + srcPath.unshift(''); + } + + if (hasTrailingSlash && (shims.substr(srcPath.join('/'), -1) !== '/')) { + srcPath.push(''); + } + + var isAbsolute = srcPath[0] === '' || + (srcPath[0] && srcPath[0].charAt(0) === '/'); + + // put the host back + if (psychotic) { + result.hostname = result.host = isAbsolute ? '' : + srcPath.length ? srcPath.shift() : ''; + //occationaly the auth can get stuck only in host + //this especialy happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); + } + } + + mustEndAbs = mustEndAbs || (result.host && srcPath.length); + + if (mustEndAbs && !isAbsolute) { + srcPath.unshift(''); + } + + if (!srcPath.length) { + result.pathname = null; + result.path = null; + } else { + result.pathname = srcPath.join('/'); + } + + //to support request.http + if (!util.isNull(result.pathname) || !util.isNull(result.search)) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.auth = relative.auth || result.auth; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; +}; + +Url.prototype.parseHost = function() { + var host = this.host; + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ':') { + this.port = port.substr(1); + } + host = host.substr(0, host.length - port.length); + } + if (host) this.hostname = host; +}; +},{"_shims":2,"querystring":6,"util":8}],8:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var shims = require('_shims'); + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + shims.forEach(array, function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = shims.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = shims.getOwnPropertyNames(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + + shims.forEach(keys, function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = shims.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (shims.indexOf(ctx.seen, desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = shims.reduce(output, function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return shims.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && objectToString(e) === '[object Error]'; +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.binarySlice === 'function' + ; +} +exports.isBuffer = isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = function(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = shims.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); +}; + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = shims.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +},{"_shims":2}],9:[function(require,module,exports){ +exports.readIEEE754 = function(buffer, offset, isBE, mLen, nBytes) { + var e, m, + eLen = nBytes * 8 - mLen - 1, + eMax = (1 << eLen) - 1, + eBias = eMax >> 1, + nBits = -7, + i = isBE ? 0 : (nBytes - 1), + d = isBE ? 1 : -1, + s = buffer[offset + i]; + + i += d; + + e = s & ((1 << (-nBits)) - 1); + s >>= (-nBits); + nBits += eLen; + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8); + + m = e & ((1 << (-nBits)) - 1); + e >>= (-nBits); + nBits += mLen; + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8); + + if (e === 0) { + e = 1 - eBias; + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity); + } else { + m = m + Math.pow(2, mLen); + e = e - eBias; + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen); +}; + +exports.writeIEEE754 = function(buffer, value, offset, isBE, mLen, nBytes) { + var e, m, c, + eLen = nBytes * 8 - mLen - 1, + eMax = (1 << eLen) - 1, + eBias = eMax >> 1, + rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0), + i = isBE ? (nBytes - 1) : 0, + d = isBE ? -1 : 1, + s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; + + value = Math.abs(value); + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0; + e = eMax; + } else { + e = Math.floor(Math.log(value) / Math.LN2); + if (value * (c = Math.pow(2, -e)) < 1) { + e--; + c *= 2; + } + if (e + eBias >= 1) { + value += rt / c; + } else { + value += rt * Math.pow(2, 1 - eBias); + } + if (value * c >= 2) { + e++; + c /= 2; + } + + if (e + eBias >= eMax) { + m = 0; + e = eMax; + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen); + e = e + eBias; + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); + e = 0; + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8); + + e = (e << mLen) | m; + eLen += mLen; + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8); + + buffer[offset + i - d] |= s * 128; +}; + +},{}],10:[function(require,module,exports){ +var assert; +exports.Buffer = Buffer; +exports.SlowBuffer = Buffer; +Buffer.poolSize = 8192; +exports.INSPECT_MAX_BYTES = 50; + +function stringtrim(str) { + if (str.trim) return str.trim(); + return str.replace(/^\s+|\s+$/g, ''); +} + +function Buffer(subject, encoding, offset) { + if(!assert) assert= require('assert'); + if (!(this instanceof Buffer)) { + return new Buffer(subject, encoding, offset); + } + this.parent = this; + this.offset = 0; + + // Work-around: node's base64 implementation + // allows for non-padded strings while base64-js + // does not.. + if (encoding == "base64" && typeof subject == "string") { + subject = stringtrim(subject); + while (subject.length % 4 != 0) { + subject = subject + "="; + } + } + + var type; + + // Are we slicing? + if (typeof offset === 'number') { + this.length = coerce(encoding); + // slicing works, with limitations (no parent tracking/update) + // check https://github.com/toots/buffer-browserify/issues/19 + for (var i = 0; i < this.length; i++) { + this[i] = subject.get(i+offset); + } + } else { + // Find the length + switch (type = typeof subject) { + case 'number': + this.length = coerce(subject); + break; + + case 'string': + this.length = Buffer.byteLength(subject, encoding); + break; + + case 'object': // Assume object is an array + this.length = coerce(subject.length); + break; + + default: + throw new TypeError('First argument needs to be a number, ' + + 'array or string.'); + } + + // Treat array-ish objects as a byte array. + if (isArrayIsh(subject)) { + for (var i = 0; i < this.length; i++) { + if (subject instanceof Buffer) { + this[i] = subject.readUInt8(i); + } + else { + // Round-up subject[i] to a UInt8. + // e.g.: ((-432 % 256) + 256) % 256 = (-176 + 256) % 256 + // = 80 + this[i] = ((subject[i] % 256) + 256) % 256; + } + } + } else if (type == 'string') { + // We are a string + this.length = this.write(subject, 0, encoding); + } else if (type === 'number') { + for (var i = 0; i < this.length; i++) { + this[i] = 0; + } + } + } +} + +Buffer.prototype.get = function get(i) { + if (i < 0 || i >= this.length) throw new Error('oob'); + return this[i]; +}; + +Buffer.prototype.set = function set(i, v) { + if (i < 0 || i >= this.length) throw new Error('oob'); + return this[i] = v; +}; + +Buffer.byteLength = function (str, encoding) { + switch (encoding || "utf8") { + case 'hex': + return str.length / 2; + + case 'utf8': + case 'utf-8': + return utf8ToBytes(str).length; + + case 'ascii': + case 'binary': + return str.length; + + case 'base64': + return base64ToBytes(str).length; + + default: + throw new Error('Unknown encoding'); + } +}; + +Buffer.prototype.utf8Write = function (string, offset, length) { + var bytes, pos; + return Buffer._charsWritten = blitBuffer(utf8ToBytes(string), this, offset, length); +}; + +Buffer.prototype.asciiWrite = function (string, offset, length) { + var bytes, pos; + return Buffer._charsWritten = blitBuffer(asciiToBytes(string), this, offset, length); +}; + +Buffer.prototype.binaryWrite = Buffer.prototype.asciiWrite; + +Buffer.prototype.base64Write = function (string, offset, length) { + var bytes, pos; + return Buffer._charsWritten = blitBuffer(base64ToBytes(string), this, offset, length); +}; + +Buffer.prototype.base64Slice = function (start, end) { + var bytes = Array.prototype.slice.apply(this, arguments) + return require("base64-js").fromByteArray(bytes); +}; + +Buffer.prototype.utf8Slice = function () { + var bytes = Array.prototype.slice.apply(this, arguments); + var res = ""; + var tmp = ""; + var i = 0; + while (i < bytes.length) { + if (bytes[i] <= 0x7F) { + res += decodeUtf8Char(tmp) + String.fromCharCode(bytes[i]); + tmp = ""; + } else + tmp += "%" + bytes[i].toString(16); + + i++; + } + + return res + decodeUtf8Char(tmp); +} + +Buffer.prototype.asciiSlice = function () { + var bytes = Array.prototype.slice.apply(this, arguments); + var ret = ""; + for (var i = 0; i < bytes.length; i++) + ret += String.fromCharCode(bytes[i]); + return ret; +} + +Buffer.prototype.binarySlice = Buffer.prototype.asciiSlice; + +Buffer.prototype.inspect = function() { + var out = [], + len = this.length; + for (var i = 0; i < len; i++) { + out[i] = toHex(this[i]); + if (i == exports.INSPECT_MAX_BYTES) { + out[i + 1] = '...'; + break; + } + } + return ''; +}; + + +Buffer.prototype.hexSlice = function(start, end) { + var len = this.length; + + if (!start || start < 0) start = 0; + if (!end || end < 0 || end > len) end = len; + + var out = ''; + for (var i = start; i < end; i++) { + out += toHex(this[i]); + } + return out; +}; + + +Buffer.prototype.toString = function(encoding, start, end) { + encoding = String(encoding || 'utf8').toLowerCase(); + start = +start || 0; + if (typeof end == 'undefined') end = this.length; + + // Fastpath empty strings + if (+end == start) { + return ''; + } + + switch (encoding) { + case 'hex': + return this.hexSlice(start, end); + + case 'utf8': + case 'utf-8': + return this.utf8Slice(start, end); + + case 'ascii': + return this.asciiSlice(start, end); + + case 'binary': + return this.binarySlice(start, end); + + case 'base64': + return this.base64Slice(start, end); + + case 'ucs2': + case 'ucs-2': + return this.ucs2Slice(start, end); + + default: + throw new Error('Unknown encoding'); + } +}; + + +Buffer.prototype.hexWrite = function(string, offset, length) { + offset = +offset || 0; + var remaining = this.length - offset; + if (!length) { + length = remaining; + } else { + length = +length; + if (length > remaining) { + length = remaining; + } + } + + // must be an even number of digits + var strLen = string.length; + if (strLen % 2) { + throw new Error('Invalid hex string'); + } + if (length > strLen / 2) { + length = strLen / 2; + } + for (var i = 0; i < length; i++) { + var b = parseInt(string.substr(i * 2, 2), 16); + if (isNaN(b)) throw new Error('Invalid hex string'); + this[offset + i] = b; + } + Buffer._charsWritten = i * 2; + return i; +}; + + +Buffer.prototype.write = function(string, offset, length, encoding) { + // Support both (string, offset, length, encoding) + // and the legacy (string, encoding, offset, length) + if (isFinite(offset)) { + if (!isFinite(length)) { + encoding = length; + length = undefined; + } + } else { // legacy + var swap = encoding; + encoding = offset; + offset = length; + length = swap; + } + + offset = +offset || 0; + var remaining = this.length - offset; + if (!length) { + length = remaining; + } else { + length = +length; + if (length > remaining) { + length = remaining; + } + } + encoding = String(encoding || 'utf8').toLowerCase(); + + switch (encoding) { + case 'hex': + return this.hexWrite(string, offset, length); + + case 'utf8': + case 'utf-8': + return this.utf8Write(string, offset, length); + + case 'ascii': + return this.asciiWrite(string, offset, length); + + case 'binary': + return this.binaryWrite(string, offset, length); + + case 'base64': + return this.base64Write(string, offset, length); + + case 'ucs2': + case 'ucs-2': + return this.ucs2Write(string, offset, length); + + default: + throw new Error('Unknown encoding'); + } +}; + +// slice(start, end) +function clamp(index, len, defaultValue) { + if (typeof index !== 'number') return defaultValue; + index = ~~index; // Coerce to integer. + if (index >= len) return len; + if (index >= 0) return index; + index += len; + if (index >= 0) return index; + return 0; +} + +Buffer.prototype.slice = function(start, end) { + var len = this.length; + start = clamp(start, len, 0); + end = clamp(end, len, len); + return new Buffer(this, end - start, +start); +}; + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function(target, target_start, start, end) { + var source = this; + start || (start = 0); + if (end === undefined || isNaN(end)) { + end = this.length; + } + target_start || (target_start = 0); + + if (end < start) throw new Error('sourceEnd < sourceStart'); + + // Copy 0 bytes; we're done + if (end === start) return 0; + if (target.length == 0 || source.length == 0) return 0; + + if (target_start < 0 || target_start >= target.length) { + throw new Error('targetStart out of bounds'); + } + + if (start < 0 || start >= source.length) { + throw new Error('sourceStart out of bounds'); + } + + if (end < 0 || end > source.length) { + throw new Error('sourceEnd out of bounds'); + } + + // Are we oob? + if (end > this.length) { + end = this.length; + } + + if (target.length - target_start < end - start) { + end = target.length - target_start + start; + } + + var temp = []; + for (var i=start; i= this.length) { + throw new Error('start out of bounds'); + } + + if (end < 0 || end > this.length) { + throw new Error('end out of bounds'); + } + + for (var i = start; i < end; i++) { + this[i] = value; + } +} + +// Static methods +Buffer.isBuffer = function isBuffer(b) { + return b instanceof Buffer; +}; + +Buffer.concat = function (list, totalLength) { + if (!isArray(list)) { + throw new Error("Usage: Buffer.concat(list, [totalLength])\n \ + list should be an Array."); + } + + if (list.length === 0) { + return new Buffer(0); + } else if (list.length === 1) { + return list[0]; + } + + if (typeof totalLength !== 'number') { + totalLength = 0; + for (var i = 0; i < list.length; i++) { + var buf = list[i]; + totalLength += buf.length; + } + } + + var buffer = new Buffer(totalLength); + var pos = 0; + for (var i = 0; i < list.length; i++) { + var buf = list[i]; + buf.copy(buffer, pos); + pos += buf.length; + } + return buffer; +}; + +Buffer.isEncoding = function(encoding) { + switch ((encoding + '').toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + case 'raw': + return true; + + default: + return false; + } +}; + +// helpers + +function coerce(length) { + // Coerce length to a number (possibly NaN), round up + // in case it's fractional (e.g. 123.456) then do a + // double negate to coerce a NaN to 0. Easy, right? + length = ~~Math.ceil(+length); + return length < 0 ? 0 : length; +} + +function isArray(subject) { + return (Array.isArray || + function(subject){ + return {}.toString.apply(subject) == '[object Array]' + }) + (subject) +} + +function isArrayIsh(subject) { + return isArray(subject) || Buffer.isBuffer(subject) || + subject && typeof subject === 'object' && + typeof subject.length === 'number'; +} + +function toHex(n) { + if (n < 16) return '0' + n.toString(16); + return n.toString(16); +} + +function utf8ToBytes(str) { + var byteArray = []; + for (var i = 0; i < str.length; i++) + if (str.charCodeAt(i) <= 0x7F) + byteArray.push(str.charCodeAt(i)); + else { + var h = encodeURIComponent(str.charAt(i)).substr(1).split('%'); + for (var j = 0; j < h.length; j++) + byteArray.push(parseInt(h[j], 16)); + } + + return byteArray; +} + +function asciiToBytes(str) { + var byteArray = [] + for (var i = 0; i < str.length; i++ ) + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push( str.charCodeAt(i) & 0xFF ); + + return byteArray; +} + +function base64ToBytes(str) { + return require("base64-js").toByteArray(str); +} + +function blitBuffer(src, dst, offset, length) { + var pos, i = 0; + while (i < length) { + if ((i+offset >= dst.length) || (i >= src.length)) + break; + + dst[i + offset] = src[i]; + i++; + } + return i; +} + +function decodeUtf8Char(str) { + try { + return decodeURIComponent(str); + } catch (err) { + return String.fromCharCode(0xFFFD); // UTF 8 invalid char + } +} + +// read/write bit-twiddling + +Buffer.prototype.readUInt8 = function(offset, noAssert) { + var buffer = this; + + if (!noAssert) { + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return; + + return buffer[offset]; +}; + +function readUInt16(buffer, offset, isBigEndian, noAssert) { + var val = 0; + + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return 0; + + if (isBigEndian) { + val = buffer[offset] << 8; + if (offset + 1 < buffer.length) { + val |= buffer[offset + 1]; + } + } else { + val = buffer[offset]; + if (offset + 1 < buffer.length) { + val |= buffer[offset + 1] << 8; + } + } + + return val; +} + +Buffer.prototype.readUInt16LE = function(offset, noAssert) { + return readUInt16(this, offset, false, noAssert); +}; + +Buffer.prototype.readUInt16BE = function(offset, noAssert) { + return readUInt16(this, offset, true, noAssert); +}; + +function readUInt32(buffer, offset, isBigEndian, noAssert) { + var val = 0; + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return 0; + + if (isBigEndian) { + if (offset + 1 < buffer.length) + val = buffer[offset + 1] << 16; + if (offset + 2 < buffer.length) + val |= buffer[offset + 2] << 8; + if (offset + 3 < buffer.length) + val |= buffer[offset + 3]; + val = val + (buffer[offset] << 24 >>> 0); + } else { + if (offset + 2 < buffer.length) + val = buffer[offset + 2] << 16; + if (offset + 1 < buffer.length) + val |= buffer[offset + 1] << 8; + val |= buffer[offset]; + if (offset + 3 < buffer.length) + val = val + (buffer[offset + 3] << 24 >>> 0); + } + + return val; +} + +Buffer.prototype.readUInt32LE = function(offset, noAssert) { + return readUInt32(this, offset, false, noAssert); +}; + +Buffer.prototype.readUInt32BE = function(offset, noAssert) { + return readUInt32(this, offset, true, noAssert); +}; + + +/* + * Signed integer types, yay team! A reminder on how two's complement actually + * works. The first bit is the signed bit, i.e. tells us whether or not the + * number should be positive or negative. If the two's complement value is + * positive, then we're done, as it's equivalent to the unsigned representation. + * + * Now if the number is positive, you're pretty much done, you can just leverage + * the unsigned translations and return those. Unfortunately, negative numbers + * aren't quite that straightforward. + * + * At first glance, one might be inclined to use the traditional formula to + * translate binary numbers between the positive and negative values in two's + * complement. (Though it doesn't quite work for the most negative value) + * Mainly: + * - invert all the bits + * - add one to the result + * + * Of course, this doesn't quite work in Javascript. Take for example the value + * of -128. This could be represented in 16 bits (big-endian) as 0xff80. But of + * course, Javascript will do the following: + * + * > ~0xff80 + * -65409 + * + * Whoh there, Javascript, that's not quite right. But wait, according to + * Javascript that's perfectly correct. When Javascript ends up seeing the + * constant 0xff80, it has no notion that it is actually a signed number. It + * assumes that we've input the unsigned value 0xff80. Thus, when it does the + * binary negation, it casts it into a signed value, (positive 0xff80). Then + * when you perform binary negation on that, it turns it into a negative number. + * + * Instead, we're going to have to use the following general formula, that works + * in a rather Javascript friendly way. I'm glad we don't support this kind of + * weird numbering scheme in the kernel. + * + * (BIT-MAX - (unsigned)val + 1) * -1 + * + * The astute observer, may think that this doesn't make sense for 8-bit numbers + * (really it isn't necessary for them). However, when you get 16-bit numbers, + * you do. Let's go back to our prior example and see how this will look: + * + * (0xffff - 0xff80 + 1) * -1 + * (0x007f + 1) * -1 + * (0x0080) * -1 + */ +Buffer.prototype.readInt8 = function(offset, noAssert) { + var buffer = this; + var neg; + + if (!noAssert) { + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return; + + neg = buffer[offset] & 0x80; + if (!neg) { + return (buffer[offset]); + } + + return ((0xff - buffer[offset] + 1) * -1); +}; + +function readInt16(buffer, offset, isBigEndian, noAssert) { + var neg, val; + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'Trying to read beyond buffer length'); + } + + val = readUInt16(buffer, offset, isBigEndian, noAssert); + neg = val & 0x8000; + if (!neg) { + return val; + } + + return (0xffff - val + 1) * -1; +} + +Buffer.prototype.readInt16LE = function(offset, noAssert) { + return readInt16(this, offset, false, noAssert); +}; + +Buffer.prototype.readInt16BE = function(offset, noAssert) { + return readInt16(this, offset, true, noAssert); +}; + +function readInt32(buffer, offset, isBigEndian, noAssert) { + var neg, val; + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to read beyond buffer length'); + } + + val = readUInt32(buffer, offset, isBigEndian, noAssert); + neg = val & 0x80000000; + if (!neg) { + return (val); + } + + return (0xffffffff - val + 1) * -1; +} + +Buffer.prototype.readInt32LE = function(offset, noAssert) { + return readInt32(this, offset, false, noAssert); +}; + +Buffer.prototype.readInt32BE = function(offset, noAssert) { + return readInt32(this, offset, true, noAssert); +}; + +function readFloat(buffer, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to read beyond buffer length'); + } + + return require('./buffer_ieee754').readIEEE754(buffer, offset, isBigEndian, + 23, 4); +} + +Buffer.prototype.readFloatLE = function(offset, noAssert) { + return readFloat(this, offset, false, noAssert); +}; + +Buffer.prototype.readFloatBE = function(offset, noAssert) { + return readFloat(this, offset, true, noAssert); +}; + +function readDouble(buffer, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset + 7 < buffer.length, + 'Trying to read beyond buffer length'); + } + + return require('./buffer_ieee754').readIEEE754(buffer, offset, isBigEndian, + 52, 8); +} + +Buffer.prototype.readDoubleLE = function(offset, noAssert) { + return readDouble(this, offset, false, noAssert); +}; + +Buffer.prototype.readDoubleBE = function(offset, noAssert) { + return readDouble(this, offset, true, noAssert); +}; + + +/* + * We have to make sure that the value is a valid integer. This means that it is + * non-negative. It has no fractional component and that it does not exceed the + * maximum allowed value. + * + * value The number to check for validity + * + * max The maximum value + */ +function verifuint(value, max) { + assert.ok(typeof (value) == 'number', + 'cannot write a non-number as a number'); + + assert.ok(value >= 0, + 'specified a negative value for writing an unsigned value'); + + assert.ok(value <= max, 'value is larger than maximum value for type'); + + assert.ok(Math.floor(value) === value, 'value has a fractional component'); +} + +Buffer.prototype.writeUInt8 = function(value, offset, noAssert) { + var buffer = this; + + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'trying to write beyond buffer length'); + + verifuint(value, 0xff); + } + + if (offset < buffer.length) { + buffer[offset] = value; + } +}; + +function writeUInt16(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'trying to write beyond buffer length'); + + verifuint(value, 0xffff); + } + + for (var i = 0; i < Math.min(buffer.length - offset, 2); i++) { + buffer[offset + i] = + (value & (0xff << (8 * (isBigEndian ? 1 - i : i)))) >>> + (isBigEndian ? 1 - i : i) * 8; + } + +} + +Buffer.prototype.writeUInt16LE = function(value, offset, noAssert) { + writeUInt16(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeUInt16BE = function(value, offset, noAssert) { + writeUInt16(this, value, offset, true, noAssert); +}; + +function writeUInt32(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'trying to write beyond buffer length'); + + verifuint(value, 0xffffffff); + } + + for (var i = 0; i < Math.min(buffer.length - offset, 4); i++) { + buffer[offset + i] = + (value >>> (isBigEndian ? 3 - i : i) * 8) & 0xff; + } +} + +Buffer.prototype.writeUInt32LE = function(value, offset, noAssert) { + writeUInt32(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) { + writeUInt32(this, value, offset, true, noAssert); +}; + + +/* + * We now move onto our friends in the signed number category. Unlike unsigned + * numbers, we're going to have to worry a bit more about how we put values into + * arrays. Since we are only worrying about signed 32-bit values, we're in + * slightly better shape. Unfortunately, we really can't do our favorite binary + * & in this system. It really seems to do the wrong thing. For example: + * + * > -32 & 0xff + * 224 + * + * What's happening above is really: 0xe0 & 0xff = 0xe0. However, the results of + * this aren't treated as a signed number. Ultimately a bad thing. + * + * What we're going to want to do is basically create the unsigned equivalent of + * our representation and pass that off to the wuint* functions. To do that + * we're going to do the following: + * + * - if the value is positive + * we can pass it directly off to the equivalent wuint + * - if the value is negative + * we do the following computation: + * mb + val + 1, where + * mb is the maximum unsigned value in that byte size + * val is the Javascript negative integer + * + * + * As a concrete value, take -128. In signed 16 bits this would be 0xff80. If + * you do out the computations: + * + * 0xffff - 128 + 1 + * 0xffff - 127 + * 0xff80 + * + * You can then encode this value as the signed version. This is really rather + * hacky, but it should work and get the job done which is our goal here. + */ + +/* + * A series of checks to make sure we actually have a signed 32-bit number + */ +function verifsint(value, max, min) { + assert.ok(typeof (value) == 'number', + 'cannot write a non-number as a number'); + + assert.ok(value <= max, 'value larger than maximum allowed value'); + + assert.ok(value >= min, 'value smaller than minimum allowed value'); + + assert.ok(Math.floor(value) === value, 'value has a fractional component'); +} + +function verifIEEE754(value, max, min) { + assert.ok(typeof (value) == 'number', + 'cannot write a non-number as a number'); + + assert.ok(value <= max, 'value larger than maximum allowed value'); + + assert.ok(value >= min, 'value smaller than minimum allowed value'); +} + +Buffer.prototype.writeInt8 = function(value, offset, noAssert) { + var buffer = this; + + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'Trying to write beyond buffer length'); + + verifsint(value, 0x7f, -0x80); + } + + if (value >= 0) { + buffer.writeUInt8(value, offset, noAssert); + } else { + buffer.writeUInt8(0xff + value + 1, offset, noAssert); + } +}; + +function writeInt16(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'Trying to write beyond buffer length'); + + verifsint(value, 0x7fff, -0x8000); + } + + if (value >= 0) { + writeUInt16(buffer, value, offset, isBigEndian, noAssert); + } else { + writeUInt16(buffer, 0xffff + value + 1, offset, isBigEndian, noAssert); + } +} + +Buffer.prototype.writeInt16LE = function(value, offset, noAssert) { + writeInt16(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeInt16BE = function(value, offset, noAssert) { + writeInt16(this, value, offset, true, noAssert); +}; + +function writeInt32(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to write beyond buffer length'); + + verifsint(value, 0x7fffffff, -0x80000000); + } + + if (value >= 0) { + writeUInt32(buffer, value, offset, isBigEndian, noAssert); + } else { + writeUInt32(buffer, 0xffffffff + value + 1, offset, isBigEndian, noAssert); + } +} + +Buffer.prototype.writeInt32LE = function(value, offset, noAssert) { + writeInt32(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeInt32BE = function(value, offset, noAssert) { + writeInt32(this, value, offset, true, noAssert); +}; + +function writeFloat(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to write beyond buffer length'); + + verifIEEE754(value, 3.4028234663852886e+38, -3.4028234663852886e+38); + } + + require('./buffer_ieee754').writeIEEE754(buffer, value, offset, isBigEndian, + 23, 4); +} + +Buffer.prototype.writeFloatLE = function(value, offset, noAssert) { + writeFloat(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeFloatBE = function(value, offset, noAssert) { + writeFloat(this, value, offset, true, noAssert); +}; + +function writeDouble(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 7 < buffer.length, + 'Trying to write beyond buffer length'); + + verifIEEE754(value, 1.7976931348623157E+308, -1.7976931348623157E+308); + } + + require('./buffer_ieee754').writeIEEE754(buffer, value, offset, isBigEndian, + 52, 8); +} + +Buffer.prototype.writeDoubleLE = function(value, offset, noAssert) { + writeDouble(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeDoubleBE = function(value, offset, noAssert) { + writeDouble(this, value, offset, true, noAssert); +}; + +},{"./buffer_ieee754":9,"assert":3,"base64-js":11}],11:[function(require,module,exports){ +(function (exports) { + 'use strict'; + + var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + + function b64ToByteArray(b64) { + var i, j, l, tmp, placeHolders, arr; + + if (b64.length % 4 > 0) { + throw 'Invalid string. Length must be a multiple of 4'; + } + + // the number of equal signs (place holders) + // if there are two placeholders, than the two characters before it + // represent one byte + // if there is only one, then the three characters before it represent 2 bytes + // this is just a cheap hack to not do indexOf twice + placeHolders = b64.indexOf('='); + placeHolders = placeHolders > 0 ? b64.length - placeHolders : 0; + + // base64 is 4/3 + up to two characters of the original data + arr = [];//new Uint8Array(b64.length * 3 / 4 - placeHolders); + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? b64.length - 4 : b64.length; + + for (i = 0, j = 0; i < l; i += 4, j += 3) { + tmp = (lookup.indexOf(b64[i]) << 18) | (lookup.indexOf(b64[i + 1]) << 12) | (lookup.indexOf(b64[i + 2]) << 6) | lookup.indexOf(b64[i + 3]); + arr.push((tmp & 0xFF0000) >> 16); + arr.push((tmp & 0xFF00) >> 8); + arr.push(tmp & 0xFF); + } + + if (placeHolders === 2) { + tmp = (lookup.indexOf(b64[i]) << 2) | (lookup.indexOf(b64[i + 1]) >> 4); + arr.push(tmp & 0xFF); + } else if (placeHolders === 1) { + tmp = (lookup.indexOf(b64[i]) << 10) | (lookup.indexOf(b64[i + 1]) << 4) | (lookup.indexOf(b64[i + 2]) >> 2); + arr.push((tmp >> 8) & 0xFF); + arr.push(tmp & 0xFF); + } + + return arr; + } + + function uint8ToBase64(uint8) { + var i, + extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes + output = "", + temp, length; + + function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]; + }; + + // go through the array every three bytes, we'll deal with trailing stuff later + for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { + temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]); + output += tripletToBase64(temp); + } + + // pad the end with zeros, but make sure to not forget the extra bytes + switch (extraBytes) { + case 1: + temp = uint8[uint8.length - 1]; + output += lookup[temp >> 2]; + output += lookup[(temp << 4) & 0x3F]; + output += '=='; + break; + case 2: + temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]); + output += lookup[temp >> 10]; + output += lookup[(temp >> 4) & 0x3F]; + output += lookup[(temp << 2) & 0x3F]; + output += '='; + break; + } + + return output; + } + + module.exports.toByteArray = b64ToByteArray; + module.exports.fromByteArray = uint8ToBase64; +}()); + +},{}],12:[function(require,module,exports){ +require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o> 1, + nBits = -7, + i = isBE ? 0 : (nBytes - 1), + d = isBE ? 1 : -1, + s = buffer[offset + i]; + + i += d; + + e = s & ((1 << (-nBits)) - 1); + s >>= (-nBits); + nBits += eLen; + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8); + + m = e & ((1 << (-nBits)) - 1); + e >>= (-nBits); + nBits += mLen; + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8); + + if (e === 0) { + e = 1 - eBias; + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity); + } else { + m = m + Math.pow(2, mLen); + e = e - eBias; + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen); +}; + +exports.writeIEEE754 = function(buffer, value, offset, isBE, mLen, nBytes) { + var e, m, c, + eLen = nBytes * 8 - mLen - 1, + eMax = (1 << eLen) - 1, + eBias = eMax >> 1, + rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0), + i = isBE ? (nBytes - 1) : 0, + d = isBE ? -1 : 1, + s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; + + value = Math.abs(value); + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0; + e = eMax; + } else { + e = Math.floor(Math.log(value) / Math.LN2); + if (value * (c = Math.pow(2, -e)) < 1) { + e--; + c *= 2; + } + if (e + eBias >= 1) { + value += rt / c; + } else { + value += rt * Math.pow(2, 1 - eBias); + } + if (value * c >= 2) { + e++; + c /= 2; + } + + if (e + eBias >= eMax) { + m = 0; + e = eMax; + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen); + e = e + eBias; + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); + e = 0; + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8); + + e = (e << mLen) | m; + eLen += mLen; + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8); + + buffer[offset + i - d] |= s * 128; +}; + +},{}],"q9TxCC":[function(require,module,exports){ +var assert; +exports.Buffer = Buffer; +exports.SlowBuffer = Buffer; +Buffer.poolSize = 8192; +exports.INSPECT_MAX_BYTES = 50; + +function stringtrim(str) { + if (str.trim) return str.trim(); + return str.replace(/^\s+|\s+$/g, ''); +} + +function Buffer(subject, encoding, offset) { + if(!assert) assert= require('assert'); + if (!(this instanceof Buffer)) { + return new Buffer(subject, encoding, offset); + } + this.parent = this; + this.offset = 0; + + // Work-around: node's base64 implementation + // allows for non-padded strings while base64-js + // does not.. + if (encoding == "base64" && typeof subject == "string") { + subject = stringtrim(subject); + while (subject.length % 4 != 0) { + subject = subject + "="; + } + } + + var type; + + // Are we slicing? + if (typeof offset === 'number') { + this.length = coerce(encoding); + // slicing works, with limitations (no parent tracking/update) + // check https://github.com/toots/buffer-browserify/issues/19 + for (var i = 0; i < this.length; i++) { + this[i] = subject.get(i+offset); + } + } else { + // Find the length + switch (type = typeof subject) { + case 'number': + this.length = coerce(subject); + break; + + case 'string': + this.length = Buffer.byteLength(subject, encoding); + break; + + case 'object': // Assume object is an array + this.length = coerce(subject.length); + break; + + default: + throw new Error('First argument needs to be a number, ' + + 'array or string.'); + } + + // Treat array-ish objects as a byte array. + if (isArrayIsh(subject)) { + for (var i = 0; i < this.length; i++) { + if (subject instanceof Buffer) { + this[i] = subject.readUInt8(i); + } + else { + this[i] = subject[i]; + } + } + } else if (type == 'string') { + // We are a string + this.length = this.write(subject, 0, encoding); + } else if (type === 'number') { + for (var i = 0; i < this.length; i++) { + this[i] = 0; + } + } + } +} + +Buffer.prototype.get = function get(i) { + if (i < 0 || i >= this.length) throw new Error('oob'); + return this[i]; +}; + +Buffer.prototype.set = function set(i, v) { + if (i < 0 || i >= this.length) throw new Error('oob'); + return this[i] = v; +}; + +Buffer.byteLength = function (str, encoding) { + switch (encoding || "utf8") { + case 'hex': + return str.length / 2; + + case 'utf8': + case 'utf-8': + return utf8ToBytes(str).length; + + case 'ascii': + case 'binary': + return str.length; + + case 'base64': + return base64ToBytes(str).length; + + default: + throw new Error('Unknown encoding'); + } +}; + +Buffer.prototype.utf8Write = function (string, offset, length) { + var bytes, pos; + return Buffer._charsWritten = blitBuffer(utf8ToBytes(string), this, offset, length); +}; + +Buffer.prototype.asciiWrite = function (string, offset, length) { + var bytes, pos; + return Buffer._charsWritten = blitBuffer(asciiToBytes(string), this, offset, length); +}; + +Buffer.prototype.binaryWrite = Buffer.prototype.asciiWrite; + +Buffer.prototype.base64Write = function (string, offset, length) { + var bytes, pos; + return Buffer._charsWritten = blitBuffer(base64ToBytes(string), this, offset, length); +}; + +Buffer.prototype.base64Slice = function (start, end) { + var bytes = Array.prototype.slice.apply(this, arguments) + return require("base64-js").fromByteArray(bytes); +}; + +Buffer.prototype.utf8Slice = function () { + var bytes = Array.prototype.slice.apply(this, arguments); + var res = ""; + var tmp = ""; + var i = 0; + while (i < bytes.length) { + if (bytes[i] <= 0x7F) { + res += decodeUtf8Char(tmp) + String.fromCharCode(bytes[i]); + tmp = ""; + } else + tmp += "%" + bytes[i].toString(16); + + i++; + } + + return res + decodeUtf8Char(tmp); +} + +Buffer.prototype.asciiSlice = function () { + var bytes = Array.prototype.slice.apply(this, arguments); + var ret = ""; + for (var i = 0; i < bytes.length; i++) + ret += String.fromCharCode(bytes[i]); + return ret; +} + +Buffer.prototype.binarySlice = Buffer.prototype.asciiSlice; + +Buffer.prototype.inspect = function() { + var out = [], + len = this.length; + for (var i = 0; i < len; i++) { + out[i] = toHex(this[i]); + if (i == exports.INSPECT_MAX_BYTES) { + out[i + 1] = '...'; + break; + } + } + return ''; +}; + + +Buffer.prototype.hexSlice = function(start, end) { + var len = this.length; + + if (!start || start < 0) start = 0; + if (!end || end < 0 || end > len) end = len; + + var out = ''; + for (var i = start; i < end; i++) { + out += toHex(this[i]); + } + return out; +}; + + +Buffer.prototype.toString = function(encoding, start, end) { + encoding = String(encoding || 'utf8').toLowerCase(); + start = +start || 0; + if (typeof end == 'undefined') end = this.length; + + // Fastpath empty strings + if (+end == start) { + return ''; + } + + switch (encoding) { + case 'hex': + return this.hexSlice(start, end); + + case 'utf8': + case 'utf-8': + return this.utf8Slice(start, end); + + case 'ascii': + return this.asciiSlice(start, end); + + case 'binary': + return this.binarySlice(start, end); + + case 'base64': + return this.base64Slice(start, end); + + case 'ucs2': + case 'ucs-2': + return this.ucs2Slice(start, end); + + default: + throw new Error('Unknown encoding'); + } +}; + + +Buffer.prototype.hexWrite = function(string, offset, length) { + offset = +offset || 0; + var remaining = this.length - offset; + if (!length) { + length = remaining; + } else { + length = +length; + if (length > remaining) { + length = remaining; + } + } + + // must be an even number of digits + var strLen = string.length; + if (strLen % 2) { + throw new Error('Invalid hex string'); + } + if (length > strLen / 2) { + length = strLen / 2; + } + for (var i = 0; i < length; i++) { + var byte = parseInt(string.substr(i * 2, 2), 16); + if (isNaN(byte)) throw new Error('Invalid hex string'); + this[offset + i] = byte; + } + Buffer._charsWritten = i * 2; + return i; +}; + + +Buffer.prototype.write = function(string, offset, length, encoding) { + // Support both (string, offset, length, encoding) + // and the legacy (string, encoding, offset, length) + if (isFinite(offset)) { + if (!isFinite(length)) { + encoding = length; + length = undefined; + } + } else { // legacy + var swap = encoding; + encoding = offset; + offset = length; + length = swap; + } + + offset = +offset || 0; + var remaining = this.length - offset; + if (!length) { + length = remaining; + } else { + length = +length; + if (length > remaining) { + length = remaining; + } + } + encoding = String(encoding || 'utf8').toLowerCase(); + + switch (encoding) { + case 'hex': + return this.hexWrite(string, offset, length); + + case 'utf8': + case 'utf-8': + return this.utf8Write(string, offset, length); + + case 'ascii': + return this.asciiWrite(string, offset, length); + + case 'binary': + return this.binaryWrite(string, offset, length); + + case 'base64': + return this.base64Write(string, offset, length); + + case 'ucs2': + case 'ucs-2': + return this.ucs2Write(string, offset, length); + + default: + throw new Error('Unknown encoding'); + } +}; + +// slice(start, end) +function clamp(index, len, defaultValue) { + if (typeof index !== 'number') return defaultValue; + index = ~~index; // Coerce to integer. + if (index >= len) return len; + if (index >= 0) return index; + index += len; + if (index >= 0) return index; + return 0; +} + +Buffer.prototype.slice = function(start, end) { + var len = this.length; + start = clamp(start, len, 0); + end = clamp(end, len, len); + return new Buffer(this, end - start, +start); +}; + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function(target, target_start, start, end) { + var source = this; + start || (start = 0); + if (end === undefined || isNaN(end)) { + end = this.length; + } + target_start || (target_start = 0); + + if (end < start) throw new Error('sourceEnd < sourceStart'); + + // Copy 0 bytes; we're done + if (end === start) return 0; + if (target.length == 0 || source.length == 0) return 0; + + if (target_start < 0 || target_start >= target.length) { + throw new Error('targetStart out of bounds'); + } + + if (start < 0 || start >= source.length) { + throw new Error('sourceStart out of bounds'); + } + + if (end < 0 || end > source.length) { + throw new Error('sourceEnd out of bounds'); + } + + // Are we oob? + if (end > this.length) { + end = this.length; + } + + if (target.length - target_start < end - start) { + end = target.length - target_start + start; + } + + var temp = []; + for (var i=start; i= this.length) { + throw new Error('start out of bounds'); + } + + if (end < 0 || end > this.length) { + throw new Error('end out of bounds'); + } + + for (var i = start; i < end; i++) { + this[i] = value; + } +} + +// Static methods +Buffer.isBuffer = function isBuffer(b) { + return b instanceof Buffer || b instanceof Buffer; +}; + +Buffer.concat = function (list, totalLength) { + if (!isArray(list)) { + throw new Error("Usage: Buffer.concat(list, [totalLength])\n \ + list should be an Array."); + } + + if (list.length === 0) { + return new Buffer(0); + } else if (list.length === 1) { + return list[0]; + } + + if (typeof totalLength !== 'number') { + totalLength = 0; + for (var i = 0; i < list.length; i++) { + var buf = list[i]; + totalLength += buf.length; + } + } + + var buffer = new Buffer(totalLength); + var pos = 0; + for (var i = 0; i < list.length; i++) { + var buf = list[i]; + buf.copy(buffer, pos); + pos += buf.length; + } + return buffer; +}; + +Buffer.isEncoding = function(encoding) { + switch ((encoding + '').toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + case 'raw': + return true; + + default: + return false; + } +}; + +// helpers + +function coerce(length) { + // Coerce length to a number (possibly NaN), round up + // in case it's fractional (e.g. 123.456) then do a + // double negate to coerce a NaN to 0. Easy, right? + length = ~~Math.ceil(+length); + return length < 0 ? 0 : length; +} + +function isArray(subject) { + return (Array.isArray || + function(subject){ + return {}.toString.apply(subject) == '[object Array]' + }) + (subject) +} + +function isArrayIsh(subject) { + return isArray(subject) || Buffer.isBuffer(subject) || + subject && typeof subject === 'object' && + typeof subject.length === 'number'; +} + +function toHex(n) { + if (n < 16) return '0' + n.toString(16); + return n.toString(16); +} + +function utf8ToBytes(str) { + var byteArray = []; + for (var i = 0; i < str.length; i++) + if (str.charCodeAt(i) <= 0x7F) + byteArray.push(str.charCodeAt(i)); + else { + var h = encodeURIComponent(str.charAt(i)).substr(1).split('%'); + for (var j = 0; j < h.length; j++) + byteArray.push(parseInt(h[j], 16)); + } + + return byteArray; +} + +function asciiToBytes(str) { + var byteArray = [] + for (var i = 0; i < str.length; i++ ) + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push( str.charCodeAt(i) & 0xFF ); + + return byteArray; +} + +function base64ToBytes(str) { + return require("base64-js").toByteArray(str); +} + +function blitBuffer(src, dst, offset, length) { + var pos, i = 0; + while (i < length) { + if ((i+offset >= dst.length) || (i >= src.length)) + break; + + dst[i + offset] = src[i]; + i++; + } + return i; +} + +function decodeUtf8Char(str) { + try { + return decodeURIComponent(str); + } catch (err) { + return String.fromCharCode(0xFFFD); // UTF 8 invalid char + } +} + +// read/write bit-twiddling + +Buffer.prototype.readUInt8 = function(offset, noAssert) { + var buffer = this; + + if (!noAssert) { + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return; + + return buffer[offset]; +}; + +function readUInt16(buffer, offset, isBigEndian, noAssert) { + var val = 0; + + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return 0; + + if (isBigEndian) { + val = buffer[offset] << 8; + if (offset + 1 < buffer.length) { + val |= buffer[offset + 1]; + } + } else { + val = buffer[offset]; + if (offset + 1 < buffer.length) { + val |= buffer[offset + 1] << 8; + } + } + + return val; +} + +Buffer.prototype.readUInt16LE = function(offset, noAssert) { + return readUInt16(this, offset, false, noAssert); +}; + +Buffer.prototype.readUInt16BE = function(offset, noAssert) { + return readUInt16(this, offset, true, noAssert); +}; + +function readUInt32(buffer, offset, isBigEndian, noAssert) { + var val = 0; + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return 0; + + if (isBigEndian) { + if (offset + 1 < buffer.length) + val = buffer[offset + 1] << 16; + if (offset + 2 < buffer.length) + val |= buffer[offset + 2] << 8; + if (offset + 3 < buffer.length) + val |= buffer[offset + 3]; + val = val + (buffer[offset] << 24 >>> 0); + } else { + if (offset + 2 < buffer.length) + val = buffer[offset + 2] << 16; + if (offset + 1 < buffer.length) + val |= buffer[offset + 1] << 8; + val |= buffer[offset]; + if (offset + 3 < buffer.length) + val = val + (buffer[offset + 3] << 24 >>> 0); + } + + return val; +} + +Buffer.prototype.readUInt32LE = function(offset, noAssert) { + return readUInt32(this, offset, false, noAssert); +}; + +Buffer.prototype.readUInt32BE = function(offset, noAssert) { + return readUInt32(this, offset, true, noAssert); +}; + + +/* + * Signed integer types, yay team! A reminder on how two's complement actually + * works. The first bit is the signed bit, i.e. tells us whether or not the + * number should be positive or negative. If the two's complement value is + * positive, then we're done, as it's equivalent to the unsigned representation. + * + * Now if the number is positive, you're pretty much done, you can just leverage + * the unsigned translations and return those. Unfortunately, negative numbers + * aren't quite that straightforward. + * + * At first glance, one might be inclined to use the traditional formula to + * translate binary numbers between the positive and negative values in two's + * complement. (Though it doesn't quite work for the most negative value) + * Mainly: + * - invert all the bits + * - add one to the result + * + * Of course, this doesn't quite work in Javascript. Take for example the value + * of -128. This could be represented in 16 bits (big-endian) as 0xff80. But of + * course, Javascript will do the following: + * + * > ~0xff80 + * -65409 + * + * Whoh there, Javascript, that's not quite right. But wait, according to + * Javascript that's perfectly correct. When Javascript ends up seeing the + * constant 0xff80, it has no notion that it is actually a signed number. It + * assumes that we've input the unsigned value 0xff80. Thus, when it does the + * binary negation, it casts it into a signed value, (positive 0xff80). Then + * when you perform binary negation on that, it turns it into a negative number. + * + * Instead, we're going to have to use the following general formula, that works + * in a rather Javascript friendly way. I'm glad we don't support this kind of + * weird numbering scheme in the kernel. + * + * (BIT-MAX - (unsigned)val + 1) * -1 + * + * The astute observer, may think that this doesn't make sense for 8-bit numbers + * (really it isn't necessary for them). However, when you get 16-bit numbers, + * you do. Let's go back to our prior example and see how this will look: + * + * (0xffff - 0xff80 + 1) * -1 + * (0x007f + 1) * -1 + * (0x0080) * -1 + */ +Buffer.prototype.readInt8 = function(offset, noAssert) { + var buffer = this; + var neg; + + if (!noAssert) { + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return; + + neg = buffer[offset] & 0x80; + if (!neg) { + return (buffer[offset]); + } + + return ((0xff - buffer[offset] + 1) * -1); +}; + +function readInt16(buffer, offset, isBigEndian, noAssert) { + var neg, val; + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'Trying to read beyond buffer length'); + } + + val = readUInt16(buffer, offset, isBigEndian, noAssert); + neg = val & 0x8000; + if (!neg) { + return val; + } + + return (0xffff - val + 1) * -1; +} + +Buffer.prototype.readInt16LE = function(offset, noAssert) { + return readInt16(this, offset, false, noAssert); +}; + +Buffer.prototype.readInt16BE = function(offset, noAssert) { + return readInt16(this, offset, true, noAssert); +}; + +function readInt32(buffer, offset, isBigEndian, noAssert) { + var neg, val; + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to read beyond buffer length'); + } + + val = readUInt32(buffer, offset, isBigEndian, noAssert); + neg = val & 0x80000000; + if (!neg) { + return (val); + } + + return (0xffffffff - val + 1) * -1; +} + +Buffer.prototype.readInt32LE = function(offset, noAssert) { + return readInt32(this, offset, false, noAssert); +}; + +Buffer.prototype.readInt32BE = function(offset, noAssert) { + return readInt32(this, offset, true, noAssert); +}; + +function readFloat(buffer, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to read beyond buffer length'); + } + + return require('./buffer_ieee754').readIEEE754(buffer, offset, isBigEndian, + 23, 4); +} + +Buffer.prototype.readFloatLE = function(offset, noAssert) { + return readFloat(this, offset, false, noAssert); +}; + +Buffer.prototype.readFloatBE = function(offset, noAssert) { + return readFloat(this, offset, true, noAssert); +}; + +function readDouble(buffer, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset + 7 < buffer.length, + 'Trying to read beyond buffer length'); + } + + return require('./buffer_ieee754').readIEEE754(buffer, offset, isBigEndian, + 52, 8); +} + +Buffer.prototype.readDoubleLE = function(offset, noAssert) { + return readDouble(this, offset, false, noAssert); +}; + +Buffer.prototype.readDoubleBE = function(offset, noAssert) { + return readDouble(this, offset, true, noAssert); +}; + + +/* + * We have to make sure that the value is a valid integer. This means that it is + * non-negative. It has no fractional component and that it does not exceed the + * maximum allowed value. + * + * value The number to check for validity + * + * max The maximum value + */ +function verifuint(value, max) { + assert.ok(typeof (value) == 'number', + 'cannot write a non-number as a number'); + + assert.ok(value >= 0, + 'specified a negative value for writing an unsigned value'); + + assert.ok(value <= max, 'value is larger than maximum value for type'); + + assert.ok(Math.floor(value) === value, 'value has a fractional component'); +} + +Buffer.prototype.writeUInt8 = function(value, offset, noAssert) { + var buffer = this; + + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'trying to write beyond buffer length'); + + verifuint(value, 0xff); + } + + if (offset < buffer.length) { + buffer[offset] = value; + } +}; + +function writeUInt16(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'trying to write beyond buffer length'); + + verifuint(value, 0xffff); + } + + for (var i = 0; i < Math.min(buffer.length - offset, 2); i++) { + buffer[offset + i] = + (value & (0xff << (8 * (isBigEndian ? 1 - i : i)))) >>> + (isBigEndian ? 1 - i : i) * 8; + } + +} + +Buffer.prototype.writeUInt16LE = function(value, offset, noAssert) { + writeUInt16(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeUInt16BE = function(value, offset, noAssert) { + writeUInt16(this, value, offset, true, noAssert); +}; + +function writeUInt32(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'trying to write beyond buffer length'); + + verifuint(value, 0xffffffff); + } + + for (var i = 0; i < Math.min(buffer.length - offset, 4); i++) { + buffer[offset + i] = + (value >>> (isBigEndian ? 3 - i : i) * 8) & 0xff; + } +} + +Buffer.prototype.writeUInt32LE = function(value, offset, noAssert) { + writeUInt32(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) { + writeUInt32(this, value, offset, true, noAssert); +}; + + +/* + * We now move onto our friends in the signed number category. Unlike unsigned + * numbers, we're going to have to worry a bit more about how we put values into + * arrays. Since we are only worrying about signed 32-bit values, we're in + * slightly better shape. Unfortunately, we really can't do our favorite binary + * & in this system. It really seems to do the wrong thing. For example: + * + * > -32 & 0xff + * 224 + * + * What's happening above is really: 0xe0 & 0xff = 0xe0. However, the results of + * this aren't treated as a signed number. Ultimately a bad thing. + * + * What we're going to want to do is basically create the unsigned equivalent of + * our representation and pass that off to the wuint* functions. To do that + * we're going to do the following: + * + * - if the value is positive + * we can pass it directly off to the equivalent wuint + * - if the value is negative + * we do the following computation: + * mb + val + 1, where + * mb is the maximum unsigned value in that byte size + * val is the Javascript negative integer + * + * + * As a concrete value, take -128. In signed 16 bits this would be 0xff80. If + * you do out the computations: + * + * 0xffff - 128 + 1 + * 0xffff - 127 + * 0xff80 + * + * You can then encode this value as the signed version. This is really rather + * hacky, but it should work and get the job done which is our goal here. + */ + +/* + * A series of checks to make sure we actually have a signed 32-bit number + */ +function verifsint(value, max, min) { + assert.ok(typeof (value) == 'number', + 'cannot write a non-number as a number'); + + assert.ok(value <= max, 'value larger than maximum allowed value'); + + assert.ok(value >= min, 'value smaller than minimum allowed value'); + + assert.ok(Math.floor(value) === value, 'value has a fractional component'); +} + +function verifIEEE754(value, max, min) { + assert.ok(typeof (value) == 'number', + 'cannot write a non-number as a number'); + + assert.ok(value <= max, 'value larger than maximum allowed value'); + + assert.ok(value >= min, 'value smaller than minimum allowed value'); +} + +Buffer.prototype.writeInt8 = function(value, offset, noAssert) { + var buffer = this; + + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'Trying to write beyond buffer length'); + + verifsint(value, 0x7f, -0x80); + } + + if (value >= 0) { + buffer.writeUInt8(value, offset, noAssert); + } else { + buffer.writeUInt8(0xff + value + 1, offset, noAssert); + } +}; + +function writeInt16(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'Trying to write beyond buffer length'); + + verifsint(value, 0x7fff, -0x8000); + } + + if (value >= 0) { + writeUInt16(buffer, value, offset, isBigEndian, noAssert); + } else { + writeUInt16(buffer, 0xffff + value + 1, offset, isBigEndian, noAssert); + } +} + +Buffer.prototype.writeInt16LE = function(value, offset, noAssert) { + writeInt16(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeInt16BE = function(value, offset, noAssert) { + writeInt16(this, value, offset, true, noAssert); +}; + +function writeInt32(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to write beyond buffer length'); + + verifsint(value, 0x7fffffff, -0x80000000); + } + + if (value >= 0) { + writeUInt32(buffer, value, offset, isBigEndian, noAssert); + } else { + writeUInt32(buffer, 0xffffffff + value + 1, offset, isBigEndian, noAssert); + } +} + +Buffer.prototype.writeInt32LE = function(value, offset, noAssert) { + writeInt32(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeInt32BE = function(value, offset, noAssert) { + writeInt32(this, value, offset, true, noAssert); +}; + +function writeFloat(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to write beyond buffer length'); + + verifIEEE754(value, 3.4028234663852886e+38, -3.4028234663852886e+38); + } + + require('./buffer_ieee754').writeIEEE754(buffer, value, offset, isBigEndian, + 23, 4); +} + +Buffer.prototype.writeFloatLE = function(value, offset, noAssert) { + writeFloat(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeFloatBE = function(value, offset, noAssert) { + writeFloat(this, value, offset, true, noAssert); +}; + +function writeDouble(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 7 < buffer.length, + 'Trying to write beyond buffer length'); + + verifIEEE754(value, 1.7976931348623157E+308, -1.7976931348623157E+308); + } + + require('./buffer_ieee754').writeIEEE754(buffer, value, offset, isBigEndian, + 52, 8); +} + +Buffer.prototype.writeDoubleLE = function(value, offset, noAssert) { + writeDouble(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeDoubleBE = function(value, offset, noAssert) { + writeDouble(this, value, offset, true, noAssert); +}; + +},{"./buffer_ieee754":1,"assert":6,"base64-js":4}],"buffer-browserify":[function(require,module,exports){ +module.exports=require('q9TxCC'); +},{}],4:[function(require,module,exports){ +(function (exports) { + 'use strict'; + + var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + + function b64ToByteArray(b64) { + var i, j, l, tmp, placeHolders, arr; + + if (b64.length % 4 > 0) { + throw 'Invalid string. Length must be a multiple of 4'; + } + + // the number of equal signs (place holders) + // if there are two placeholders, than the two characters before it + // represent one byte + // if there is only one, then the three characters before it represent 2 bytes + // this is just a cheap hack to not do indexOf twice + placeHolders = b64.indexOf('='); + placeHolders = placeHolders > 0 ? b64.length - placeHolders : 0; + + // base64 is 4/3 + up to two characters of the original data + arr = [];//new Uint8Array(b64.length * 3 / 4 - placeHolders); + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? b64.length - 4 : b64.length; + + for (i = 0, j = 0; i < l; i += 4, j += 3) { + tmp = (lookup.indexOf(b64[i]) << 18) | (lookup.indexOf(b64[i + 1]) << 12) | (lookup.indexOf(b64[i + 2]) << 6) | lookup.indexOf(b64[i + 3]); + arr.push((tmp & 0xFF0000) >> 16); + arr.push((tmp & 0xFF00) >> 8); + arr.push(tmp & 0xFF); + } + + if (placeHolders === 2) { + tmp = (lookup.indexOf(b64[i]) << 2) | (lookup.indexOf(b64[i + 1]) >> 4); + arr.push(tmp & 0xFF); + } else if (placeHolders === 1) { + tmp = (lookup.indexOf(b64[i]) << 10) | (lookup.indexOf(b64[i + 1]) << 4) | (lookup.indexOf(b64[i + 2]) >> 2); + arr.push((tmp >> 8) & 0xFF); + arr.push(tmp & 0xFF); + } + + return arr; + } + + function uint8ToBase64(uint8) { + var i, + extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes + output = "", + temp, length; + + function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]; + }; + + // go through the array every three bytes, we'll deal with trailing stuff later + for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { + temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]); + output += tripletToBase64(temp); + } + + // pad the end with zeros, but make sure to not forget the extra bytes + switch (extraBytes) { + case 1: + temp = uint8[uint8.length - 1]; + output += lookup[temp >> 2]; + output += lookup[(temp << 4) & 0x3F]; + output += '=='; + break; + case 2: + temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]); + output += lookup[temp >> 10]; + output += lookup[(temp >> 4) & 0x3F]; + output += lookup[(temp << 2) & 0x3F]; + output += '='; + break; + } + + return output; + } + + module.exports.toByteArray = b64ToByteArray; + module.exports.fromByteArray = uint8ToBase64; +}()); + +},{}],5:[function(require,module,exports){ + + +// +// The shims in this file are not fully implemented shims for the ES5 +// features, but do work for the particular usecases there is in +// the other modules. +// + +var toString = Object.prototype.toString; +var hasOwnProperty = Object.prototype.hasOwnProperty; + +// Array.isArray is supported in IE9 +function isArray(xs) { + return toString.call(xs) === '[object Array]'; +} +exports.isArray = typeof Array.isArray === 'function' ? Array.isArray : isArray; + +// Array.prototype.indexOf is supported in IE9 +exports.indexOf = function indexOf(xs, x) { + if (xs.indexOf) return xs.indexOf(x); + for (var i = 0; i < xs.length; i++) { + if (x === xs[i]) return i; + } + return -1; +}; + +// Array.prototype.filter is supported in IE9 +exports.filter = function filter(xs, fn) { + if (xs.filter) return xs.filter(fn); + var res = []; + for (var i = 0; i < xs.length; i++) { + if (fn(xs[i], i, xs)) res.push(xs[i]); + } + return res; +}; + +// Array.prototype.forEach is supported in IE9 +exports.forEach = function forEach(xs, fn, self) { + if (xs.forEach) return xs.forEach(fn, self); + for (var i = 0; i < xs.length; i++) { + fn.call(self, xs[i], i, xs); + } +}; + +// Array.prototype.map is supported in IE9 +exports.map = function map(xs, fn) { + if (xs.map) return xs.map(fn); + var out = new Array(xs.length); + for (var i = 0; i < xs.length; i++) { + out[i] = fn(xs[i], i, xs); + } + return out; +}; + +// Array.prototype.reduce is supported in IE9 +exports.reduce = function reduce(array, callback, opt_initialValue) { + if (array.reduce) return array.reduce(callback, opt_initialValue); + var value, isValueSet = false; + + if (2 < arguments.length) { + value = opt_initialValue; + isValueSet = true; + } + for (var i = 0, l = array.length; l > i; ++i) { + if (array.hasOwnProperty(i)) { + if (isValueSet) { + value = callback(value, array[i], i, array); + } + else { + value = array[i]; + isValueSet = true; + } + } + } + + return value; +}; + +// String.prototype.substr - negative index don't work in IE8 +if ('ab'.substr(-1) !== 'b') { + exports.substr = function (str, start, length) { + // did we get a negative start, calculate how much it is from the beginning of the string + if (start < 0) start = str.length + start; + + // call the original function + return str.substr(start, length); + }; +} else { + exports.substr = function (str, start, length) { + return str.substr(start, length); + }; +} + +// String.prototype.trim is supported in IE9 +exports.trim = function (str) { + if (str.trim) return str.trim(); + return str.replace(/^\s+|\s+$/g, ''); +}; + +// Function.prototype.bind is supported in IE9 +exports.bind = function () { + var args = Array.prototype.slice.call(arguments); + var fn = args.shift(); + if (fn.bind) return fn.bind.apply(fn, args); + var self = args.shift(); + return function () { + fn.apply(self, args.concat([Array.prototype.slice.call(arguments)])); + }; +}; + +// Object.create is supported in IE9 +function create(prototype, properties) { + var object; + if (prototype === null) { + object = { '__proto__' : null }; + } + else { + if (typeof prototype !== 'object') { + throw new TypeError( + 'typeof prototype[' + (typeof prototype) + '] != \'object\'' + ); + } + var Type = function () {}; + Type.prototype = prototype; + object = new Type(); + object.__proto__ = prototype; + } + if (typeof properties !== 'undefined' && Object.defineProperties) { + Object.defineProperties(object, properties); + } + return object; +} +exports.create = typeof Object.create === 'function' ? Object.create : create; + +// Object.keys and Object.getOwnPropertyNames is supported in IE9 however +// they do show a description and number property on Error objects +function notObject(object) { + return ((typeof object != "object" && typeof object != "function") || object === null); +} + +function keysShim(object) { + if (notObject(object)) { + throw new TypeError("Object.keys called on a non-object"); + } + + var result = []; + for (var name in object) { + if (hasOwnProperty.call(object, name)) { + result.push(name); + } + } + return result; +} + +// getOwnPropertyNames is almost the same as Object.keys one key feature +// is that it returns hidden properties, since that can't be implemented, +// this feature gets reduced so it just shows the length property on arrays +function propertyShim(object) { + if (notObject(object)) { + throw new TypeError("Object.getOwnPropertyNames called on a non-object"); + } + + var result = keysShim(object); + if (exports.isArray(object) && exports.indexOf(object, 'length') === -1) { + result.push('length'); + } + return result; +} + +var keys = typeof Object.keys === 'function' ? Object.keys : keysShim; +var getOwnPropertyNames = typeof Object.getOwnPropertyNames === 'function' ? + Object.getOwnPropertyNames : propertyShim; + +if (new Error().hasOwnProperty('description')) { + var ERROR_PROPERTY_FILTER = function (obj, array) { + if (toString.call(obj) === '[object Error]') { + array = exports.filter(array, function (name) { + return name !== 'description' && name !== 'number' && name !== 'message'; + }); + } + return array; + }; + + exports.keys = function (object) { + return ERROR_PROPERTY_FILTER(object, keys(object)); + }; + exports.getOwnPropertyNames = function (object) { + return ERROR_PROPERTY_FILTER(object, getOwnPropertyNames(object)); + }; +} else { + exports.keys = keys; + exports.getOwnPropertyNames = getOwnPropertyNames; +} + +// Object.getOwnPropertyDescriptor - supported in IE8 but only on dom elements +function valueObject(value, key) { + return { value: value[key] }; +} + +if (typeof Object.getOwnPropertyDescriptor === 'function') { + try { + Object.getOwnPropertyDescriptor({'a': 1}, 'a'); + exports.getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; + } catch (e) { + // IE8 dom element issue - use a try catch and default to valueObject + exports.getOwnPropertyDescriptor = function (value, key) { + try { + return Object.getOwnPropertyDescriptor(value, key); + } catch (e) { + return valueObject(value, key); + } + }; + } +} else { + exports.getOwnPropertyDescriptor = valueObject; +} + +},{}],6:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// UTILITY +var util = require('util'); +var shims = require('_shims'); +var pSlice = Array.prototype.slice; + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = module.exports = ok; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + this.message = options.message || getMessage(this); +}; + +// assert.AssertionError instanceof Error +util.inherits(assert.AssertionError, Error); + +function replacer(key, value) { + if (util.isUndefined(value)) { + return '' + value; + } + if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) { + return value.toString(); + } + if (util.isFunction(value) || util.isRegExp(value)) { + return value.toString(); + } + return value; +} + +function truncate(s, n) { + if (util.isString(s)) { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } +} + +function getMessage(self) { + return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + + self.operator + ' ' + + truncate(JSON.stringify(self.expected, replacer), 128); +} + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, !!guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +function ok(value, message) { + if (!value) fail(value, true, message, '==', assert.ok); +} +assert.ok = ok; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (util.isBuffer(actual) && util.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (util.isDate(actual) && util.isDate(expected)) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (!util.isObject(actual) && !util.isObject(expected)) { + return actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b) { + if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + try { + var ka = shims.keys(a), + kb = shims.keys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (Object.prototype.toString.call(expected) == '[object RegExp]') { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (util.isString(expected)) { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail(actual, expected, 'Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail(actual, expected, 'Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function(err) { if (err) {throw err;}}; +},{"_shims":5,"util":7}],7:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var shims = require('_shims'); + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + shims.forEach(array, function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = shims.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = shims.getOwnPropertyNames(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + + shims.forEach(keys, function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = shims.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (shims.indexOf(ctx.seen, desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = shims.reduce(output, function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return shims.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && objectToString(e) === '[object Error]'; +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +function isBuffer(arg) { + return arg instanceof Buffer; +} +exports.isBuffer = isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = function(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = shims.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); +}; + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = shims.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +},{"_shims":5}]},{},[]) +;;module.exports=require("buffer-browserify") + +},{}],13:[function(require,module,exports){ +// shim for using process in browser + +var process = module.exports = {}; + +process.nextTick = (function () { + var canSetImmediate = typeof window !== 'undefined' + && window.setImmediate; + var canPost = typeof window !== 'undefined' + && window.postMessage && window.addEventListener + ; + + if (canSetImmediate) { + return function (f) { return window.setImmediate(f) }; + } + + if (canPost) { + var queue = []; + window.addEventListener('message', function (ev) { + var source = ev.source; + if ((source === window || source === null) && ev.data === 'process-tick') { + ev.stopPropagation(); + if (queue.length > 0) { + var fn = queue.shift(); + fn(); + } + } + }, true); + + return function nextTick(fn) { + queue.push(fn); + window.postMessage('process-tick', '*'); + }; + } + + return function nextTick(fn) { + setTimeout(fn, 0); + }; +})(); + +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +} + +// TODO(shtylman) +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; + +},{}],14:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +module.exports = { + 'compact': require('./arrays/compact'), + 'difference': require('./arrays/difference'), + 'drop': require('./arrays/rest'), + 'findIndex': require('./arrays/findIndex'), + 'findLastIndex': require('./arrays/findLastIndex'), + 'first': require('./arrays/first'), + 'flatten': require('./arrays/flatten'), + 'head': require('./arrays/first'), + 'indexOf': require('./arrays/indexOf'), + 'initial': require('./arrays/initial'), + 'intersection': require('./arrays/intersection'), + 'last': require('./arrays/last'), + 'lastIndexOf': require('./arrays/lastIndexOf'), + 'object': require('./arrays/zipObject'), + 'pull': require('./arrays/pull'), + 'range': require('./arrays/range'), + 'remove': require('./arrays/remove'), + 'rest': require('./arrays/rest'), + 'sortedIndex': require('./arrays/sortedIndex'), + 'tail': require('./arrays/rest'), + 'take': require('./arrays/first'), + 'union': require('./arrays/union'), + 'uniq': require('./arrays/uniq'), + 'unique': require('./arrays/uniq'), + 'unzip': require('./arrays/zip'), + 'without': require('./arrays/without'), + 'xor': require('./arrays/xor'), + 'zip': require('./arrays/zip'), + 'zipObject': require('./arrays/zipObject') +}; + +},{"./arrays/compact":15,"./arrays/difference":16,"./arrays/findIndex":17,"./arrays/findLastIndex":18,"./arrays/first":19,"./arrays/flatten":20,"./arrays/indexOf":21,"./arrays/initial":22,"./arrays/intersection":23,"./arrays/last":24,"./arrays/lastIndexOf":25,"./arrays/pull":26,"./arrays/range":27,"./arrays/remove":28,"./arrays/rest":29,"./arrays/sortedIndex":30,"./arrays/union":31,"./arrays/uniq":32,"./arrays/without":33,"./arrays/xor":34,"./arrays/zip":35,"./arrays/zipObject":36}],15:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Creates an array with all falsey values removed. The values `false`, `null`, + * `0`, `""`, `undefined`, and `NaN` are all falsey. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to compact. + * @returns {Array} Returns a new array of filtered values. + * @example + * + * _.compact([0, 1, false, 2, '', 3]); + * // => [1, 2, 3] + */ +function compact(array) { + var index = -1, + length = array ? array.length : 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (value) { + result.push(value); + } + } + return result; +} + +module.exports = compact; + +},{}],16:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseDifference = require('../internals/baseDifference'), + baseFlatten = require('../internals/baseFlatten'); + +/** + * Creates an array excluding all values of the provided arrays using strict + * equality for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to process. + * @param {...Array} [values] The arrays of values to exclude. + * @returns {Array} Returns a new array of filtered values. + * @example + * + * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); + * // => [1, 3, 4] + */ +function difference(array) { + return baseDifference(array, baseFlatten(arguments, true, true, 1)); +} + +module.exports = difference; + +},{"../internals/baseDifference":94,"../internals/baseFlatten":95}],17:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'); + +/** + * This method is like `_.find` except that it returns the index of the first + * element that passes the callback check, instead of the element itself. + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {number} Returns the index of the found element, else `-1`. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true }, + * { 'name': 'pebbles', 'age': 1, 'blocked': false } + * ]; + * + * _.findIndex(characters, function(chr) { + * return chr.age < 20; + * }); + * // => 2 + * + * // using "_.where" callback shorthand + * _.findIndex(characters, { 'age': 36 }); + * // => 0 + * + * // using "_.pluck" callback shorthand + * _.findIndex(characters, 'blocked'); + * // => 1 + */ +function findIndex(array, callback, thisArg) { + var index = -1, + length = array ? array.length : 0; + + callback = createCallback(callback, thisArg, 3); + while (++index < length) { + if (callback(array[index], index, array)) { + return index; + } + } + return -1; +} + +module.exports = findIndex; + +},{"../functions/createCallback":76}],18:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'); + +/** + * This method is like `_.findIndex` except that it iterates over elements + * of a `collection` from right to left. + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {number} Returns the index of the found element, else `-1`. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': true }, + * { 'name': 'fred', 'age': 40, 'blocked': false }, + * { 'name': 'pebbles', 'age': 1, 'blocked': true } + * ]; + * + * _.findLastIndex(characters, function(chr) { + * return chr.age > 30; + * }); + * // => 1 + * + * // using "_.where" callback shorthand + * _.findLastIndex(characters, { 'age': 36 }); + * // => 0 + * + * // using "_.pluck" callback shorthand + * _.findLastIndex(characters, 'blocked'); + * // => 2 + */ +function findLastIndex(array, callback, thisArg) { + var length = array ? array.length : 0; + callback = createCallback(callback, thisArg, 3); + while (length--) { + if (callback(array[length], length, array)) { + return length; + } + } + return -1; +} + +module.exports = findLastIndex; + +},{"../functions/createCallback":76}],19:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + slice = require('../internals/slice'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMax = Math.max, + nativeMin = Math.min; + +/** + * Gets the first element or first `n` elements of an array. If a callback + * is provided elements at the beginning of the array are returned as long + * as the callback returns truey. The callback is bound to `thisArg` and + * invoked with three arguments; (value, index, array). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias head, take + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|number|string} [callback] The function called + * per element or the number of elements to return. If a property name or + * object is provided it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the first element(s) of `array`. + * @example + * + * _.first([1, 2, 3]); + * // => 1 + * + * _.first([1, 2, 3], 2); + * // => [1, 2] + * + * _.first([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [1, 2] + * + * var characters = [ + * { 'name': 'barney', 'blocked': true, 'employer': 'slate' }, + * { 'name': 'fred', 'blocked': false, 'employer': 'slate' }, + * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.first(characters, 'blocked'); + * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }] + * + * // using "_.where" callback shorthand + * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name'); + * // => ['barney', 'fred'] + */ +function first(array, callback, thisArg) { + var n = 0, + length = array ? array.length : 0; + + if (typeof callback != 'number' && callback != null) { + var index = -1; + callback = createCallback(callback, thisArg, 3); + while (++index < length && callback(array[index], index, array)) { + n++; + } + } else { + n = callback; + if (n == null || thisArg) { + return array ? array[0] : undefined; + } + } + return slice(array, 0, nativeMin(nativeMax(0, n), length)); +} + +module.exports = first; + +},{"../functions/createCallback":76,"../internals/slice":129}],20:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseFlatten = require('../internals/baseFlatten'), + map = require('../collections/map'); + +/** + * Flattens a nested array (the nesting can be to any depth). If `isShallow` + * is truey, the array will only be flattened a single level. If a callback + * is provided each element of the array is passed through the callback before + * flattening. The callback is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to flatten. + * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new flattened array. + * @example + * + * _.flatten([1, [2], [3, [[4]]]]); + * // => [1, 2, 3, 4]; + * + * _.flatten([1, [2], [3, [[4]]]], true); + * // => [1, 2, 3, [[4]]]; + * + * var characters = [ + * { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] }, + * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] } + * ]; + * + * // using "_.pluck" callback shorthand + * _.flatten(characters, 'pets'); + * // => ['hoppy', 'baby puss', 'dino'] + */ +function flatten(array, isShallow, callback, thisArg) { + // juggle arguments + if (typeof isShallow != 'boolean' && isShallow != null) { + thisArg = callback; + callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow; + isShallow = false; + } + if (callback != null) { + array = map(array, callback, thisArg); + } + return baseFlatten(array, isShallow); +} + +module.exports = flatten; + +},{"../collections/map":56,"../internals/baseFlatten":95}],21:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseIndexOf = require('../internals/baseIndexOf'), + sortedIndex = require('./sortedIndex'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMax = Math.max; + +/** + * Gets the index at which the first occurrence of `value` is found using + * strict equality for comparisons, i.e. `===`. If the array is already sorted + * providing `true` for `fromIndex` will run a faster binary search. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {*} value The value to search for. + * @param {boolean|number} [fromIndex=0] The index to search from or `true` + * to perform a binary search on a sorted array. + * @returns {number} Returns the index of the matched value or `-1`. + * @example + * + * _.indexOf([1, 2, 3, 1, 2, 3], 2); + * // => 1 + * + * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 4 + * + * _.indexOf([1, 1, 2, 2, 3, 3], 2, true); + * // => 2 + */ +function indexOf(array, value, fromIndex) { + if (typeof fromIndex == 'number') { + var length = array ? array.length : 0; + fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0); + } else if (fromIndex) { + var index = sortedIndex(array, value); + return array[index] === value ? index : -1; + } + return baseIndexOf(array, value, fromIndex); +} + +module.exports = indexOf; + +},{"../internals/baseIndexOf":96,"./sortedIndex":30}],22:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + slice = require('../internals/slice'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMax = Math.max, + nativeMin = Math.min; + +/** + * Gets all but the last element or last `n` elements of an array. If a + * callback is provided elements at the end of the array are excluded from + * the result as long as the callback returns truey. The callback is bound + * to `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|number|string} [callback=1] The function called + * per element or the number of elements to exclude. If a property name or + * object is provided it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a slice of `array`. + * @example + * + * _.initial([1, 2, 3]); + * // => [1, 2] + * + * _.initial([1, 2, 3], 2); + * // => [1] + * + * _.initial([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [1] + * + * var characters = [ + * { 'name': 'barney', 'blocked': false, 'employer': 'slate' }, + * { 'name': 'fred', 'blocked': true, 'employer': 'slate' }, + * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.initial(characters, 'blocked'); + * // => [{ 'name': 'barney', 'blocked': false, 'employer': 'slate' }] + * + * // using "_.where" callback shorthand + * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name'); + * // => ['barney', 'fred'] + */ +function initial(array, callback, thisArg) { + var n = 0, + length = array ? array.length : 0; + + if (typeof callback != 'number' && callback != null) { + var index = length; + callback = createCallback(callback, thisArg, 3); + while (index-- && callback(array[index], index, array)) { + n++; + } + } else { + n = (callback == null || thisArg) ? 1 : callback || n; + } + return slice(array, 0, nativeMin(nativeMax(0, length - n), length)); +} + +module.exports = initial; + +},{"../functions/createCallback":76,"../internals/slice":129}],23:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseIndexOf = require('../internals/baseIndexOf'), + cacheIndexOf = require('../internals/cacheIndexOf'), + createCache = require('../internals/createCache'), + getArray = require('../internals/getArray'), + isArguments = require('../objects/isArguments'), + isArray = require('../objects/isArray'), + largeArraySize = require('../internals/largeArraySize'), + releaseArray = require('../internals/releaseArray'), + releaseObject = require('../internals/releaseObject'); + +/** + * Creates an array of unique values present in all provided arrays using + * strict equality for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {...Array} [array] The arrays to inspect. + * @returns {Array} Returns an array of shared values. + * @example + * + * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]); + * // => [1, 2] + */ +function intersection() { + var args = [], + argsIndex = -1, + argsLength = arguments.length, + caches = getArray(), + indexOf = baseIndexOf, + trustIndexOf = indexOf === baseIndexOf, + seen = getArray(); + + while (++argsIndex < argsLength) { + var value = arguments[argsIndex]; + if (isArray(value) || isArguments(value)) { + args.push(value); + caches.push(trustIndexOf && value.length >= largeArraySize && + createCache(argsIndex ? args[argsIndex] : seen)); + } + } + var array = args[0], + index = -1, + length = array ? array.length : 0, + result = []; + + outer: + while (++index < length) { + var cache = caches[0]; + value = array[index]; + + if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) { + argsIndex = argsLength; + (cache || seen).push(value); + while (--argsIndex) { + cache = caches[argsIndex]; + if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) { + continue outer; + } + } + result.push(value); + } + } + while (argsLength--) { + cache = caches[argsLength]; + if (cache) { + releaseObject(cache); + } + } + releaseArray(caches); + releaseArray(seen); + return result; +} + +module.exports = intersection; + +},{"../internals/baseIndexOf":96,"../internals/cacheIndexOf":101,"../internals/createCache":106,"../internals/getArray":110,"../internals/largeArraySize":116,"../internals/releaseArray":124,"../internals/releaseObject":125,"../objects/isArguments":146,"../objects/isArray":147}],24:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + slice = require('../internals/slice'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMax = Math.max; + +/** + * Gets the last element or last `n` elements of an array. If a callback is + * provided elements at the end of the array are returned as long as the + * callback returns truey. The callback is bound to `thisArg` and invoked + * with three arguments; (value, index, array). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|number|string} [callback] The function called + * per element or the number of elements to return. If a property name or + * object is provided it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the last element(s) of `array`. + * @example + * + * _.last([1, 2, 3]); + * // => 3 + * + * _.last([1, 2, 3], 2); + * // => [2, 3] + * + * _.last([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [2, 3] + * + * var characters = [ + * { 'name': 'barney', 'blocked': false, 'employer': 'slate' }, + * { 'name': 'fred', 'blocked': true, 'employer': 'slate' }, + * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.pluck(_.last(characters, 'blocked'), 'name'); + * // => ['fred', 'pebbles'] + * + * // using "_.where" callback shorthand + * _.last(characters, { 'employer': 'na' }); + * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }] + */ +function last(array, callback, thisArg) { + var n = 0, + length = array ? array.length : 0; + + if (typeof callback != 'number' && callback != null) { + var index = length; + callback = createCallback(callback, thisArg, 3); + while (index-- && callback(array[index], index, array)) { + n++; + } + } else { + n = callback; + if (n == null || thisArg) { + return array ? array[length - 1] : undefined; + } + } + return slice(array, nativeMax(0, length - n)); +} + +module.exports = last; + +},{"../functions/createCallback":76,"../internals/slice":129}],25:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMax = Math.max, + nativeMin = Math.min; + +/** + * Gets the index at which the last occurrence of `value` is found using strict + * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used + * as the offset from the end of the collection. + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to search. + * @param {*} value The value to search for. + * @param {number} [fromIndex=array.length-1] The index to search from. + * @returns {number} Returns the index of the matched value or `-1`. + * @example + * + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); + * // => 4 + * + * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); + * // => 1 + */ +function lastIndexOf(array, value, fromIndex) { + var index = array ? array.length : 0; + if (typeof fromIndex == 'number') { + index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1; + } + while (index--) { + if (array[index] === value) { + return index; + } + } + return -1; +} + +module.exports = lastIndexOf; + +},{}],26:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Used for `Array` method references. + * + * Normally `Array.prototype` would suffice, however, using an array literal + * avoids issues in Narwhal. + */ +var arrayRef = []; + +/** Native method shortcuts */ +var splice = arrayRef.splice; + +/** + * Removes all provided values from the given array using strict equality for + * comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to modify. + * @param {...*} [value] The values to remove. + * @returns {Array} Returns `array`. + * @example + * + * var array = [1, 2, 3, 1, 2, 3]; + * _.pull(array, 2, 3); + * console.log(array); + * // => [1, 1] + */ +function pull(array) { + var args = arguments, + argsIndex = 0, + argsLength = args.length, + length = array ? array.length : 0; + + while (++argsIndex < argsLength) { + var index = -1, + value = args[argsIndex]; + while (++index < length) { + if (array[index] === value) { + splice.call(array, index--, 1); + length--; + } + } + } + return array; +} + +module.exports = pull; + +},{}],27:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Native method shortcuts */ +var ceil = Math.ceil; + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMax = Math.max; + +/** + * Creates an array of numbers (positive and/or negative) progressing from + * `start` up to but not including `end`. If `start` is less than `stop` a + * zero-length range is created unless a negative `step` is specified. + * + * @static + * @memberOf _ + * @category Arrays + * @param {number} [start=0] The start of the range. + * @param {number} end The end of the range. + * @param {number} [step=1] The value to increment or decrement by. + * @returns {Array} Returns a new range array. + * @example + * + * _.range(4); + * // => [0, 1, 2, 3] + * + * _.range(1, 5); + * // => [1, 2, 3, 4] + * + * _.range(0, 20, 5); + * // => [0, 5, 10, 15] + * + * _.range(0, -4, -1); + * // => [0, -1, -2, -3] + * + * _.range(1, 4, 0); + * // => [1, 1, 1] + * + * _.range(0); + * // => [] + */ +function range(start, end, step) { + start = +start || 0; + step = typeof step == 'number' ? step : (+step || 1); + + if (end == null) { + end = start; + start = 0; + } + // use `Array(length)` so engines like Chakra and V8 avoid slower modes + // http://youtu.be/XAqIpGU8ZZk#t=17m25s + var index = -1, + length = nativeMax(0, ceil((end - start) / (step || 1))), + result = Array(length); + + while (++index < length) { + result[index] = start; + start += step; + } + return result; +} + +module.exports = range; + +},{}],28:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'); + +/** + * Used for `Array` method references. + * + * Normally `Array.prototype` would suffice, however, using an array literal + * avoids issues in Narwhal. + */ +var arrayRef = []; + +/** Native method shortcuts */ +var splice = arrayRef.splice; + +/** + * Removes all elements from an array that the callback returns truey for + * and returns an array of removed elements. The callback is bound to `thisArg` + * and invoked with three arguments; (value, index, array). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to modify. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of removed elements. + * @example + * + * var array = [1, 2, 3, 4, 5, 6]; + * var evens = _.remove(array, function(num) { return num % 2 == 0; }); + * + * console.log(array); + * // => [1, 3, 5] + * + * console.log(evens); + * // => [2, 4, 6] + */ +function remove(array, callback, thisArg) { + var index = -1, + length = array ? array.length : 0, + result = []; + + callback = createCallback(callback, thisArg, 3); + while (++index < length) { + var value = array[index]; + if (callback(value, index, array)) { + result.push(value); + splice.call(array, index--, 1); + length--; + } + } + return result; +} + +module.exports = remove; + +},{"../functions/createCallback":76}],29:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + slice = require('../internals/slice'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMax = Math.max; + +/** + * The opposite of `_.initial` this method gets all but the first element or + * first `n` elements of an array. If a callback function is provided elements + * at the beginning of the array are excluded from the result as long as the + * callback returns truey. The callback is bound to `thisArg` and invoked + * with three arguments; (value, index, array). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias drop, tail + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|number|string} [callback=1] The function called + * per element or the number of elements to exclude. If a property name or + * object is provided it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a slice of `array`. + * @example + * + * _.rest([1, 2, 3]); + * // => [2, 3] + * + * _.rest([1, 2, 3], 2); + * // => [3] + * + * _.rest([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [3] + * + * var characters = [ + * { 'name': 'barney', 'blocked': true, 'employer': 'slate' }, + * { 'name': 'fred', 'blocked': false, 'employer': 'slate' }, + * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.pluck(_.rest(characters, 'blocked'), 'name'); + * // => ['fred', 'pebbles'] + * + * // using "_.where" callback shorthand + * _.rest(characters, { 'employer': 'slate' }); + * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }] + */ +function rest(array, callback, thisArg) { + if (typeof callback != 'number' && callback != null) { + var n = 0, + index = -1, + length = array ? array.length : 0; + + callback = createCallback(callback, thisArg, 3); + while (++index < length && callback(array[index], index, array)) { + n++; + } + } else { + n = (callback == null || thisArg) ? 1 : nativeMax(0, callback); + } + return slice(array, n); +} + +module.exports = rest; + +},{"../functions/createCallback":76,"../internals/slice":129}],30:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + identity = require('../utilities/identity'); + +/** + * Uses a binary search to determine the smallest index at which a value + * should be inserted into a given sorted array in order to maintain the sort + * order of the array. If a callback is provided it will be executed for + * `value` and each element of `array` to compute their sort ranking. The + * callback is bound to `thisArg` and invoked with one argument; (value). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to inspect. + * @param {*} value The value to evaluate. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + * @example + * + * _.sortedIndex([20, 30, 50], 40); + * // => 2 + * + * // using "_.pluck" callback shorthand + * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x'); + * // => 2 + * + * var dict = { + * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 } + * }; + * + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { + * return dict.wordToNumber[word]; + * }); + * // => 2 + * + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { + * return this.wordToNumber[word]; + * }, dict); + * // => 2 + */ +function sortedIndex(array, value, callback, thisArg) { + var low = 0, + high = array ? array.length : low; + + // explicitly reference `identity` for better inlining in Firefox + callback = callback ? createCallback(callback, thisArg, 1) : identity; + value = callback(value); + + while (low < high) { + var mid = (low + high) >>> 1; + (callback(array[mid]) < value) + ? low = mid + 1 + : high = mid; + } + return low; +} + +module.exports = sortedIndex; + +},{"../functions/createCallback":76,"../utilities/identity":175}],31:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseFlatten = require('../internals/baseFlatten'), + baseUniq = require('../internals/baseUniq'); + +/** + * Creates an array of unique values, in order, of the provided arrays using + * strict equality for comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {...Array} [array] The arrays to inspect. + * @returns {Array} Returns an array of combined values. + * @example + * + * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]); + * // => [1, 2, 3, 5, 4] + */ +function union() { + return baseUniq(baseFlatten(arguments, true, true)); +} + +module.exports = union; + +},{"../internals/baseFlatten":95,"../internals/baseUniq":100}],32:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseUniq = require('../internals/baseUniq'), + createCallback = require('../functions/createCallback'); + +/** + * Creates a duplicate-value-free version of an array using strict equality + * for comparisons, i.e. `===`. If the array is sorted, providing + * `true` for `isSorted` will use a faster algorithm. If a callback is provided + * each element of `array` is passed through the callback before uniqueness + * is computed. The callback is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias unique + * @category Arrays + * @param {Array} array The array to process. + * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a duplicate-value-free array. + * @example + * + * _.uniq([1, 2, 1, 3, 1]); + * // => [1, 2, 3] + * + * _.uniq([1, 1, 2, 2, 3], true); + * // => [1, 2, 3] + * + * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); }); + * // => ['A', 'b', 'C'] + * + * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math); + * // => [1, 2.5, 3] + * + * // using "_.pluck" callback shorthand + * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }, { 'x': 2 }] + */ +function uniq(array, isSorted, callback, thisArg) { + // juggle arguments + if (typeof isSorted != 'boolean' && isSorted != null) { + thisArg = callback; + callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted; + isSorted = false; + } + if (callback != null) { + callback = createCallback(callback, thisArg, 3); + } + return baseUniq(array, isSorted, callback); +} + +module.exports = uniq; + +},{"../functions/createCallback":76,"../internals/baseUniq":100}],33:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseDifference = require('../internals/baseDifference'), + slice = require('../internals/slice'); + +/** + * Creates an array excluding all provided values using strict equality for + * comparisons, i.e. `===`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to filter. + * @param {...*} [value] The values to exclude. + * @returns {Array} Returns a new array of filtered values. + * @example + * + * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); + * // => [2, 3, 4] + */ +function without(array) { + return baseDifference(array, slice(arguments, 1)); +} + +module.exports = without; + +},{"../internals/baseDifference":94,"../internals/slice":129}],34:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseDifference = require('../internals/baseDifference'), + baseUniq = require('../internals/baseUniq'), + isArguments = require('../objects/isArguments'), + isArray = require('../objects/isArray'); + +/** + * Creates an array that is the symmetric difference of the provided arrays. + * See http://en.wikipedia.org/wiki/Symmetric_difference. + * + * @static + * @memberOf _ + * @category Arrays + * @param {...Array} [array] The arrays to inspect. + * @returns {Array} Returns an array of values. + * @example + * + * _.xor([1, 2, 3], [5, 2, 1, 4]); + * // => [3, 5, 4] + * + * _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]); + * // => [1, 4, 5] + */ +function xor() { + var index = -1, + length = arguments.length; + + while (++index < length) { + var array = arguments[index]; + if (isArray(array) || isArguments(array)) { + var result = result + ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result))) + : array; + } + } + return result || []; +} + +module.exports = xor; + +},{"../internals/baseDifference":94,"../internals/baseUniq":100,"../objects/isArguments":146,"../objects/isArray":147}],35:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var max = require('../collections/max'), + pluck = require('../collections/pluck'); + +/** + * Creates an array of grouped elements, the first of which contains the first + * elements of the given arrays, the second of which contains the second + * elements of the given arrays, and so on. + * + * @static + * @memberOf _ + * @alias unzip + * @category Arrays + * @param {...Array} [array] Arrays to process. + * @returns {Array} Returns a new array of grouped elements. + * @example + * + * _.zip(['fred', 'barney'], [30, 40], [true, false]); + * // => [['fred', 30, true], ['barney', 40, false]] + */ +function zip() { + var array = arguments.length > 1 ? arguments : arguments[0], + index = -1, + length = array ? max(pluck(array, 'length')) : 0, + result = Array(length < 0 ? 0 : length); + + while (++index < length) { + result[index] = pluck(array, index); + } + return result; +} + +module.exports = zip; + +},{"../collections/max":57,"../collections/pluck":59}],36:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isArray = require('../objects/isArray'); + +/** + * Creates an object composed from arrays of `keys` and `values`. Provide + * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]` + * or two arrays, one of `keys` and one of corresponding `values`. + * + * @static + * @memberOf _ + * @alias object + * @category Arrays + * @param {Array} keys The array of keys. + * @param {Array} [values=[]] The array of values. + * @returns {Object} Returns an object composed of the given keys and + * corresponding values. + * @example + * + * _.zipObject(['fred', 'barney'], [30, 40]); + * // => { 'fred': 30, 'barney': 40 } + */ +function zipObject(keys, values) { + var index = -1, + length = keys ? keys.length : 0, + result = {}; + + if (!values && length && !isArray(keys[0])) { + values = []; + } + while (++index < length) { + var key = keys[index]; + if (values) { + result[key] = values[index]; + } else if (key) { + result[key[0]] = key[1]; + } + } + return result; +} + +module.exports = zipObject; + +},{"../objects/isArray":147}],37:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +module.exports = { + 'chain': require('./chaining/chain'), + 'tap': require('./chaining/tap'), + 'value': require('./chaining/wrapperValueOf'), + 'wrapperChain': require('./chaining/wrapperChain'), + 'wrapperToString': require('./chaining/wrapperToString'), + 'wrapperValueOf': require('./chaining/wrapperValueOf') +}; + +},{"./chaining/chain":38,"./chaining/tap":39,"./chaining/wrapperChain":40,"./chaining/wrapperToString":41,"./chaining/wrapperValueOf":42}],38:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var lodashWrapper = require('../internals/lodashWrapper'); + +/** + * Creates a `lodash` object that wraps the given value with explicit + * method chaining enabled. + * + * @static + * @memberOf _ + * @category Chaining + * @param {*} value The value to wrap. + * @returns {Object} Returns the wrapper object. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 }, + * { 'name': 'pebbles', 'age': 1 } + * ]; + * + * var youngest = _.chain(characters) + * .sortBy('age') + * .map(function(chr) { return chr.name + ' is ' + chr.age; }) + * .first() + * .value(); + * // => 'pebbles is 1' + */ +function chain(value) { + value = new lodashWrapper(value); + value.__chain__ = true; + return value; +} + +module.exports = chain; + +},{"../internals/lodashWrapper":117}],39:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Invokes `interceptor` with the `value` as the first argument and then + * returns `value`. The purpose of this method is to "tap into" a method + * chain in order to perform operations on intermediate results within + * the chain. + * + * @static + * @memberOf _ + * @category Chaining + * @param {*} value The value to provide to `interceptor`. + * @param {Function} interceptor The function to invoke. + * @returns {*} Returns `value`. + * @example + * + * _([1, 2, 3, 4]) + * .tap(function(array) { array.pop(); }) + * .reverse() + * .value(); + * // => [3, 2, 1] + */ +function tap(value, interceptor) { + interceptor(value); + return value; +} + +module.exports = tap; + +},{}],40:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Enables explicit method chaining on the wrapper object. + * + * @name chain + * @memberOf _ + * @category Chaining + * @returns {*} Returns the wrapper object. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * // without explicit chaining + * _(characters).first(); + * // => { 'name': 'barney', 'age': 36 } + * + * // with explicit chaining + * _(characters).chain() + * .first() + * .pick('age') + * .value(); + * // => { 'age': 36 } + */ +function wrapperChain() { + this.__chain__ = true; + return this; +} + +module.exports = wrapperChain; + +},{}],41:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Produces the `toString` result of the wrapped value. + * + * @name toString + * @memberOf _ + * @category Chaining + * @returns {string} Returns the string result. + * @example + * + * _([1, 2, 3]).toString(); + * // => '1,2,3' + */ +function wrapperToString() { + return String(this.__wrapped__); +} + +module.exports = wrapperToString; + +},{}],42:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var forEach = require('../collections/forEach'), + support = require('../support'); + +/** + * Extracts the wrapped value. + * + * @name valueOf + * @memberOf _ + * @alias value + * @category Chaining + * @returns {*} Returns the wrapped value. + * @example + * + * _([1, 2, 3]).valueOf(); + * // => [1, 2, 3] + */ +function wrapperValueOf() { + return this.__wrapped__; +} + +module.exports = wrapperValueOf; + +},{"../collections/forEach":51,"../support":171}],43:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +module.exports = { + 'all': require('./collections/every'), + 'any': require('./collections/some'), + 'at': require('./collections/at'), + 'collect': require('./collections/map'), + 'contains': require('./collections/contains'), + 'countBy': require('./collections/countBy'), + 'detect': require('./collections/find'), + 'each': require('./collections/forEach'), + 'eachRight': require('./collections/forEachRight'), + 'every': require('./collections/every'), + 'filter': require('./collections/filter'), + 'find': require('./collections/find'), + 'findLast': require('./collections/findLast'), + 'findWhere': require('./collections/find'), + 'foldl': require('./collections/reduce'), + 'foldr': require('./collections/reduceRight'), + 'forEach': require('./collections/forEach'), + 'forEachRight': require('./collections/forEachRight'), + 'groupBy': require('./collections/groupBy'), + 'include': require('./collections/contains'), + 'indexBy': require('./collections/indexBy'), + 'inject': require('./collections/reduce'), + 'invoke': require('./collections/invoke'), + 'map': require('./collections/map'), + 'max': require('./collections/max'), + 'min': require('./collections/min'), + 'pluck': require('./collections/pluck'), + 'reduce': require('./collections/reduce'), + 'reduceRight': require('./collections/reduceRight'), + 'reject': require('./collections/reject'), + 'sample': require('./collections/sample'), + 'select': require('./collections/filter'), + 'shuffle': require('./collections/shuffle'), + 'size': require('./collections/size'), + 'some': require('./collections/some'), + 'sortBy': require('./collections/sortBy'), + 'toArray': require('./collections/toArray'), + 'where': require('./collections/where') +}; + +},{"./collections/at":44,"./collections/contains":45,"./collections/countBy":46,"./collections/every":47,"./collections/filter":48,"./collections/find":49,"./collections/findLast":50,"./collections/forEach":51,"./collections/forEachRight":52,"./collections/groupBy":53,"./collections/indexBy":54,"./collections/invoke":55,"./collections/map":56,"./collections/max":57,"./collections/min":58,"./collections/pluck":59,"./collections/reduce":60,"./collections/reduceRight":61,"./collections/reject":62,"./collections/sample":63,"./collections/shuffle":64,"./collections/size":65,"./collections/some":66,"./collections/sortBy":67,"./collections/toArray":68,"./collections/where":69}],44:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseFlatten = require('../internals/baseFlatten'), + isString = require('../objects/isString'); + +/** + * Creates an array of elements from the specified indexes, or keys, of the + * `collection`. Indexes may be specified as individual arguments or as arrays + * of indexes. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {...(number|number[]|string|string[])} [index] The indexes of `collection` + * to retrieve, specified as individual indexes or arrays of indexes. + * @returns {Array} Returns a new array of elements corresponding to the + * provided indexes. + * @example + * + * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]); + * // => ['a', 'c', 'e'] + * + * _.at(['fred', 'barney', 'pebbles'], 0, 2); + * // => ['fred', 'pebbles'] + */ +function at(collection) { + var args = arguments, + index = -1, + props = baseFlatten(args, true, false, 1), + length = (args[2] && args[2][args[1]] === collection) ? 1 : props.length, + result = Array(length); + + while(++index < length) { + result[index] = collection[props[index]]; + } + return result; +} + +module.exports = at; + +},{"../internals/baseFlatten":95,"../objects/isString":161}],45:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseIndexOf = require('../internals/baseIndexOf'), + forOwn = require('../objects/forOwn'), + isArray = require('../objects/isArray'), + isString = require('../objects/isString'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMax = Math.max; + +/** + * Checks if a given value is present in a collection using strict equality + * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the + * offset from the end of the collection. + * + * @static + * @memberOf _ + * @alias include + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {*} target The value to check for. + * @param {number} [fromIndex=0] The index to search from. + * @returns {boolean} Returns `true` if the `target` element is found, else `false`. + * @example + * + * _.contains([1, 2, 3], 1); + * // => true + * + * _.contains([1, 2, 3], 1, 2); + * // => false + * + * _.contains({ 'name': 'fred', 'age': 40 }, 'fred'); + * // => true + * + * _.contains('pebbles', 'eb'); + * // => true + */ +function contains(collection, target, fromIndex) { + var index = -1, + indexOf = baseIndexOf, + length = collection ? collection.length : 0, + result = false; + + fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0; + if (isArray(collection)) { + result = indexOf(collection, target, fromIndex) > -1; + } else if (typeof length == 'number') { + result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1; + } else { + forOwn(collection, function(value) { + if (++index >= fromIndex) { + return !(result = value === target); + } + }); + } + return result; +} + +module.exports = contains; + +},{"../internals/baseIndexOf":96,"../objects/forOwn":141,"../objects/isArray":147,"../objects/isString":161}],46:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createAggregator = require('../internals/createAggregator'); + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Native method shortcuts */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Creates an object composed of keys generated from the results of running + * each element of `collection` through the callback. The corresponding value + * of each key is the number of times the key was returned by the callback. + * The callback is bound to `thisArg` and invoked with three arguments; + * (value, index|key, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': 1, '6': 2 } + * + * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': 1, '6': 2 } + * + * _.countBy(['one', 'two', 'three'], 'length'); + * // => { '3': 2, '5': 1 } + */ +var countBy = createAggregator(function(result, value, key) { + (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1); +}); + +module.exports = countBy; + +},{"../internals/createAggregator":105}],47:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forOwn = require('../objects/forOwn'); + +/** + * Checks if the given callback returns truey value for **all** elements of + * a collection. The callback is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias all + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {boolean} Returns `true` if all elements passed the callback check, + * else `false`. + * @example + * + * _.every([true, 1, null, 'yes']); + * // => false + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.every(characters, 'age'); + * // => true + * + * // using "_.where" callback shorthand + * _.every(characters, { 'age': 36 }); + * // => false + */ +function every(collection, callback, thisArg) { + var result = true; + callback = createCallback(callback, thisArg, 3); + + var index = -1, + length = collection ? collection.length : 0; + + if (typeof length == 'number') { + while (++index < length) { + if (!(result = !!callback(collection[index], index, collection))) { + break; + } + } + } else { + forOwn(collection, function(value, index, collection) { + return (result = !!callback(value, index, collection)); + }); + } + return result; +} + +module.exports = every; + +},{"../functions/createCallback":76,"../objects/forOwn":141}],48:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forOwn = require('../objects/forOwn'); + +/** + * Iterates over elements of a collection, returning an array of all elements + * the callback returns truey for. The callback is bound to `thisArg` and + * invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias select + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that passed the callback check. + * @example + * + * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [2, 4, 6] + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.filter(characters, 'blocked'); + * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }] + * + * // using "_.where" callback shorthand + * _.filter(characters, { 'age': 36 }); + * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }] + */ +function filter(collection, callback, thisArg) { + var result = []; + callback = createCallback(callback, thisArg, 3); + + var index = -1, + length = collection ? collection.length : 0; + + if (typeof length == 'number') { + while (++index < length) { + var value = collection[index]; + if (callback(value, index, collection)) { + result.push(value); + } + } + } else { + forOwn(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result.push(value); + } + }); + } + return result; +} + +module.exports = filter; + +},{"../functions/createCallback":76,"../objects/forOwn":141}],49:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forOwn = require('../objects/forOwn'); + +/** + * Iterates over elements of a collection, returning the first element that + * the callback returns truey for. The callback is bound to `thisArg` and + * invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias detect, findWhere + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the found element, else `undefined`. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true }, + * { 'name': 'pebbles', 'age': 1, 'blocked': false } + * ]; + * + * _.find(characters, function(chr) { + * return chr.age < 40; + * }); + * // => { 'name': 'barney', 'age': 36, 'blocked': false } + * + * // using "_.where" callback shorthand + * _.find(characters, { 'age': 1 }); + * // => { 'name': 'pebbles', 'age': 1, 'blocked': false } + * + * // using "_.pluck" callback shorthand + * _.find(characters, 'blocked'); + * // => { 'name': 'fred', 'age': 40, 'blocked': true } + */ +function find(collection, callback, thisArg) { + callback = createCallback(callback, thisArg, 3); + + var index = -1, + length = collection ? collection.length : 0; + + if (typeof length == 'number') { + while (++index < length) { + var value = collection[index]; + if (callback(value, index, collection)) { + return value; + } + } + } else { + var result; + forOwn(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result = value; + return false; + } + }); + return result; + } +} + +module.exports = find; + +},{"../functions/createCallback":76,"../objects/forOwn":141}],50:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forEachRight = require('./forEachRight'); + +/** + * This method is like `_.find` except that it iterates over elements + * of a `collection` from right to left. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the found element, else `undefined`. + * @example + * + * _.findLast([1, 2, 3, 4], function(num) { + * return num % 2 == 1; + * }); + * // => 3 + */ +function findLast(collection, callback, thisArg) { + var result; + callback = createCallback(callback, thisArg, 3); + forEachRight(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result = value; + return false; + } + }); + return result; +} + +module.exports = findLast; + +},{"../functions/createCallback":76,"./forEachRight":52}],51:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreateCallback = require('../internals/baseCreateCallback'), + forOwn = require('../objects/forOwn'); + +/** + * Iterates over elements of a collection, executing the callback for each + * element. The callback is bound to `thisArg` and invoked with three arguments; + * (value, index|key, collection). Callbacks may exit iteration early by + * explicitly returning `false`. + * + * Note: As with other "Collections" methods, objects with a `length` property + * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn` + * may be used for object iteration. + * + * @static + * @memberOf _ + * @alias each + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array|Object|string} Returns `collection`. + * @example + * + * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(','); + * // => logs each number and returns '1,2,3' + * + * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); }); + * // => logs each number and returns the object (property order is not guaranteed across environments) + */ +function forEach(collection, callback, thisArg) { + var index = -1, + length = collection ? collection.length : 0; + + callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3); + if (typeof length == 'number') { + while (++index < length) { + if (callback(collection[index], index, collection) === false) { + break; + } + } + } else { + forOwn(collection, callback); + } + return collection; +} + +module.exports = forEach; + +},{"../internals/baseCreateCallback":92,"../objects/forOwn":141}],52:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreateCallback = require('../internals/baseCreateCallback'), + forOwn = require('../objects/forOwn'), + isArray = require('../objects/isArray'), + isString = require('../objects/isString'), + keys = require('../objects/keys'); + +/** + * This method is like `_.forEach` except that it iterates over elements + * of a `collection` from right to left. + * + * @static + * @memberOf _ + * @alias eachRight + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array|Object|string} Returns `collection`. + * @example + * + * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(','); + * // => logs each number from right to left and returns '3,2,1' + */ +function forEachRight(collection, callback, thisArg) { + var length = collection ? collection.length : 0; + callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3); + if (typeof length == 'number') { + while (length--) { + if (callback(collection[length], length, collection) === false) { + break; + } + } + } else { + var props = keys(collection); + length = props.length; + forOwn(collection, function(value, key, collection) { + key = props ? props[--length] : --length; + return callback(collection[key], key, collection); + }); + } + return collection; +} + +module.exports = forEachRight; + +},{"../internals/baseCreateCallback":92,"../objects/forOwn":141,"../objects/isArray":147,"../objects/isString":161,"../objects/keys":163}],53:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createAggregator = require('../internals/createAggregator'); + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Native method shortcuts */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Creates an object composed of keys generated from the results of running + * each element of a collection through the callback. The corresponding value + * of each key is an array of the elements responsible for generating the key. + * The callback is bound to `thisArg` and invoked with three arguments; + * (value, index|key, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false` + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': [4.2], '6': [6.1, 6.4] } + * + * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': [4.2], '6': [6.1, 6.4] } + * + * // using "_.pluck" callback shorthand + * _.groupBy(['one', 'two', 'three'], 'length'); + * // => { '3': ['one', 'two'], '5': ['three'] } + */ +var groupBy = createAggregator(function(result, value, key) { + (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value); +}); + +module.exports = groupBy; + +},{"../internals/createAggregator":105}],54:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createAggregator = require('../internals/createAggregator'); + +/** + * Creates an object composed of keys generated from the results of running + * each element of the collection through the given callback. The corresponding + * value of each key is the last element responsible for generating the key. + * The callback is bound to `thisArg` and invoked with three arguments; + * (value, index|key, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * var keys = [ + * { 'dir': 'left', 'code': 97 }, + * { 'dir': 'right', 'code': 100 } + * ]; + * + * _.indexBy(keys, 'dir'); + * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } } + * + * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); }); + * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } + * + * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String); + * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } + */ +var indexBy = createAggregator(function(result, value, key) { + result[key] = value; +}); + +module.exports = indexBy; + +},{"../internals/createAggregator":105}],55:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var forEach = require('./forEach'), + slice = require('../internals/slice'); + +/** + * Invokes the method named by `methodName` on each element in the `collection` + * returning an array of the results of each invoked method. Additional arguments + * will be provided to each invoked method. If `methodName` is a function it + * will be invoked for, and `this` bound to, each element in the `collection`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|string} methodName The name of the method to invoke or + * the function invoked per iteration. + * @param {...*} [arg] Arguments to invoke the method with. + * @returns {Array} Returns a new array of the results of each invoked method. + * @example + * + * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); + * // => [[1, 5, 7], [1, 2, 3]] + * + * _.invoke([123, 456], String.prototype.split, ''); + * // => [['1', '2', '3'], ['4', '5', '6']] + */ +function invoke(collection, methodName) { + var args = slice(arguments, 2), + index = -1, + isFunc = typeof methodName == 'function', + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + forEach(collection, function(value) { + result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args); + }); + return result; +} + +module.exports = invoke; + +},{"../internals/slice":129,"./forEach":51}],56:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forOwn = require('../objects/forOwn'); + +/** + * Creates an array of values by running each element in the collection + * through the callback. The callback is bound to `thisArg` and invoked with + * three arguments; (value, index|key, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias collect + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of the results of each `callback` execution. + * @example + * + * _.map([1, 2, 3], function(num) { return num * 3; }); + * // => [3, 6, 9] + * + * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); + * // => [3, 6, 9] (property order is not guaranteed across environments) + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.map(characters, 'name'); + * // => ['barney', 'fred'] + */ +function map(collection, callback, thisArg) { + var index = -1, + length = collection ? collection.length : 0; + + callback = createCallback(callback, thisArg, 3); + if (typeof length == 'number') { + var result = Array(length); + while (++index < length) { + result[index] = callback(collection[index], index, collection); + } + } else { + result = []; + forOwn(collection, function(value, key, collection) { + result[++index] = callback(value, key, collection); + }); + } + return result; +} + +module.exports = map; + +},{"../functions/createCallback":76,"../objects/forOwn":141}],57:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var charAtCallback = require('../internals/charAtCallback'), + createCallback = require('../functions/createCallback'), + forEach = require('./forEach'), + forOwn = require('../objects/forOwn'), + isArray = require('../objects/isArray'), + isString = require('../objects/isString'); + +/** + * Retrieves the maximum value of a collection. If the collection is empty or + * falsey `-Infinity` is returned. If a callback is provided it will be executed + * for each value in the collection to generate the criterion by which the value + * is ranked. The callback is bound to `thisArg` and invoked with three + * arguments; (value, index, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the maximum value. + * @example + * + * _.max([4, 2, 8, 6]); + * // => 8 + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * _.max(characters, function(chr) { return chr.age; }); + * // => { 'name': 'fred', 'age': 40 }; + * + * // using "_.pluck" callback shorthand + * _.max(characters, 'age'); + * // => { 'name': 'fred', 'age': 40 }; + */ +function max(collection, callback, thisArg) { + var computed = -Infinity, + result = computed; + + // allows working with functions like `_.map` without using + // their `index` argument as a callback + if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) { + callback = null; + } + if (callback == null && isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (value > result) { + result = value; + } + } + } else { + callback = (callback == null && isString(collection)) + ? charAtCallback + : createCallback(callback, thisArg, 3); + + forEach(collection, function(value, index, collection) { + var current = callback(value, index, collection); + if (current > computed) { + computed = current; + result = value; + } + }); + } + return result; +} + +module.exports = max; + +},{"../functions/createCallback":76,"../internals/charAtCallback":103,"../objects/forOwn":141,"../objects/isArray":147,"../objects/isString":161,"./forEach":51}],58:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var charAtCallback = require('../internals/charAtCallback'), + createCallback = require('../functions/createCallback'), + forEach = require('./forEach'), + forOwn = require('../objects/forOwn'), + isArray = require('../objects/isArray'), + isString = require('../objects/isString'); + +/** + * Retrieves the minimum value of a collection. If the collection is empty or + * falsey `Infinity` is returned. If a callback is provided it will be executed + * for each value in the collection to generate the criterion by which the value + * is ranked. The callback is bound to `thisArg` and invoked with three + * arguments; (value, index, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the minimum value. + * @example + * + * _.min([4, 2, 8, 6]); + * // => 2 + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * _.min(characters, function(chr) { return chr.age; }); + * // => { 'name': 'barney', 'age': 36 }; + * + * // using "_.pluck" callback shorthand + * _.min(characters, 'age'); + * // => { 'name': 'barney', 'age': 36 }; + */ +function min(collection, callback, thisArg) { + var computed = Infinity, + result = computed; + + // allows working with functions like `_.map` without using + // their `index` argument as a callback + if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) { + callback = null; + } + if (callback == null && isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (value < result) { + result = value; + } + } + } else { + callback = (callback == null && isString(collection)) + ? charAtCallback + : createCallback(callback, thisArg, 3); + + forEach(collection, function(value, index, collection) { + var current = callback(value, index, collection); + if (current < computed) { + computed = current; + result = value; + } + }); + } + return result; +} + +module.exports = min; + +},{"../functions/createCallback":76,"../internals/charAtCallback":103,"../objects/forOwn":141,"../objects/isArray":147,"../objects/isString":161,"./forEach":51}],59:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var map = require('./map'); + +/** + * Retrieves the value of a specified property from all elements in the collection. + * + * @static + * @memberOf _ + * @type Function + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {string} property The name of the property to pluck. + * @returns {Array} Returns a new array of property values. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * _.pluck(characters, 'name'); + * // => ['barney', 'fred'] + */ +var pluck = map; + +module.exports = pluck; + +},{"./map":56}],60:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forOwn = require('../objects/forOwn'); + +/** + * Reduces a collection to a value which is the accumulated result of running + * each element in the collection through the callback, where each successive + * callback execution consumes the return value of the previous execution. If + * `accumulator` is not provided the first element of the collection will be + * used as the initial `accumulator` value. The callback is bound to `thisArg` + * and invoked with four arguments; (accumulator, value, index|key, collection). + * + * @static + * @memberOf _ + * @alias foldl, inject + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [accumulator] Initial value of the accumulator. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the accumulated value. + * @example + * + * var sum = _.reduce([1, 2, 3], function(sum, num) { + * return sum + num; + * }); + * // => 6 + * + * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { + * result[key] = num * 3; + * return result; + * }, {}); + * // => { 'a': 3, 'b': 6, 'c': 9 } + */ +function reduce(collection, callback, accumulator, thisArg) { + if (!collection) return accumulator; + var noaccum = arguments.length < 3; + callback = createCallback(callback, thisArg, 4); + + var index = -1, + length = collection.length; + + if (typeof length == 'number') { + if (noaccum) { + accumulator = collection[++index]; + } + while (++index < length) { + accumulator = callback(accumulator, collection[index], index, collection); + } + } else { + forOwn(collection, function(value, index, collection) { + accumulator = noaccum + ? (noaccum = false, value) + : callback(accumulator, value, index, collection) + }); + } + return accumulator; +} + +module.exports = reduce; + +},{"../functions/createCallback":76,"../objects/forOwn":141}],61:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forEachRight = require('./forEachRight'); + +/** + * This method is like `_.reduce` except that it iterates over elements + * of a `collection` from right to left. + * + * @static + * @memberOf _ + * @alias foldr + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [accumulator] Initial value of the accumulator. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the accumulated value. + * @example + * + * var list = [[0, 1], [2, 3], [4, 5]]; + * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); + * // => [4, 5, 2, 3, 0, 1] + */ +function reduceRight(collection, callback, accumulator, thisArg) { + var noaccum = arguments.length < 3; + callback = createCallback(callback, thisArg, 4); + forEachRight(collection, function(value, index, collection) { + accumulator = noaccum + ? (noaccum = false, value) + : callback(accumulator, value, index, collection); + }); + return accumulator; +} + +module.exports = reduceRight; + +},{"../functions/createCallback":76,"./forEachRight":52}],62:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + filter = require('./filter'); + +/** + * The opposite of `_.filter` this method returns the elements of a + * collection that the callback does **not** return truey for. + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that failed the callback check. + * @example + * + * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * // => [1, 3, 5] + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.reject(characters, 'blocked'); + * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }] + * + * // using "_.where" callback shorthand + * _.reject(characters, { 'age': 36 }); + * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }] + */ +function reject(collection, callback, thisArg) { + callback = createCallback(callback, thisArg, 3); + return filter(collection, function(value, index, collection) { + return !callback(value, index, collection); + }); +} + +module.exports = reject; + +},{"../functions/createCallback":76,"./filter":48}],63:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseRandom = require('../internals/baseRandom'), + isString = require('../objects/isString'), + shuffle = require('./shuffle'), + values = require('../objects/values'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMax = Math.max, + nativeMin = Math.min; + +/** + * Retrieves a random element or `n` random elements from a collection. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to sample. + * @param {number} [n] The number of elements to sample. + * @param- {Object} [guard] Allows working with functions like `_.map` + * without using their `index` arguments as `n`. + * @returns {Array} Returns the random sample(s) of `collection`. + * @example + * + * _.sample([1, 2, 3, 4]); + * // => 2 + * + * _.sample([1, 2, 3, 4], 2); + * // => [3, 1] + */ +function sample(collection, n, guard) { + if (collection && typeof collection.length != 'number') { + collection = values(collection); + } + if (n == null || guard) { + return collection ? collection[baseRandom(0, collection.length - 1)] : undefined; + } + var result = shuffle(collection); + result.length = nativeMin(nativeMax(0, n), result.length); + return result; +} + +module.exports = sample; + +},{"../internals/baseRandom":99,"../objects/isString":161,"../objects/values":170,"./shuffle":64}],64:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseRandom = require('../internals/baseRandom'), + forEach = require('./forEach'); + +/** + * Creates an array of shuffled values, using a version of the Fisher-Yates + * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to shuffle. + * @returns {Array} Returns a new shuffled collection. + * @example + * + * _.shuffle([1, 2, 3, 4, 5, 6]); + * // => [4, 1, 6, 3, 5, 2] + */ +function shuffle(collection) { + var index = -1, + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + forEach(collection, function(value) { + var rand = baseRandom(0, ++index); + result[index] = result[rand]; + result[rand] = value; + }); + return result; +} + +module.exports = shuffle; + +},{"../internals/baseRandom":99,"./forEach":51}],65:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var keys = require('../objects/keys'); + +/** + * Gets the size of the `collection` by returning `collection.length` for arrays + * and array-like objects or the number of own enumerable properties for objects. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to inspect. + * @returns {number} Returns `collection.length` or number of own enumerable properties. + * @example + * + * _.size([1, 2]); + * // => 2 + * + * _.size({ 'one': 1, 'two': 2, 'three': 3 }); + * // => 3 + * + * _.size('pebbles'); + * // => 7 + */ +function size(collection) { + var length = collection ? collection.length : 0; + return typeof length == 'number' ? length : keys(collection).length; +} + +module.exports = size; + +},{"../objects/keys":163}],66:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forOwn = require('../objects/forOwn'), + isArray = require('../objects/isArray'); + +/** + * Checks if the callback returns a truey value for **any** element of a + * collection. The function returns as soon as it finds a passing value and + * does not iterate over the entire collection. The callback is bound to + * `thisArg` and invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @alias any + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {boolean} Returns `true` if any element passed the callback check, + * else `false`. + * @example + * + * _.some([null, 0, 'yes', false], Boolean); + * // => true + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.some(characters, 'blocked'); + * // => true + * + * // using "_.where" callback shorthand + * _.some(characters, { 'age': 1 }); + * // => false + */ +function some(collection, callback, thisArg) { + var result; + callback = createCallback(callback, thisArg, 3); + + var index = -1, + length = collection ? collection.length : 0; + + if (typeof length == 'number') { + while (++index < length) { + if ((result = callback(collection[index], index, collection))) { + break; + } + } + } else { + forOwn(collection, function(value, index, collection) { + return !(result = callback(value, index, collection)); + }); + } + return !!result; +} + +module.exports = some; + +},{"../functions/createCallback":76,"../objects/forOwn":141,"../objects/isArray":147}],67:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var compareAscending = require('../internals/compareAscending'), + createCallback = require('../functions/createCallback'), + forEach = require('./forEach'), + getArray = require('../internals/getArray'), + getObject = require('../internals/getObject'), + isArray = require('../objects/isArray'), + map = require('./map'), + releaseArray = require('../internals/releaseArray'), + releaseObject = require('../internals/releaseObject'); + +/** + * Creates an array of elements, sorted in ascending order by the results of + * running each element in a collection through the callback. This method + * performs a stable sort, that is, it will preserve the original sort order + * of equal elements. The callback is bound to `thisArg` and invoked with + * three arguments; (value, index|key, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an array of property names is provided for `callback` the collection + * will be sorted by each property value. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Array|Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of sorted elements. + * @example + * + * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); + * // => [3, 1, 2] + * + * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); + * // => [3, 1, 2] + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 }, + * { 'name': 'barney', 'age': 26 }, + * { 'name': 'fred', 'age': 30 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.map(_.sortBy(characters, 'age'), _.values); + * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]] + * + * // sorting by multiple properties + * _.map(_.sortBy(characters, ['name', 'age']), _.values); + * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]] + */ +function sortBy(collection, callback, thisArg) { + var index = -1, + isArr = isArray(callback), + length = collection ? collection.length : 0, + result = Array(typeof length == 'number' ? length : 0); + + if (!isArr) { + callback = createCallback(callback, thisArg, 3); + } + forEach(collection, function(value, key, collection) { + var object = result[++index] = getObject(); + if (isArr) { + object.criteria = map(callback, function(key) { return value[key]; }); + } else { + (object.criteria = getArray())[0] = callback(value, key, collection); + } + object.index = index; + object.value = value; + }); + + length = result.length; + result.sort(compareAscending); + while (length--) { + var object = result[length]; + result[length] = object.value; + if (!isArr) { + releaseArray(object.criteria); + } + releaseObject(object); + } + return result; +} + +module.exports = sortBy; + +},{"../functions/createCallback":76,"../internals/compareAscending":104,"../internals/getArray":110,"../internals/getObject":111,"../internals/releaseArray":124,"../internals/releaseObject":125,"../objects/isArray":147,"./forEach":51,"./map":56}],68:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isString = require('../objects/isString'), + slice = require('../internals/slice'), + values = require('../objects/values'); + +/** + * Converts the `collection` to an array. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to convert. + * @returns {Array} Returns the new converted array. + * @example + * + * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); + * // => [2, 3, 4] + */ +function toArray(collection) { + if (collection && typeof collection.length == 'number') { + return slice(collection); + } + return values(collection); +} + +module.exports = toArray; + +},{"../internals/slice":129,"../objects/isString":161,"../objects/values":170}],69:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var filter = require('./filter'); + +/** + * Performs a deep comparison of each element in a `collection` to the given + * `properties` object, returning an array of all elements that have equivalent + * property values. + * + * @static + * @memberOf _ + * @type Function + * @category Collections + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Object} props The object of property values to filter by. + * @returns {Array} Returns a new array of elements that have the given properties. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }, + * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] } + * ]; + * + * _.where(characters, { 'age': 36 }); + * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }] + * + * _.where(characters, { 'pets': ['dino'] }); + * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }] + */ +var where = filter; + +module.exports = where; + +},{"./filter":48}],70:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +module.exports = { + 'after': require('./functions/after'), + 'bind': require('./functions/bind'), + 'bindAll': require('./functions/bindAll'), + 'bindKey': require('./functions/bindKey'), + 'compose': require('./functions/compose'), + 'createCallback': require('./functions/createCallback'), + 'curry': require('./functions/curry'), + 'debounce': require('./functions/debounce'), + 'defer': require('./functions/defer'), + 'delay': require('./functions/delay'), + 'memoize': require('./functions/memoize'), + 'once': require('./functions/once'), + 'partial': require('./functions/partial'), + 'partialRight': require('./functions/partialRight'), + 'throttle': require('./functions/throttle'), + 'wrap': require('./functions/wrap') +}; + +},{"./functions/after":71,"./functions/bind":72,"./functions/bindAll":73,"./functions/bindKey":74,"./functions/compose":75,"./functions/createCallback":76,"./functions/curry":77,"./functions/debounce":78,"./functions/defer":79,"./functions/delay":80,"./functions/memoize":81,"./functions/once":82,"./functions/partial":83,"./functions/partialRight":84,"./functions/throttle":85,"./functions/wrap":86}],71:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isFunction = require('../objects/isFunction'); + +/** + * Creates a function that executes `func`, with the `this` binding and + * arguments of the created function, only after being called `n` times. + * + * @static + * @memberOf _ + * @category Functions + * @param {number} n The number of times the function must be called before + * `func` is executed. + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var saves = ['profile', 'settings']; + * + * var done = _.after(saves.length, function() { + * console.log('Done saving!'); + * }); + * + * _.forEach(saves, function(type) { + * asyncSave({ 'type': type, 'complete': done }); + * }); + * // => logs 'Done saving!', after all saves have completed + */ +function after(n, func) { + if (!isFunction(func)) { + throw new TypeError; + } + return function() { + if (--n < 1) { + return func.apply(this, arguments); + } + }; +} + +module.exports = after; + +},{"../objects/isFunction":154}],72:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createWrapper = require('../internals/createWrapper'), + slice = require('../internals/slice'); + +/** + * Creates a function that, when called, invokes `func` with the `this` + * binding of `thisArg` and prepends any additional `bind` arguments to those + * provided to the bound function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to bind. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {...*} [arg] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var func = function(greeting) { + * return greeting + ' ' + this.name; + * }; + * + * func = _.bind(func, { 'name': 'fred' }, 'hi'); + * func(); + * // => 'hi fred' + */ +function bind(func, thisArg) { + return arguments.length > 2 + ? createWrapper(func, 17, slice(arguments, 2), null, thisArg) + : createWrapper(func, 1, null, null, thisArg); +} + +module.exports = bind; + +},{"../internals/createWrapper":107,"../internals/slice":129}],73:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseFlatten = require('../internals/baseFlatten'), + createWrapper = require('../internals/createWrapper'), + functions = require('../objects/functions'); + +/** + * Binds methods of an object to the object itself, overwriting the existing + * method. Method names may be specified as individual arguments or as arrays + * of method names. If no method names are provided all the function properties + * of `object` will be bound. + * + * @static + * @memberOf _ + * @category Functions + * @param {Object} object The object to bind and assign the bound methods to. + * @param {...string} [methodName] The object method names to + * bind, specified as individual method names or arrays of method names. + * @returns {Object} Returns `object`. + * @example + * + * var view = { + * 'label': 'docs', + * 'onClick': function() { console.log('clicked ' + this.label); } + * }; + * + * _.bindAll(view); + * jQuery('#docs').on('click', view.onClick); + * // => logs 'clicked docs', when the button is clicked + */ +function bindAll(object) { + var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object), + index = -1, + length = funcs.length; + + while (++index < length) { + var key = funcs[index]; + object[key] = createWrapper(object[key], 1, null, null, object); + } + return object; +} + +module.exports = bindAll; + +},{"../internals/baseFlatten":95,"../internals/createWrapper":107,"../objects/functions":143}],74:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createWrapper = require('../internals/createWrapper'), + slice = require('../internals/slice'); + +/** + * Creates a function that, when called, invokes the method at `object[key]` + * and prepends any additional `bindKey` arguments to those provided to the bound + * function. This method differs from `_.bind` by allowing bound functions to + * reference methods that will be redefined or don't yet exist. + * See http://michaux.ca/articles/lazy-function-definition-pattern. + * + * @static + * @memberOf _ + * @category Functions + * @param {Object} object The object the method belongs to. + * @param {string} key The key of the method. + * @param {...*} [arg] Arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var object = { + * 'name': 'fred', + * 'greet': function(greeting) { + * return greeting + ' ' + this.name; + * } + * }; + * + * var func = _.bindKey(object, 'greet', 'hi'); + * func(); + * // => 'hi fred' + * + * object.greet = function(greeting) { + * return greeting + 'ya ' + this.name + '!'; + * }; + * + * func(); + * // => 'hiya fred!' + */ +function bindKey(object, key) { + return arguments.length > 2 + ? createWrapper(key, 19, slice(arguments, 2), null, object) + : createWrapper(key, 3, null, null, object); +} + +module.exports = bindKey; + +},{"../internals/createWrapper":107,"../internals/slice":129}],75:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isFunction = require('../objects/isFunction'); + +/** + * Creates a function that is the composition of the provided functions, + * where each function consumes the return value of the function that follows. + * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. + * Each function is executed with the `this` binding of the composed function. + * + * @static + * @memberOf _ + * @category Functions + * @param {...Function} [func] Functions to compose. + * @returns {Function} Returns the new composed function. + * @example + * + * var realNameMap = { + * 'pebbles': 'penelope' + * }; + * + * var format = function(name) { + * name = realNameMap[name.toLowerCase()] || name; + * return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase(); + * }; + * + * var greet = function(formatted) { + * return 'Hiya ' + formatted + '!'; + * }; + * + * var welcome = _.compose(greet, format); + * welcome('pebbles'); + * // => 'Hiya Penelope!' + */ +function compose() { + var funcs = arguments, + length = funcs.length; + + while (length--) { + if (!isFunction(funcs[length])) { + throw new TypeError; + } + } + return function() { + var args = arguments, + length = funcs.length; + + while (length--) { + args = [funcs[length].apply(this, args)]; + } + return args[0]; + }; +} + +module.exports = compose; + +},{"../objects/isFunction":154}],76:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreateCallback = require('../internals/baseCreateCallback'), + baseIsEqual = require('../internals/baseIsEqual'), + isObject = require('../objects/isObject'), + keys = require('../objects/keys'), + property = require('../utilities/property'); + +/** + * Produces a callback bound to an optional `thisArg`. If `func` is a property + * name the created callback will return the property value for a given element. + * If `func` is an object the created callback will return `true` for elements + * that contain the equivalent object properties, otherwise it will return `false`. + * + * @static + * @memberOf _ + * @category Utilities + * @param {*} [func=identity] The value to convert to a callback. + * @param {*} [thisArg] The `this` binding of the created callback. + * @param {number} [argCount] The number of arguments the callback accepts. + * @returns {Function} Returns a callback function. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * // wrap to create custom callback shorthands + * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) { + * var match = /^(.+?)__([gl]t)(.+)$/.exec(callback); + * return !match ? func(callback, thisArg) : function(object) { + * return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3]; + * }; + * }); + * + * _.filter(characters, 'age__gt38'); + * // => [{ 'name': 'fred', 'age': 40 }] + */ +function createCallback(func, thisArg, argCount) { + var type = typeof func; + if (func == null || type == 'function') { + return baseCreateCallback(func, thisArg, argCount); + } + // handle "_.pluck" style callback shorthands + if (type != 'object') { + return property(func); + } + var props = keys(func), + key = props[0], + a = func[key]; + + // handle "_.where" style callback shorthands + if (props.length == 1 && a === a && !isObject(a)) { + // fast path the common case of providing an object with a single + // property containing a primitive value + return function(object) { + var b = object[key]; + return a === b && (a !== 0 || (1 / a == 1 / b)); + }; + } + return function(object) { + var length = props.length, + result = false; + + while (length--) { + if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) { + break; + } + } + return result; + }; +} + +module.exports = createCallback; + +},{"../internals/baseCreateCallback":92,"../internals/baseIsEqual":97,"../objects/isObject":158,"../objects/keys":163,"../utilities/property":181}],77:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createWrapper = require('../internals/createWrapper'); + +/** + * Creates a function which accepts one or more arguments of `func` that when + * invoked either executes `func` returning its result, if all `func` arguments + * have been provided, or returns a function that accepts one or more of the + * remaining `func` arguments, and so on. The arity of `func` can be specified + * if `func.length` is not sufficient. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to curry. + * @param {number} [arity=func.length] The arity of `func`. + * @returns {Function} Returns the new curried function. + * @example + * + * var curried = _.curry(function(a, b, c) { + * console.log(a + b + c); + * }); + * + * curried(1)(2)(3); + * // => 6 + * + * curried(1, 2)(3); + * // => 6 + * + * curried(1, 2, 3); + * // => 6 + */ +function curry(func, arity) { + arity = typeof arity == 'number' ? arity : (+arity || func.length); + return createWrapper(func, 4, null, null, null, arity); +} + +module.exports = curry; + +},{"../internals/createWrapper":107}],78:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isFunction = require('../objects/isFunction'), + isObject = require('../objects/isObject'), + now = require('../utilities/now'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMax = Math.max; + +/** + * Creates a function that will delay the execution of `func` until after + * `wait` milliseconds have elapsed since the last time it was invoked. + * Provide an options object to indicate that `func` should be invoked on + * the leading and/or trailing edge of the `wait` timeout. Subsequent calls + * to the debounced function will return the result of the last `func` call. + * + * Note: If `leading` and `trailing` options are `true` `func` will be called + * on the trailing edge of the timeout only if the the debounced function is + * invoked more than once during the `wait` timeout. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to debounce. + * @param {number} wait The number of milliseconds to delay. + * @param {Object} [options] The options object. + * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout. + * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called. + * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example + * + * // avoid costly calculations while the window size is in flux + * var lazyLayout = _.debounce(calculateLayout, 150); + * jQuery(window).on('resize', lazyLayout); + * + * // execute `sendMail` when the click event is fired, debouncing subsequent calls + * jQuery('#postbox').on('click', _.debounce(sendMail, 300, { + * 'leading': true, + * 'trailing': false + * }); + * + * // ensure `batchLog` is executed once after 1 second of debounced calls + * var source = new EventSource('/stream'); + * source.addEventListener('message', _.debounce(batchLog, 250, { + * 'maxWait': 1000 + * }, false); + */ +function debounce(func, wait, options) { + var args, + maxTimeoutId, + result, + stamp, + thisArg, + timeoutId, + trailingCall, + lastCalled = 0, + maxWait = false, + trailing = true; + + if (!isFunction(func)) { + throw new TypeError; + } + wait = nativeMax(0, wait) || 0; + if (options === true) { + var leading = true; + trailing = false; + } else if (isObject(options)) { + leading = options.leading; + maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0); + trailing = 'trailing' in options ? options.trailing : trailing; + } + var delayed = function() { + var remaining = wait - (now() - stamp); + if (remaining <= 0) { + if (maxTimeoutId) { + clearTimeout(maxTimeoutId); + } + var isCalled = trailingCall; + maxTimeoutId = timeoutId = trailingCall = undefined; + if (isCalled) { + lastCalled = now(); + result = func.apply(thisArg, args); + if (!timeoutId && !maxTimeoutId) { + args = thisArg = null; + } + } + } else { + timeoutId = setTimeout(delayed, remaining); + } + }; + + var maxDelayed = function() { + if (timeoutId) { + clearTimeout(timeoutId); + } + maxTimeoutId = timeoutId = trailingCall = undefined; + if (trailing || (maxWait !== wait)) { + lastCalled = now(); + result = func.apply(thisArg, args); + if (!timeoutId && !maxTimeoutId) { + args = thisArg = null; + } + } + }; + + return function() { + args = arguments; + stamp = now(); + thisArg = this; + trailingCall = trailing && (timeoutId || !leading); + + if (maxWait === false) { + var leadingCall = leading && !timeoutId; + } else { + if (!maxTimeoutId && !leading) { + lastCalled = stamp; + } + var remaining = maxWait - (stamp - lastCalled), + isCalled = remaining <= 0; + + if (isCalled) { + if (maxTimeoutId) { + maxTimeoutId = clearTimeout(maxTimeoutId); + } + lastCalled = stamp; + result = func.apply(thisArg, args); + } + else if (!maxTimeoutId) { + maxTimeoutId = setTimeout(maxDelayed, remaining); + } + } + if (isCalled && timeoutId) { + timeoutId = clearTimeout(timeoutId); + } + else if (!timeoutId && wait !== maxWait) { + timeoutId = setTimeout(delayed, wait); + } + if (leadingCall) { + isCalled = true; + result = func.apply(thisArg, args); + } + if (isCalled && !timeoutId && !maxTimeoutId) { + args = thisArg = null; + } + return result; + }; +} + +module.exports = debounce; + +},{"../objects/isFunction":154,"../objects/isObject":158,"../utilities/now":179}],79:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isFunction = require('../objects/isFunction'), + slice = require('../internals/slice'); + +/** + * Defers executing the `func` function until the current call stack has cleared. + * Additional arguments will be provided to `func` when it is invoked. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to defer. + * @param {...*} [arg] Arguments to invoke the function with. + * @returns {number} Returns the timer id. + * @example + * + * _.defer(function(text) { console.log(text); }, 'deferred'); + * // logs 'deferred' after one or more milliseconds + */ +function defer(func) { + if (!isFunction(func)) { + throw new TypeError; + } + var args = slice(arguments, 1); + return setTimeout(function() { func.apply(undefined, args); }, 1); +} + +module.exports = defer; + +},{"../internals/slice":129,"../objects/isFunction":154}],80:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isFunction = require('../objects/isFunction'), + slice = require('../internals/slice'); + +/** + * Executes the `func` function after `wait` milliseconds. Additional arguments + * will be provided to `func` when it is invoked. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to delay. + * @param {number} wait The number of milliseconds to delay execution. + * @param {...*} [arg] Arguments to invoke the function with. + * @returns {number} Returns the timer id. + * @example + * + * _.delay(function(text) { console.log(text); }, 1000, 'later'); + * // => logs 'later' after one second + */ +function delay(func, wait) { + if (!isFunction(func)) { + throw new TypeError; + } + var args = slice(arguments, 2); + return setTimeout(function() { func.apply(undefined, args); }, wait); +} + +module.exports = delay; + +},{"../internals/slice":129,"../objects/isFunction":154}],81:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isFunction = require('../objects/isFunction'), + keyPrefix = require('../internals/keyPrefix'); + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Native method shortcuts */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Creates a function that memoizes the result of `func`. If `resolver` is + * provided it will be used to determine the cache key for storing the result + * based on the arguments provided to the memoized function. By default, the + * first argument provided to the memoized function is used as the cache key. + * The `func` is executed with the `this` binding of the memoized function. + * The result cache is exposed as the `cache` property on the memoized function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to have its output memoized. + * @param {Function} [resolver] A function used to resolve the cache key. + * @returns {Function} Returns the new memoizing function. + * @example + * + * var fibonacci = _.memoize(function(n) { + * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); + * }); + * + * fibonacci(9) + * // => 34 + * + * var data = { + * 'fred': { 'name': 'fred', 'age': 40 }, + * 'pebbles': { 'name': 'pebbles', 'age': 1 } + * }; + * + * // modifying the result cache + * var get = _.memoize(function(name) { return data[name]; }, _.identity); + * get('pebbles'); + * // => { 'name': 'pebbles', 'age': 1 } + * + * get.cache.pebbles.name = 'penelope'; + * get('pebbles'); + * // => { 'name': 'penelope', 'age': 1 } + */ +function memoize(func, resolver) { + if (!isFunction(func)) { + throw new TypeError; + } + var memoized = function() { + var cache = memoized.cache, + key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0]; + + return hasOwnProperty.call(cache, key) + ? cache[key] + : (cache[key] = func.apply(this, arguments)); + } + memoized.cache = {}; + return memoized; +} + +module.exports = memoize; + +},{"../internals/keyPrefix":115,"../objects/isFunction":154}],82:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isFunction = require('../objects/isFunction'); + +/** + * Creates a function that is restricted to execute `func` once. Repeat calls to + * the function will return the value of the first call. The `func` is executed + * with the `this` binding of the created function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var initialize = _.once(createApplication); + * initialize(); + * initialize(); + * // `initialize` executes `createApplication` once + */ +function once(func) { + var ran, + result; + + if (!isFunction(func)) { + throw new TypeError; + } + return function() { + if (ran) { + return result; + } + ran = true; + result = func.apply(this, arguments); + + // clear the `func` variable so the function may be garbage collected + func = null; + return result; + }; +} + +module.exports = once; + +},{"../objects/isFunction":154}],83:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createWrapper = require('../internals/createWrapper'), + slice = require('../internals/slice'); + +/** + * Creates a function that, when called, invokes `func` with any additional + * `partial` arguments prepended to those provided to the new function. This + * method is similar to `_.bind` except it does **not** alter the `this` binding. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to partially apply arguments to. + * @param {...*} [arg] Arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * var greet = function(greeting, name) { return greeting + ' ' + name; }; + * var hi = _.partial(greet, 'hi'); + * hi('fred'); + * // => 'hi fred' + */ +function partial(func) { + return createWrapper(func, 16, slice(arguments, 1)); +} + +module.exports = partial; + +},{"../internals/createWrapper":107,"../internals/slice":129}],84:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createWrapper = require('../internals/createWrapper'), + slice = require('../internals/slice'); + +/** + * This method is like `_.partial` except that `partial` arguments are + * appended to those provided to the new function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to partially apply arguments to. + * @param {...*} [arg] Arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * var defaultsDeep = _.partialRight(_.merge, _.defaults); + * + * var options = { + * 'variable': 'data', + * 'imports': { 'jq': $ } + * }; + * + * defaultsDeep(options, _.templateSettings); + * + * options.variable + * // => 'data' + * + * options.imports + * // => { '_': _, 'jq': $ } + */ +function partialRight(func) { + return createWrapper(func, 32, null, slice(arguments, 1)); +} + +module.exports = partialRight; + +},{"../internals/createWrapper":107,"../internals/slice":129}],85:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var debounce = require('./debounce'), + isFunction = require('../objects/isFunction'), + isObject = require('../objects/isObject'); + +/** Used as an internal `_.debounce` options object */ +var debounceOptions = { + 'leading': false, + 'maxWait': 0, + 'trailing': false +}; + +/** + * Creates a function that, when executed, will only call the `func` function + * at most once per every `wait` milliseconds. Provide an options object to + * indicate that `func` should be invoked on the leading and/or trailing edge + * of the `wait` timeout. Subsequent calls to the throttled function will + * return the result of the last `func` call. + * + * Note: If `leading` and `trailing` options are `true` `func` will be called + * on the trailing edge of the timeout only if the the throttled function is + * invoked more than once during the `wait` timeout. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} func The function to throttle. + * @param {number} wait The number of milliseconds to throttle executions to. + * @param {Object} [options] The options object. + * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout. + * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout. + * @returns {Function} Returns the new throttled function. + * @example + * + * // avoid excessively updating the position while scrolling + * var throttled = _.throttle(updatePosition, 100); + * jQuery(window).on('scroll', throttled); + * + * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes + * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, { + * 'trailing': false + * })); + */ +function throttle(func, wait, options) { + var leading = true, + trailing = true; + + if (!isFunction(func)) { + throw new TypeError; + } + if (options === false) { + leading = false; + } else if (isObject(options)) { + leading = 'leading' in options ? options.leading : leading; + trailing = 'trailing' in options ? options.trailing : trailing; + } + debounceOptions.leading = leading; + debounceOptions.maxWait = wait; + debounceOptions.trailing = trailing; + + return debounce(func, wait, debounceOptions); +} + +module.exports = throttle; + +},{"../objects/isFunction":154,"../objects/isObject":158,"./debounce":78}],86:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createWrapper = require('../internals/createWrapper'); + +/** + * Creates a function that provides `value` to the wrapper function as its + * first argument. Additional arguments provided to the function are appended + * to those provided to the wrapper function. The wrapper is executed with + * the `this` binding of the created function. + * + * @static + * @memberOf _ + * @category Functions + * @param {*} value The value to wrap. + * @param {Function} wrapper The wrapper function. + * @returns {Function} Returns the new function. + * @example + * + * var p = _.wrap(_.escape, function(func, text) { + * return '

    ' + func(text) + '

    '; + * }); + * + * p('Fred, Wilma, & Pebbles'); + * // => '

    Fred, Wilma, & Pebbles

    ' + */ +function wrap(value, wrapper) { + return createWrapper(wrapper, 16, [value]); +} + +module.exports = wrap; + +},{"../internals/createWrapper":107}],87:[function(require,module,exports){ +/** + * @license + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var arrays = require('./arrays'), + chaining = require('./chaining'), + collections = require('./collections'), + functions = require('./functions'), + objects = require('./objects'), + utilities = require('./utilities'), + forEach = require('./collections/forEach'), + forOwn = require('./objects/forOwn'), + isArray = require('./objects/isArray'), + lodashWrapper = require('./internals/lodashWrapper'), + mixin = require('./utilities/mixin'), + support = require('./support'), + templateSettings = require('./utilities/templateSettings'); + +/** + * Used for `Array` method references. + * + * Normally `Array.prototype` would suffice, however, using an array literal + * avoids issues in Narwhal. + */ +var arrayRef = []; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Native method shortcuts */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Creates a `lodash` object which wraps the given value to enable intuitive + * method chaining. + * + * In addition to Lo-Dash methods, wrappers also have the following `Array` methods: + * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`, + * and `unshift` + * + * Chaining is supported in custom builds as long as the `value` method is + * implicitly or explicitly included in the build. + * + * The chainable wrapper functions are: + * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, + * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`, + * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`, + * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`, + * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`, + * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`, + * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`, + * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, + * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`, + * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`, + * and `zip` + * + * The non-chainable wrapper functions are: + * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`, + * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`, + * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`, + * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`, + * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`, + * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`, + * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`, + * `template`, `unescape`, `uniqueId`, and `value` + * + * The wrapper functions `first` and `last` return wrapped values when `n` is + * provided, otherwise they return unwrapped values. + * + * Explicit chaining can be enabled by using the `_.chain` method. + * + * @name _ + * @constructor + * @category Chaining + * @param {*} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns a `lodash` instance. + * @example + * + * var wrapped = _([1, 2, 3]); + * + * // returns an unwrapped value + * wrapped.reduce(function(sum, num) { + * return sum + num; + * }); + * // => 6 + * + * // returns a wrapped value + * var squares = wrapped.map(function(num) { + * return num * num; + * }); + * + * _.isArray(squares); + * // => false + * + * _.isArray(squares.value()); + * // => true + */ +function lodash(value) { + // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor + return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__')) + ? value + : new lodashWrapper(value); +} +// ensure `new lodashWrapper` is an instance of `lodash` +lodashWrapper.prototype = lodash.prototype; + +// wrap `_.mixin` so it works when provided only one argument +mixin = (function(fn) { + var functions = objects.functions; + return function(object, source, options) { + if (!source || (!options && !functions(source).length)) { + if (options == null) { + options = source; + } + source = object; + object = lodash; + } + return fn(object, source, options); + }; +}(mixin)); + +// add functions that return wrapped values when chaining +lodash.after = functions.after; +lodash.assign = objects.assign; +lodash.at = collections.at; +lodash.bind = functions.bind; +lodash.bindAll = functions.bindAll; +lodash.bindKey = functions.bindKey; +lodash.chain = chaining.chain; +lodash.compact = arrays.compact; +lodash.compose = functions.compose; +lodash.constant = utilities.constant; +lodash.countBy = collections.countBy; +lodash.create = objects.create; +lodash.createCallback = functions.createCallback; +lodash.curry = functions.curry; +lodash.debounce = functions.debounce; +lodash.defaults = objects.defaults; +lodash.defer = functions.defer; +lodash.delay = functions.delay; +lodash.difference = arrays.difference; +lodash.filter = collections.filter; +lodash.flatten = arrays.flatten; +lodash.forEach = forEach; +lodash.forEachRight = collections.forEachRight; +lodash.forIn = objects.forIn; +lodash.forInRight = objects.forInRight; +lodash.forOwn = forOwn; +lodash.forOwnRight = objects.forOwnRight; +lodash.functions = objects.functions; +lodash.groupBy = collections.groupBy; +lodash.indexBy = collections.indexBy; +lodash.initial = arrays.initial; +lodash.intersection = arrays.intersection; +lodash.invert = objects.invert; +lodash.invoke = collections.invoke; +lodash.keys = objects.keys; +lodash.map = collections.map; +lodash.mapValues = objects.mapValues; +lodash.max = collections.max; +lodash.memoize = functions.memoize; +lodash.merge = objects.merge; +lodash.min = collections.min; +lodash.omit = objects.omit; +lodash.once = functions.once; +lodash.pairs = objects.pairs; +lodash.partial = functions.partial; +lodash.partialRight = functions.partialRight; +lodash.pick = objects.pick; +lodash.pluck = collections.pluck; +lodash.property = utilities.property; +lodash.pull = arrays.pull; +lodash.range = arrays.range; +lodash.reject = collections.reject; +lodash.remove = arrays.remove; +lodash.rest = arrays.rest; +lodash.shuffle = collections.shuffle; +lodash.sortBy = collections.sortBy; +lodash.tap = chaining.tap; +lodash.throttle = functions.throttle; +lodash.times = utilities.times; +lodash.toArray = collections.toArray; +lodash.transform = objects.transform; +lodash.union = arrays.union; +lodash.uniq = arrays.uniq; +lodash.values = objects.values; +lodash.where = collections.where; +lodash.without = arrays.without; +lodash.wrap = functions.wrap; +lodash.xor = arrays.xor; +lodash.zip = arrays.zip; +lodash.zipObject = arrays.zipObject; + +// add aliases +lodash.collect = collections.map; +lodash.drop = arrays.rest; +lodash.each = forEach; +lodash.eachRight = collections.forEachRight; +lodash.extend = objects.assign; +lodash.methods = objects.functions; +lodash.object = arrays.zipObject; +lodash.select = collections.filter; +lodash.tail = arrays.rest; +lodash.unique = arrays.uniq; +lodash.unzip = arrays.zip; + +// add functions to `lodash.prototype` +mixin(lodash); + +// add functions that return unwrapped values when chaining +lodash.clone = objects.clone; +lodash.cloneDeep = objects.cloneDeep; +lodash.contains = collections.contains; +lodash.escape = utilities.escape; +lodash.every = collections.every; +lodash.find = collections.find; +lodash.findIndex = arrays.findIndex; +lodash.findKey = objects.findKey; +lodash.findLast = collections.findLast; +lodash.findLastIndex = arrays.findLastIndex; +lodash.findLastKey = objects.findLastKey; +lodash.has = objects.has; +lodash.identity = utilities.identity; +lodash.indexOf = arrays.indexOf; +lodash.isArguments = objects.isArguments; +lodash.isArray = isArray; +lodash.isBoolean = objects.isBoolean; +lodash.isDate = objects.isDate; +lodash.isElement = objects.isElement; +lodash.isEmpty = objects.isEmpty; +lodash.isEqual = objects.isEqual; +lodash.isFinite = objects.isFinite; +lodash.isFunction = objects.isFunction; +lodash.isNaN = objects.isNaN; +lodash.isNull = objects.isNull; +lodash.isNumber = objects.isNumber; +lodash.isObject = objects.isObject; +lodash.isPlainObject = objects.isPlainObject; +lodash.isRegExp = objects.isRegExp; +lodash.isString = objects.isString; +lodash.isUndefined = objects.isUndefined; +lodash.lastIndexOf = arrays.lastIndexOf; +lodash.mixin = mixin; +lodash.noConflict = utilities.noConflict; +lodash.noop = utilities.noop; +lodash.now = utilities.now; +lodash.parseInt = utilities.parseInt; +lodash.random = utilities.random; +lodash.reduce = collections.reduce; +lodash.reduceRight = collections.reduceRight; +lodash.result = utilities.result; +lodash.size = collections.size; +lodash.some = collections.some; +lodash.sortedIndex = arrays.sortedIndex; +lodash.template = utilities.template; +lodash.unescape = utilities.unescape; +lodash.uniqueId = utilities.uniqueId; + +// add aliases +lodash.all = collections.every; +lodash.any = collections.some; +lodash.detect = collections.find; +lodash.findWhere = collections.find; +lodash.foldl = collections.reduce; +lodash.foldr = collections.reduceRight; +lodash.include = collections.contains; +lodash.inject = collections.reduce; + +mixin(function() { + var source = {} + forOwn(lodash, function(func, methodName) { + if (!lodash.prototype[methodName]) { + source[methodName] = func; + } + }); + return source; +}(), false); + +// add functions capable of returning wrapped and unwrapped values when chaining +lodash.first = arrays.first; +lodash.last = arrays.last; +lodash.sample = collections.sample; + +// add aliases +lodash.take = arrays.first; +lodash.head = arrays.first; + +forOwn(lodash, function(func, methodName) { + var callbackable = methodName !== 'sample'; + if (!lodash.prototype[methodName]) { + lodash.prototype[methodName]= function(n, guard) { + var chainAll = this.__chain__, + result = func(this.__wrapped__, n, guard); + + return !chainAll && (n == null || (guard && !(callbackable && typeof n == 'function'))) + ? result + : new lodashWrapper(result, chainAll); + }; + } +}); + +/** + * The semantic version number. + * + * @static + * @memberOf _ + * @type string + */ +lodash.VERSION = '2.4.1'; + +// add "Chaining" functions to the wrapper +lodash.prototype.chain = chaining.wrapperChain; +lodash.prototype.toString = chaining.wrapperToString; +lodash.prototype.value = chaining.wrapperValueOf; +lodash.prototype.valueOf = chaining.wrapperValueOf; + +// add `Array` functions that return unwrapped values +forEach(['join', 'pop', 'shift'], function(methodName) { + var func = arrayRef[methodName]; + lodash.prototype[methodName] = function() { + var chainAll = this.__chain__, + result = func.apply(this.__wrapped__, arguments); + + return chainAll + ? new lodashWrapper(result, chainAll) + : result; + }; +}); + +// add `Array` functions that return the existing wrapped value +forEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) { + var func = arrayRef[methodName]; + lodash.prototype[methodName] = function() { + func.apply(this.__wrapped__, arguments); + return this; + }; +}); + +// add `Array` functions that return new wrapped values +forEach(['concat', 'slice', 'splice'], function(methodName) { + var func = arrayRef[methodName]; + lodash.prototype[methodName] = function() { + return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__); + }; +}); + +lodash.support = support; +(lodash.templateSettings = utilities.templateSettings).imports._ = lodash; +module.exports = lodash; + +},{"./arrays":14,"./chaining":37,"./collections":43,"./collections/forEach":51,"./functions":70,"./internals/lodashWrapper":117,"./objects":131,"./objects/forOwn":141,"./objects/isArray":147,"./support":171,"./utilities":172,"./utilities/mixin":176,"./utilities/templateSettings":185}],88:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used to pool arrays and objects used internally */ +var arrayPool = []; + +module.exports = arrayPool; + +},{}],89:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreate = require('./baseCreate'), + isObject = require('../objects/isObject'), + setBindData = require('./setBindData'), + slice = require('./slice'); + +/** + * Used for `Array` method references. + * + * Normally `Array.prototype` would suffice, however, using an array literal + * avoids issues in Narwhal. + */ +var arrayRef = []; + +/** Native method shortcuts */ +var push = arrayRef.push; + +/** + * The base implementation of `_.bind` that creates the bound function and + * sets its meta data. + * + * @private + * @param {Array} bindData The bind data array. + * @returns {Function} Returns the new bound function. + */ +function baseBind(bindData) { + var func = bindData[0], + partialArgs = bindData[2], + thisArg = bindData[4]; + + function bound() { + // `Function#bind` spec + // http://es5.github.io/#x15.3.4.5 + if (partialArgs) { + // avoid `arguments` object deoptimizations by using `slice` instead + // of `Array.prototype.slice.call` and not assigning `arguments` to a + // variable as a ternary expression + var args = slice(partialArgs); + push.apply(args, arguments); + } + // mimic the constructor's `return` behavior + // http://es5.github.io/#x13.2.2 + if (this instanceof bound) { + // ensure `new bound` is an instance of `func` + var thisBinding = baseCreate(func.prototype), + result = func.apply(thisBinding, args || arguments); + return isObject(result) ? result : thisBinding; + } + return func.apply(thisArg, args || arguments); + } + setBindData(bound, bindData); + return bound; +} + +module.exports = baseBind; + +},{"../objects/isObject":158,"./baseCreate":91,"./setBindData":126,"./slice":129}],90:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var assign = require('../objects/assign'), + forEach = require('../collections/forEach'), + forOwn = require('../objects/forOwn'), + getArray = require('./getArray'), + isArray = require('../objects/isArray'), + isObject = require('../objects/isObject'), + releaseArray = require('./releaseArray'), + slice = require('./slice'); + +/** Used to match regexp flags from their coerced string values */ +var reFlags = /\w*$/; + +/** `Object#toString` result shortcuts */ +var argsClass = '[object Arguments]', + arrayClass = '[object Array]', + boolClass = '[object Boolean]', + dateClass = '[object Date]', + funcClass = '[object Function]', + numberClass = '[object Number]', + objectClass = '[object Object]', + regexpClass = '[object RegExp]', + stringClass = '[object String]'; + +/** Used to identify object classifications that `_.clone` supports */ +var cloneableClasses = {}; +cloneableClasses[funcClass] = false; +cloneableClasses[argsClass] = cloneableClasses[arrayClass] = +cloneableClasses[boolClass] = cloneableClasses[dateClass] = +cloneableClasses[numberClass] = cloneableClasses[objectClass] = +cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** Native method shortcuts */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** Used to lookup a built-in constructor by [[Class]] */ +var ctorByClass = {}; +ctorByClass[arrayClass] = Array; +ctorByClass[boolClass] = Boolean; +ctorByClass[dateClass] = Date; +ctorByClass[funcClass] = Function; +ctorByClass[objectClass] = Object; +ctorByClass[numberClass] = Number; +ctorByClass[regexpClass] = RegExp; +ctorByClass[stringClass] = String; + +/** + * The base implementation of `_.clone` without argument juggling or support + * for `thisArg` binding. + * + * @private + * @param {*} value The value to clone. + * @param {boolean} [isDeep=false] Specify a deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {Array} [stackA=[]] Tracks traversed source objects. + * @param {Array} [stackB=[]] Associates clones with source counterparts. + * @returns {*} Returns the cloned value. + */ +function baseClone(value, isDeep, callback, stackA, stackB) { + if (callback) { + var result = callback(value); + if (typeof result != 'undefined') { + return result; + } + } + // inspect [[Class]] + var isObj = isObject(value); + if (isObj) { + var className = toString.call(value); + if (!cloneableClasses[className]) { + return value; + } + var ctor = ctorByClass[className]; + switch (className) { + case boolClass: + case dateClass: + return new ctor(+value); + + case numberClass: + case stringClass: + return new ctor(value); + + case regexpClass: + result = ctor(value.source, reFlags.exec(value)); + result.lastIndex = value.lastIndex; + return result; + } + } else { + return value; + } + var isArr = isArray(value); + if (isDeep) { + // check for circular references and return corresponding clone + var initedStack = !stackA; + stackA || (stackA = getArray()); + stackB || (stackB = getArray()); + + var length = stackA.length; + while (length--) { + if (stackA[length] == value) { + return stackB[length]; + } + } + result = isArr ? ctor(value.length) : {}; + } + else { + result = isArr ? slice(value) : assign({}, value); + } + // add array properties assigned by `RegExp#exec` + if (isArr) { + if (hasOwnProperty.call(value, 'index')) { + result.index = value.index; + } + if (hasOwnProperty.call(value, 'input')) { + result.input = value.input; + } + } + // exit for shallow clone + if (!isDeep) { + return result; + } + // add the source value to the stack of traversed objects + // and associate it with its clone + stackA.push(value); + stackB.push(result); + + // recursively populate clone (susceptible to call stack limits) + (isArr ? forEach : forOwn)(value, function(objValue, key) { + result[key] = baseClone(objValue, isDeep, callback, stackA, stackB); + }); + + if (initedStack) { + releaseArray(stackA); + releaseArray(stackB); + } + return result; +} + +module.exports = baseClone; + +},{"../collections/forEach":51,"../objects/assign":132,"../objects/forOwn":141,"../objects/isArray":147,"../objects/isObject":158,"./getArray":110,"./releaseArray":124,"./slice":129}],91:[function(require,module,exports){ +var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isNative = require('./isNative'), + isObject = require('../objects/isObject'), + noop = require('../utilities/noop'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate; + +/** + * The base implementation of `_.create` without support for assigning + * properties to the created object. + * + * @private + * @param {Object} prototype The object to inherit from. + * @returns {Object} Returns the new object. + */ +function baseCreate(prototype, properties) { + return isObject(prototype) ? nativeCreate(prototype) : {}; +} +// fallback for browsers without `Object.create` +if (!nativeCreate) { + baseCreate = (function() { + function Object() {} + return function(prototype) { + if (isObject(prototype)) { + Object.prototype = prototype; + var result = new Object; + Object.prototype = null; + } + return result || global.Object(); + }; + }()); +} + +module.exports = baseCreate; + +},{"../objects/isObject":158,"../utilities/noop":178,"./isNative":114}],92:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var bind = require('../functions/bind'), + identity = require('../utilities/identity'), + setBindData = require('./setBindData'), + support = require('../support'); + +/** Used to detected named functions */ +var reFuncName = /^\s*function[ \n\r\t]+\w/; + +/** Used to detect functions containing a `this` reference */ +var reThis = /\bthis\b/; + +/** Native method shortcuts */ +var fnToString = Function.prototype.toString; + +/** + * The base implementation of `_.createCallback` without support for creating + * "_.pluck" or "_.where" style callbacks. + * + * @private + * @param {*} [func=identity] The value to convert to a callback. + * @param {*} [thisArg] The `this` binding of the created callback. + * @param {number} [argCount] The number of arguments the callback accepts. + * @returns {Function} Returns a callback function. + */ +function baseCreateCallback(func, thisArg, argCount) { + if (typeof func != 'function') { + return identity; + } + // exit early for no `thisArg` or already bound by `Function#bind` + if (typeof thisArg == 'undefined' || !('prototype' in func)) { + return func; + } + var bindData = func.__bindData__; + if (typeof bindData == 'undefined') { + if (support.funcNames) { + bindData = !func.name; + } + bindData = bindData || !support.funcDecomp; + if (!bindData) { + var source = fnToString.call(func); + if (!support.funcNames) { + bindData = !reFuncName.test(source); + } + if (!bindData) { + // checks if `func` references the `this` keyword and stores the result + bindData = reThis.test(source); + setBindData(func, bindData); + } + } + } + // exit early if there are no `this` references or `func` is bound + if (bindData === false || (bindData !== true && bindData[1] & 1)) { + return func; + } + switch (argCount) { + case 1: return function(value) { + return func.call(thisArg, value); + }; + case 2: return function(a, b) { + return func.call(thisArg, a, b); + }; + case 3: return function(value, index, collection) { + return func.call(thisArg, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(thisArg, accumulator, value, index, collection); + }; + } + return bind(func, thisArg); +} + +module.exports = baseCreateCallback; + +},{"../functions/bind":72,"../support":171,"../utilities/identity":175,"./setBindData":126}],93:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreate = require('./baseCreate'), + isObject = require('../objects/isObject'), + setBindData = require('./setBindData'), + slice = require('./slice'); + +/** + * Used for `Array` method references. + * + * Normally `Array.prototype` would suffice, however, using an array literal + * avoids issues in Narwhal. + */ +var arrayRef = []; + +/** Native method shortcuts */ +var push = arrayRef.push; + +/** + * The base implementation of `createWrapper` that creates the wrapper and + * sets its meta data. + * + * @private + * @param {Array} bindData The bind data array. + * @returns {Function} Returns the new function. + */ +function baseCreateWrapper(bindData) { + var func = bindData[0], + bitmask = bindData[1], + partialArgs = bindData[2], + partialRightArgs = bindData[3], + thisArg = bindData[4], + arity = bindData[5]; + + var isBind = bitmask & 1, + isBindKey = bitmask & 2, + isCurry = bitmask & 4, + isCurryBound = bitmask & 8, + key = func; + + function bound() { + var thisBinding = isBind ? thisArg : this; + if (partialArgs) { + var args = slice(partialArgs); + push.apply(args, arguments); + } + if (partialRightArgs || isCurry) { + args || (args = slice(arguments)); + if (partialRightArgs) { + push.apply(args, partialRightArgs); + } + if (isCurry && args.length < arity) { + bitmask |= 16 & ~32; + return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]); + } + } + args || (args = arguments); + if (isBindKey) { + func = thisBinding[key]; + } + if (this instanceof bound) { + thisBinding = baseCreate(func.prototype); + var result = func.apply(thisBinding, args); + return isObject(result) ? result : thisBinding; + } + return func.apply(thisBinding, args); + } + setBindData(bound, bindData); + return bound; +} + +module.exports = baseCreateWrapper; + +},{"../objects/isObject":158,"./baseCreate":91,"./setBindData":126,"./slice":129}],94:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseIndexOf = require('./baseIndexOf'), + cacheIndexOf = require('./cacheIndexOf'), + createCache = require('./createCache'), + largeArraySize = require('./largeArraySize'), + releaseObject = require('./releaseObject'); + +/** + * The base implementation of `_.difference` that accepts a single array + * of values to exclude. + * + * @private + * @param {Array} array The array to process. + * @param {Array} [values] The array of values to exclude. + * @returns {Array} Returns a new array of filtered values. + */ +function baseDifference(array, values) { + var index = -1, + indexOf = baseIndexOf, + length = array ? array.length : 0, + isLarge = length >= largeArraySize, + result = []; + + if (isLarge) { + var cache = createCache(values); + if (cache) { + indexOf = cacheIndexOf; + values = cache; + } else { + isLarge = false; + } + } + while (++index < length) { + var value = array[index]; + if (indexOf(values, value) < 0) { + result.push(value); + } + } + if (isLarge) { + releaseObject(values); + } + return result; +} + +module.exports = baseDifference; + +},{"./baseIndexOf":96,"./cacheIndexOf":101,"./createCache":106,"./largeArraySize":116,"./releaseObject":125}],95:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isArguments = require('../objects/isArguments'), + isArray = require('../objects/isArray'); + +/** + * The base implementation of `_.flatten` without support for callback + * shorthands or `thisArg` binding. + * + * @private + * @param {Array} array The array to flatten. + * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level. + * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects. + * @param {number} [fromIndex=0] The index to start from. + * @returns {Array} Returns a new flattened array. + */ +function baseFlatten(array, isShallow, isStrict, fromIndex) { + var index = (fromIndex || 0) - 1, + length = array ? array.length : 0, + result = []; + + while (++index < length) { + var value = array[index]; + + if (value && typeof value == 'object' && typeof value.length == 'number' + && (isArray(value) || isArguments(value))) { + // recursively flatten arrays (susceptible to call stack limits) + if (!isShallow) { + value = baseFlatten(value, isShallow, isStrict); + } + var valIndex = -1, + valLength = value.length, + resIndex = result.length; + + result.length += valLength; + while (++valIndex < valLength) { + result[resIndex++] = value[valIndex]; + } + } else if (!isStrict) { + result.push(value); + } + } + return result; +} + +module.exports = baseFlatten; + +},{"../objects/isArguments":146,"../objects/isArray":147}],96:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * The base implementation of `_.indexOf` without support for binary searches + * or `fromIndex` constraints. + * + * @private + * @param {Array} array The array to search. + * @param {*} value The value to search for. + * @param {number} [fromIndex=0] The index to search from. + * @returns {number} Returns the index of the matched value or `-1`. + */ +function baseIndexOf(array, value, fromIndex) { + var index = (fromIndex || 0) - 1, + length = array ? array.length : 0; + + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; +} + +module.exports = baseIndexOf; + +},{}],97:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var forIn = require('../objects/forIn'), + getArray = require('./getArray'), + isFunction = require('../objects/isFunction'), + objectTypes = require('./objectTypes'), + releaseArray = require('./releaseArray'); + +/** `Object#toString` result shortcuts */ +var argsClass = '[object Arguments]', + arrayClass = '[object Array]', + boolClass = '[object Boolean]', + dateClass = '[object Date]', + numberClass = '[object Number]', + objectClass = '[object Object]', + regexpClass = '[object RegExp]', + stringClass = '[object String]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** Native method shortcuts */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * The base implementation of `_.isEqual`, without support for `thisArg` binding, + * that allows partial "_.where" style comparisons. + * + * @private + * @param {*} a The value to compare. + * @param {*} b The other value to compare. + * @param {Function} [callback] The function to customize comparing values. + * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons. + * @param {Array} [stackA=[]] Tracks traversed `a` objects. + * @param {Array} [stackB=[]] Tracks traversed `b` objects. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + */ +function baseIsEqual(a, b, callback, isWhere, stackA, stackB) { + // used to indicate that when comparing objects, `a` has at least the properties of `b` + if (callback) { + var result = callback(a, b); + if (typeof result != 'undefined') { + return !!result; + } + } + // exit early for identical values + if (a === b) { + // treat `+0` vs. `-0` as not equal + return a !== 0 || (1 / a == 1 / b); + } + var type = typeof a, + otherType = typeof b; + + // exit early for unlike primitive values + if (a === a && + !(a && objectTypes[type]) && + !(b && objectTypes[otherType])) { + return false; + } + // exit early for `null` and `undefined` avoiding ES3's Function#call behavior + // http://es5.github.io/#x15.3.4.4 + if (a == null || b == null) { + return a === b; + } + // compare [[Class]] names + var className = toString.call(a), + otherClass = toString.call(b); + + if (className == argsClass) { + className = objectClass; + } + if (otherClass == argsClass) { + otherClass = objectClass; + } + if (className != otherClass) { + return false; + } + switch (className) { + case boolClass: + case dateClass: + // coerce dates and booleans to numbers, dates to milliseconds and booleans + // to `1` or `0` treating invalid dates coerced to `NaN` as not equal + return +a == +b; + + case numberClass: + // treat `NaN` vs. `NaN` as equal + return (a != +a) + ? b != +b + // but treat `+0` vs. `-0` as not equal + : (a == 0 ? (1 / a == 1 / b) : a == +b); + + case regexpClass: + case stringClass: + // coerce regexes to strings (http://es5.github.io/#x15.10.6.4) + // treat string primitives and their corresponding object instances as equal + return a == String(b); + } + var isArr = className == arrayClass; + if (!isArr) { + // unwrap any `lodash` wrapped values + var aWrapped = hasOwnProperty.call(a, '__wrapped__'), + bWrapped = hasOwnProperty.call(b, '__wrapped__'); + + if (aWrapped || bWrapped) { + return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB); + } + // exit for functions and DOM nodes + if (className != objectClass) { + return false; + } + // in older versions of Opera, `arguments` objects have `Array` constructors + var ctorA = a.constructor, + ctorB = b.constructor; + + // non `Object` object instances with different constructors are not equal + if (ctorA != ctorB && + !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) && + ('constructor' in a && 'constructor' in b) + ) { + return false; + } + } + // assume cyclic structures are equal + // the algorithm for detecting cyclic structures is adapted from ES 5.1 + // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3) + var initedStack = !stackA; + stackA || (stackA = getArray()); + stackB || (stackB = getArray()); + + var length = stackA.length; + while (length--) { + if (stackA[length] == a) { + return stackB[length] == b; + } + } + var size = 0; + result = true; + + // add `a` and `b` to the stack of traversed objects + stackA.push(a); + stackB.push(b); + + // recursively compare objects and arrays (susceptible to call stack limits) + if (isArr) { + // compare lengths to determine if a deep comparison is necessary + length = a.length; + size = b.length; + result = size == length; + + if (result || isWhere) { + // deep compare the contents, ignoring non-numeric properties + while (size--) { + var index = length, + value = b[size]; + + if (isWhere) { + while (index--) { + if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) { + break; + } + } + } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) { + break; + } + } + } + } + else { + // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` + // which, in this case, is more costly + forIn(b, function(value, key, b) { + if (hasOwnProperty.call(b, key)) { + // count the number of properties. + size++; + // deep compare each property value. + return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB)); + } + }); + + if (result && !isWhere) { + // ensure both objects have the same number of properties + forIn(a, function(value, key, a) { + if (hasOwnProperty.call(a, key)) { + // `size` will be `-1` if `a` has more properties than `b` + return (result = --size > -1); + } + }); + } + } + stackA.pop(); + stackB.pop(); + + if (initedStack) { + releaseArray(stackA); + releaseArray(stackB); + } + return result; +} + +module.exports = baseIsEqual; + +},{"../objects/forIn":139,"../objects/isFunction":154,"./getArray":110,"./objectTypes":120,"./releaseArray":124}],98:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var forEach = require('../collections/forEach'), + forOwn = require('../objects/forOwn'), + isArray = require('../objects/isArray'), + isPlainObject = require('../objects/isPlainObject'); + +/** + * The base implementation of `_.merge` without argument juggling or support + * for `thisArg` binding. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {Function} [callback] The function to customize merging properties. + * @param {Array} [stackA=[]] Tracks traversed source objects. + * @param {Array} [stackB=[]] Associates values with source counterparts. + */ +function baseMerge(object, source, callback, stackA, stackB) { + (isArray(source) ? forEach : forOwn)(source, function(source, key) { + var found, + isArr, + result = source, + value = object[key]; + + if (source && ((isArr = isArray(source)) || isPlainObject(source))) { + // avoid merging previously merged cyclic sources + var stackLength = stackA.length; + while (stackLength--) { + if ((found = stackA[stackLength] == source)) { + value = stackB[stackLength]; + break; + } + } + if (!found) { + var isShallow; + if (callback) { + result = callback(value, source); + if ((isShallow = typeof result != 'undefined')) { + value = result; + } + } + if (!isShallow) { + value = isArr + ? (isArray(value) ? value : []) + : (isPlainObject(value) ? value : {}); + } + // add `source` and associated `value` to the stack of traversed objects + stackA.push(source); + stackB.push(value); + + // recursively merge objects and arrays (susceptible to call stack limits) + if (!isShallow) { + baseMerge(value, source, callback, stackA, stackB); + } + } + } + else { + if (callback) { + result = callback(value, source); + if (typeof result == 'undefined') { + result = source; + } + } + if (typeof result != 'undefined') { + value = result; + } + } + object[key] = value; + }); +} + +module.exports = baseMerge; + +},{"../collections/forEach":51,"../objects/forOwn":141,"../objects/isArray":147,"../objects/isPlainObject":159}],99:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Native method shortcuts */ +var floor = Math.floor; + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeRandom = Math.random; + +/** + * The base implementation of `_.random` without argument juggling or support + * for returning floating-point numbers. + * + * @private + * @param {number} min The minimum possible value. + * @param {number} max The maximum possible value. + * @returns {number} Returns a random number. + */ +function baseRandom(min, max) { + return min + floor(nativeRandom() * (max - min + 1)); +} + +module.exports = baseRandom; + +},{}],100:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseIndexOf = require('./baseIndexOf'), + cacheIndexOf = require('./cacheIndexOf'), + createCache = require('./createCache'), + getArray = require('./getArray'), + largeArraySize = require('./largeArraySize'), + releaseArray = require('./releaseArray'), + releaseObject = require('./releaseObject'); + +/** + * The base implementation of `_.uniq` without support for callback shorthands + * or `thisArg` binding. + * + * @private + * @param {Array} array The array to process. + * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted. + * @param {Function} [callback] The function called per iteration. + * @returns {Array} Returns a duplicate-value-free array. + */ +function baseUniq(array, isSorted, callback) { + var index = -1, + indexOf = baseIndexOf, + length = array ? array.length : 0, + result = []; + + var isLarge = !isSorted && length >= largeArraySize, + seen = (callback || isLarge) ? getArray() : result; + + if (isLarge) { + var cache = createCache(seen); + indexOf = cacheIndexOf; + seen = cache; + } + while (++index < length) { + var value = array[index], + computed = callback ? callback(value, index, array) : value; + + if (isSorted + ? !index || seen[seen.length - 1] !== computed + : indexOf(seen, computed) < 0 + ) { + if (callback || isLarge) { + seen.push(computed); + } + result.push(value); + } + } + if (isLarge) { + releaseArray(seen.array); + releaseObject(seen); + } else if (callback) { + releaseArray(seen); + } + return result; +} + +module.exports = baseUniq; + +},{"./baseIndexOf":96,"./cacheIndexOf":101,"./createCache":106,"./getArray":110,"./largeArraySize":116,"./releaseArray":124,"./releaseObject":125}],101:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseIndexOf = require('./baseIndexOf'), + keyPrefix = require('./keyPrefix'); + +/** + * An implementation of `_.contains` for cache objects that mimics the return + * signature of `_.indexOf` by returning `0` if the value is found, else `-1`. + * + * @private + * @param {Object} cache The cache object to inspect. + * @param {*} value The value to search for. + * @returns {number} Returns `0` if `value` is found, else `-1`. + */ +function cacheIndexOf(cache, value) { + var type = typeof value; + cache = cache.cache; + + if (type == 'boolean' || value == null) { + return cache[value] ? 0 : -1; + } + if (type != 'number' && type != 'string') { + type = 'object'; + } + var key = type == 'number' ? value : keyPrefix + value; + cache = (cache = cache[type]) && cache[key]; + + return type == 'object' + ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1) + : (cache ? 0 : -1); +} + +module.exports = cacheIndexOf; + +},{"./baseIndexOf":96,"./keyPrefix":115}],102:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var keyPrefix = require('./keyPrefix'); + +/** + * Adds a given value to the corresponding cache object. + * + * @private + * @param {*} value The value to add to the cache. + */ +function cachePush(value) { + var cache = this.cache, + type = typeof value; + + if (type == 'boolean' || value == null) { + cache[value] = true; + } else { + if (type != 'number' && type != 'string') { + type = 'object'; + } + var key = type == 'number' ? value : keyPrefix + value, + typeCache = cache[type] || (cache[type] = {}); + + if (type == 'object') { + (typeCache[key] || (typeCache[key] = [])).push(value); + } else { + typeCache[key] = true; + } + } +} + +module.exports = cachePush; + +},{"./keyPrefix":115}],103:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Used by `_.max` and `_.min` as the default callback when a given + * collection is a string value. + * + * @private + * @param {string} value The character to inspect. + * @returns {number} Returns the code unit of given character. + */ +function charAtCallback(value) { + return value.charCodeAt(0); +} + +module.exports = charAtCallback; + +},{}],104:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Used by `sortBy` to compare transformed `collection` elements, stable sorting + * them in ascending order. + * + * @private + * @param {Object} a The object to compare to `b`. + * @param {Object} b The object to compare to `a`. + * @returns {number} Returns the sort order indicator of `1` or `-1`. + */ +function compareAscending(a, b) { + var ac = a.criteria, + bc = b.criteria, + index = -1, + length = ac.length; + + while (++index < length) { + var value = ac[index], + other = bc[index]; + + if (value !== other) { + if (value > other || typeof value == 'undefined') { + return 1; + } + if (value < other || typeof other == 'undefined') { + return -1; + } + } + } + // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications + // that causes it, under certain circumstances, to return the same value for + // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247 + // + // This also ensures a stable sort in V8 and other engines. + // See http://code.google.com/p/v8/issues/detail?id=90 + return a.index - b.index; +} + +module.exports = compareAscending; + +},{}],105:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forOwn = require('../objects/forOwn'), + isArray = require('../objects/isArray'); + +/** + * Creates a function that aggregates a collection, creating an object composed + * of keys generated from the results of running each element of the collection + * through a callback. The given `setter` function sets the keys and values + * of the composed object. + * + * @private + * @param {Function} setter The setter function. + * @returns {Function} Returns the new aggregator function. + */ +function createAggregator(setter) { + return function(collection, callback, thisArg) { + var result = {}; + callback = createCallback(callback, thisArg, 3); + + var index = -1, + length = collection ? collection.length : 0; + + if (typeof length == 'number') { + while (++index < length) { + var value = collection[index]; + setter(result, value, callback(value, index, collection), collection); + } + } else { + forOwn(collection, function(value, key, collection) { + setter(result, value, callback(value, key, collection), collection); + }); + } + return result; + }; +} + +module.exports = createAggregator; + +},{"../functions/createCallback":76,"../objects/forOwn":141,"../objects/isArray":147}],106:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var cachePush = require('./cachePush'), + getObject = require('./getObject'), + releaseObject = require('./releaseObject'); + +/** + * Creates a cache object to optimize linear searches of large arrays. + * + * @private + * @param {Array} [array=[]] The array to search. + * @returns {null|Object} Returns the cache object or `null` if caching should not be used. + */ +function createCache(array) { + var index = -1, + length = array.length, + first = array[0], + mid = array[(length / 2) | 0], + last = array[length - 1]; + + if (first && typeof first == 'object' && + mid && typeof mid == 'object' && last && typeof last == 'object') { + return false; + } + var cache = getObject(); + cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false; + + var result = getObject(); + result.array = array; + result.cache = cache; + result.push = cachePush; + + while (++index < length) { + result.push(array[index]); + } + return result; +} + +module.exports = createCache; + +},{"./cachePush":102,"./getObject":111,"./releaseObject":125}],107:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseBind = require('./baseBind'), + baseCreateWrapper = require('./baseCreateWrapper'), + isFunction = require('../objects/isFunction'), + slice = require('./slice'); + +/** + * Used for `Array` method references. + * + * Normally `Array.prototype` would suffice, however, using an array literal + * avoids issues in Narwhal. + */ +var arrayRef = []; + +/** Native method shortcuts */ +var push = arrayRef.push, + unshift = arrayRef.unshift; + +/** + * Creates a function that, when called, either curries or invokes `func` + * with an optional `this` binding and partially applied arguments. + * + * @private + * @param {Function|string} func The function or method name to reference. + * @param {number} bitmask The bitmask of method flags to compose. + * The bitmask may be composed of the following flags: + * 1 - `_.bind` + * 2 - `_.bindKey` + * 4 - `_.curry` + * 8 - `_.curry` (bound) + * 16 - `_.partial` + * 32 - `_.partialRight` + * @param {Array} [partialArgs] An array of arguments to prepend to those + * provided to the new function. + * @param {Array} [partialRightArgs] An array of arguments to append to those + * provided to the new function. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new function. + */ +function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) { + var isBind = bitmask & 1, + isBindKey = bitmask & 2, + isCurry = bitmask & 4, + isCurryBound = bitmask & 8, + isPartial = bitmask & 16, + isPartialRight = bitmask & 32; + + if (!isBindKey && !isFunction(func)) { + throw new TypeError; + } + if (isPartial && !partialArgs.length) { + bitmask &= ~16; + isPartial = partialArgs = false; + } + if (isPartialRight && !partialRightArgs.length) { + bitmask &= ~32; + isPartialRight = partialRightArgs = false; + } + var bindData = func && func.__bindData__; + if (bindData && bindData !== true) { + // clone `bindData` + bindData = slice(bindData); + if (bindData[2]) { + bindData[2] = slice(bindData[2]); + } + if (bindData[3]) { + bindData[3] = slice(bindData[3]); + } + // set `thisBinding` is not previously bound + if (isBind && !(bindData[1] & 1)) { + bindData[4] = thisArg; + } + // set if previously bound but not currently (subsequent curried functions) + if (!isBind && bindData[1] & 1) { + bitmask |= 8; + } + // set curried arity if not yet set + if (isCurry && !(bindData[1] & 4)) { + bindData[5] = arity; + } + // append partial left arguments + if (isPartial) { + push.apply(bindData[2] || (bindData[2] = []), partialArgs); + } + // append partial right arguments + if (isPartialRight) { + unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs); + } + // merge flags + bindData[1] |= bitmask; + return createWrapper.apply(null, bindData); + } + // fast path for `_.bind` + var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper; + return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]); +} + +module.exports = createWrapper; + +},{"../objects/isFunction":154,"./baseBind":89,"./baseCreateWrapper":93,"./slice":129}],108:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var htmlEscapes = require('./htmlEscapes'); + +/** + * Used by `escape` to convert characters to HTML entities. + * + * @private + * @param {string} match The matched character to escape. + * @returns {string} Returns the escaped character. + */ +function escapeHtmlChar(match) { + return htmlEscapes[match]; +} + +module.exports = escapeHtmlChar; + +},{"./htmlEscapes":112}],109:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used to escape characters for inclusion in compiled string literals */ +var stringEscapes = { + '\\': '\\', + "'": "'", + '\n': 'n', + '\r': 'r', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' +}; + +/** + * Used by `template` to escape characters for inclusion in compiled + * string literals. + * + * @private + * @param {string} match The matched character to escape. + * @returns {string} Returns the escaped character. + */ +function escapeStringChar(match) { + return '\\' + stringEscapes[match]; +} + +module.exports = escapeStringChar; + +},{}],110:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var arrayPool = require('./arrayPool'); + +/** + * Gets an array from the array pool or creates a new one if the pool is empty. + * + * @private + * @returns {Array} The array from the pool. + */ +function getArray() { + return arrayPool.pop() || []; +} + +module.exports = getArray; + +},{"./arrayPool":88}],111:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var objectPool = require('./objectPool'); + +/** + * Gets an object from the object pool or creates a new one if the pool is empty. + * + * @private + * @returns {Object} The object from the pool. + */ +function getObject() { + return objectPool.pop() || { + 'array': null, + 'cache': null, + 'criteria': null, + 'false': false, + 'index': 0, + 'null': false, + 'number': null, + 'object': null, + 'push': null, + 'string': null, + 'true': false, + 'undefined': false, + 'value': null + }; +} + +module.exports = getObject; + +},{"./objectPool":119}],112:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Used to convert characters to HTML entities: + * + * Though the `>` character is escaped for symmetry, characters like `>` and `/` + * don't require escaping in HTML and have no special meaning unless they're part + * of a tag or an unquoted attribute value. + * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact") + */ +var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' +}; + +module.exports = htmlEscapes; + +},{}],113:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var htmlEscapes = require('./htmlEscapes'), + invert = require('../objects/invert'); + +/** Used to convert HTML entities to characters */ +var htmlUnescapes = invert(htmlEscapes); + +module.exports = htmlUnescapes; + +},{"../objects/invert":145,"./htmlEscapes":112}],114:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** Used to detect if a method is native */ +var reNative = RegExp('^' + + String(toString) + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/toString| for [^\]]+/g, '.*?') + '$' +); + +/** + * Checks if `value` is a native function. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a native function, else `false`. + */ +function isNative(value) { + return typeof value == 'function' && reNative.test(value); +} + +module.exports = isNative; + +},{}],115:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */ +var keyPrefix = +new Date + ''; + +module.exports = keyPrefix; + +},{}],116:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used as the size when optimizations are enabled for large arrays */ +var largeArraySize = 75; + +module.exports = largeArraySize; + +},{}],117:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * A fast path for creating `lodash` wrapper objects. + * + * @private + * @param {*} value The value to wrap in a `lodash` instance. + * @param {boolean} chainAll A flag to enable chaining for all methods + * @returns {Object} Returns a `lodash` instance. + */ +function lodashWrapper(value, chainAll) { + this.__chain__ = !!chainAll; + this.__wrapped__ = value; +} + +module.exports = lodashWrapper; + +},{}],118:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used as the max size of the `arrayPool` and `objectPool` */ +var maxPoolSize = 40; + +module.exports = maxPoolSize; + +},{}],119:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used to pool arrays and objects used internally */ +var objectPool = []; + +module.exports = objectPool; + +},{}],120:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used to determine if values are of the language type Object */ +var objectTypes = { + 'boolean': false, + 'function': true, + 'object': true, + 'number': false, + 'string': false, + 'undefined': false +}; + +module.exports = objectTypes; + +},{}],121:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var htmlUnescapes = require('./htmlUnescapes'), + keys = require('../objects/keys'); + +/** Used to match HTML entities and HTML characters */ +var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'); + +module.exports = reEscapedHtml; + +},{"../objects/keys":163,"./htmlUnescapes":113}],122:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used to match "interpolate" template delimiters */ +var reInterpolate = /<%=([\s\S]+?)%>/g; + +module.exports = reInterpolate; + +},{}],123:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var htmlEscapes = require('./htmlEscapes'), + keys = require('../objects/keys'); + +/** Used to match HTML entities and HTML characters */ +var reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g'); + +module.exports = reUnescapedHtml; + +},{"../objects/keys":163,"./htmlEscapes":112}],124:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var arrayPool = require('./arrayPool'), + maxPoolSize = require('./maxPoolSize'); + +/** + * Releases the given array back to the array pool. + * + * @private + * @param {Array} [array] The array to release. + */ +function releaseArray(array) { + array.length = 0; + if (arrayPool.length < maxPoolSize) { + arrayPool.push(array); + } +} + +module.exports = releaseArray; + +},{"./arrayPool":88,"./maxPoolSize":118}],125:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var maxPoolSize = require('./maxPoolSize'), + objectPool = require('./objectPool'); + +/** + * Releases the given object back to the object pool. + * + * @private + * @param {Object} [object] The object to release. + */ +function releaseObject(object) { + var cache = object.cache; + if (cache) { + releaseObject(cache); + } + object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null; + if (objectPool.length < maxPoolSize) { + objectPool.push(object); + } +} + +module.exports = releaseObject; + +},{"./maxPoolSize":118,"./objectPool":119}],126:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isNative = require('./isNative'), + noop = require('../utilities/noop'); + +/** Used as the property descriptor for `__bindData__` */ +var descriptor = { + 'configurable': false, + 'enumerable': false, + 'value': null, + 'writable': false +}; + +/** Used to set meta data on functions */ +var defineProperty = (function() { + // IE 8 only accepts DOM elements + try { + var o = {}, + func = isNative(func = Object.defineProperty) && func, + result = func(o, o, o) && func; + } catch(e) { } + return result; +}()); + +/** + * Sets `this` binding data on a given function. + * + * @private + * @param {Function} func The function to set data on. + * @param {Array} value The data array to set. + */ +var setBindData = !defineProperty ? noop : function(func, value) { + descriptor.value = value; + defineProperty(func, '__bindData__', descriptor); +}; + +module.exports = setBindData; + +},{"../utilities/noop":178,"./isNative":114}],127:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var forIn = require('../objects/forIn'), + isFunction = require('../objects/isFunction'); + +/** `Object#toString` result shortcuts */ +var objectClass = '[object Object]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** Native method shortcuts */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * A fallback implementation of `isPlainObject` which checks if a given value + * is an object created by the `Object` constructor, assuming objects created + * by the `Object` constructor have no inherited enumerable properties and that + * there are no `Object.prototype` extensions. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + */ +function shimIsPlainObject(value) { + var ctor, + result; + + // avoid non Object objects, `arguments` objects, and DOM elements + if (!(value && toString.call(value) == objectClass) || + (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor))) { + return false; + } + // In most environments an object's own properties are iterated before + // its inherited properties. If the last iterated property is an object's + // own property then there are no inherited enumerable properties. + forIn(value, function(value, key) { + result = key; + }); + return typeof result == 'undefined' || hasOwnProperty.call(value, result); +} + +module.exports = shimIsPlainObject; + +},{"../objects/forIn":139,"../objects/isFunction":154}],128:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var objectTypes = require('./objectTypes'); + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Native method shortcuts */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * A fallback implementation of `Object.keys` which produces an array of the + * given object's own enumerable property names. + * + * @private + * @type Function + * @param {Object} object The object to inspect. + * @returns {Array} Returns an array of property names. + */ +var shimKeys = function(object) { + var index, iterable = object, result = []; + if (!iterable) return result; + if (!(objectTypes[typeof object])) return result; + for (index in iterable) { + if (hasOwnProperty.call(iterable, index)) { + result.push(index); + } + } + return result +}; + +module.exports = shimKeys; + +},{"./objectTypes":120}],129:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Slices the `collection` from the `start` index up to, but not including, + * the `end` index. + * + * Note: This function is used instead of `Array#slice` to support node lists + * in IE < 9 and to ensure dense arrays are returned. + * + * @private + * @param {Array|Object|string} collection The collection to slice. + * @param {number} start The start index. + * @param {number} end The end index. + * @returns {Array} Returns the new array. + */ +function slice(array, start, end) { + start || (start = 0); + if (typeof end == 'undefined') { + end = array ? array.length : 0; + } + var index = -1, + length = end - start || 0, + result = Array(length < 0 ? 0 : length); + + while (++index < length) { + result[index] = array[start + index]; + } + return result; +} + +module.exports = slice; + +},{}],130:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var htmlUnescapes = require('./htmlUnescapes'); + +/** + * Used by `unescape` to convert HTML entities to characters. + * + * @private + * @param {string} match The matched character to unescape. + * @returns {string} Returns the unescaped character. + */ +function unescapeHtmlChar(match) { + return htmlUnescapes[match]; +} + +module.exports = unescapeHtmlChar; + +},{"./htmlUnescapes":113}],131:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +module.exports = { + 'assign': require('./objects/assign'), + 'clone': require('./objects/clone'), + 'cloneDeep': require('./objects/cloneDeep'), + 'create': require('./objects/create'), + 'defaults': require('./objects/defaults'), + 'extend': require('./objects/assign'), + 'findKey': require('./objects/findKey'), + 'findLastKey': require('./objects/findLastKey'), + 'forIn': require('./objects/forIn'), + 'forInRight': require('./objects/forInRight'), + 'forOwn': require('./objects/forOwn'), + 'forOwnRight': require('./objects/forOwnRight'), + 'functions': require('./objects/functions'), + 'has': require('./objects/has'), + 'invert': require('./objects/invert'), + 'isArguments': require('./objects/isArguments'), + 'isArray': require('./objects/isArray'), + 'isBoolean': require('./objects/isBoolean'), + 'isDate': require('./objects/isDate'), + 'isElement': require('./objects/isElement'), + 'isEmpty': require('./objects/isEmpty'), + 'isEqual': require('./objects/isEqual'), + 'isFinite': require('./objects/isFinite'), + 'isFunction': require('./objects/isFunction'), + 'isNaN': require('./objects/isNaN'), + 'isNull': require('./objects/isNull'), + 'isNumber': require('./objects/isNumber'), + 'isObject': require('./objects/isObject'), + 'isPlainObject': require('./objects/isPlainObject'), + 'isRegExp': require('./objects/isRegExp'), + 'isString': require('./objects/isString'), + 'isUndefined': require('./objects/isUndefined'), + 'keys': require('./objects/keys'), + 'mapValues': require('./objects/mapValues'), + 'merge': require('./objects/merge'), + 'methods': require('./objects/functions'), + 'omit': require('./objects/omit'), + 'pairs': require('./objects/pairs'), + 'pick': require('./objects/pick'), + 'transform': require('./objects/transform'), + 'values': require('./objects/values') +}; + +},{"./objects/assign":132,"./objects/clone":133,"./objects/cloneDeep":134,"./objects/create":135,"./objects/defaults":136,"./objects/findKey":137,"./objects/findLastKey":138,"./objects/forIn":139,"./objects/forInRight":140,"./objects/forOwn":141,"./objects/forOwnRight":142,"./objects/functions":143,"./objects/has":144,"./objects/invert":145,"./objects/isArguments":146,"./objects/isArray":147,"./objects/isBoolean":148,"./objects/isDate":149,"./objects/isElement":150,"./objects/isEmpty":151,"./objects/isEqual":152,"./objects/isFinite":153,"./objects/isFunction":154,"./objects/isNaN":155,"./objects/isNull":156,"./objects/isNumber":157,"./objects/isObject":158,"./objects/isPlainObject":159,"./objects/isRegExp":160,"./objects/isString":161,"./objects/isUndefined":162,"./objects/keys":163,"./objects/mapValues":164,"./objects/merge":165,"./objects/omit":166,"./objects/pairs":167,"./objects/pick":168,"./objects/transform":169,"./objects/values":170}],132:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreateCallback = require('../internals/baseCreateCallback'), + keys = require('./keys'), + objectTypes = require('../internals/objectTypes'); + +/** + * Assigns own enumerable properties of source object(s) to the destination + * object. Subsequent sources will overwrite property assignments of previous + * sources. If a callback is provided it will be executed to produce the + * assigned values. The callback is bound to `thisArg` and invoked with two + * arguments; (objectValue, sourceValue). + * + * @static + * @memberOf _ + * @type Function + * @alias extend + * @category Objects + * @param {Object} object The destination object. + * @param {...Object} [source] The source objects. + * @param {Function} [callback] The function to customize assigning values. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the destination object. + * @example + * + * _.assign({ 'name': 'fred' }, { 'employer': 'slate' }); + * // => { 'name': 'fred', 'employer': 'slate' } + * + * var defaults = _.partialRight(_.assign, function(a, b) { + * return typeof a == 'undefined' ? b : a; + * }); + * + * var object = { 'name': 'barney' }; + * defaults(object, { 'name': 'fred', 'employer': 'slate' }); + * // => { 'name': 'barney', 'employer': 'slate' } + */ +var assign = function(object, source, guard) { + var index, iterable = object, result = iterable; + if (!iterable) return result; + var args = arguments, + argsIndex = 0, + argsLength = typeof guard == 'number' ? 2 : args.length; + if (argsLength > 3 && typeof args[argsLength - 2] == 'function') { + var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2); + } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') { + callback = args[--argsLength]; + } + while (++argsIndex < argsLength) { + iterable = args[argsIndex]; + if (iterable && objectTypes[typeof iterable]) { + var ownIndex = -1, + ownProps = objectTypes[typeof iterable] && keys(iterable), + length = ownProps ? ownProps.length : 0; + + while (++ownIndex < length) { + index = ownProps[ownIndex]; + result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]; + } + } + } + return result +}; + +module.exports = assign; + +},{"../internals/baseCreateCallback":92,"../internals/objectTypes":120,"./keys":163}],133:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseClone = require('../internals/baseClone'), + baseCreateCallback = require('../internals/baseCreateCallback'); + +/** + * Creates a clone of `value`. If `isDeep` is `true` nested objects will also + * be cloned, otherwise they will be assigned by reference. If a callback + * is provided it will be executed to produce the cloned values. If the + * callback returns `undefined` cloning will be handled by the method instead. + * The callback is bound to `thisArg` and invoked with one argument; (value). + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to clone. + * @param {boolean} [isDeep=false] Specify a deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the cloned value. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * var shallow = _.clone(characters); + * shallow[0] === characters[0]; + * // => true + * + * var deep = _.clone(characters, true); + * deep[0] === characters[0]; + * // => false + * + * _.mixin({ + * 'clone': _.partialRight(_.clone, function(value) { + * return _.isElement(value) ? value.cloneNode(false) : undefined; + * }) + * }); + * + * var clone = _.clone(document.body); + * clone.childNodes.length; + * // => 0 + */ +function clone(value, isDeep, callback, thisArg) { + // allows working with "Collections" methods without using their `index` + // and `collection` arguments for `isDeep` and `callback` + if (typeof isDeep != 'boolean' && isDeep != null) { + thisArg = callback; + callback = isDeep; + isDeep = false; + } + return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1)); +} + +module.exports = clone; + +},{"../internals/baseClone":90,"../internals/baseCreateCallback":92}],134:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseClone = require('../internals/baseClone'), + baseCreateCallback = require('../internals/baseCreateCallback'); + +/** + * Creates a deep clone of `value`. If a callback is provided it will be + * executed to produce the cloned values. If the callback returns `undefined` + * cloning will be handled by the method instead. The callback is bound to + * `thisArg` and invoked with one argument; (value). + * + * Note: This method is loosely based on the structured clone algorithm. Functions + * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and + * objects created by constructors other than `Object` are cloned to plain `Object` objects. + * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the deep cloned value. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * var deep = _.cloneDeep(characters); + * deep[0] === characters[0]; + * // => false + * + * var view = { + * 'label': 'docs', + * 'node': element + * }; + * + * var clone = _.cloneDeep(view, function(value) { + * return _.isElement(value) ? value.cloneNode(true) : undefined; + * }); + * + * clone.node == view.node; + * // => false + */ +function cloneDeep(value, callback, thisArg) { + return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1)); +} + +module.exports = cloneDeep; + +},{"../internals/baseClone":90,"../internals/baseCreateCallback":92}],135:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var assign = require('./assign'), + baseCreate = require('../internals/baseCreate'); + +/** + * Creates an object that inherits from the given `prototype` object. If a + * `properties` object is provided its own enumerable properties are assigned + * to the created object. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} prototype The object to inherit from. + * @param {Object} [properties] The properties to assign to the object. + * @returns {Object} Returns the new object. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * function Circle() { + * Shape.call(this); + * } + * + * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle }); + * + * var circle = new Circle; + * circle instanceof Circle; + * // => true + * + * circle instanceof Shape; + * // => true + */ +function create(prototype, properties) { + var result = baseCreate(prototype); + return properties ? assign(result, properties) : result; +} + +module.exports = create; + +},{"../internals/baseCreate":91,"./assign":132}],136:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var keys = require('./keys'), + objectTypes = require('../internals/objectTypes'); + +/** + * Assigns own enumerable properties of source object(s) to the destination + * object for all destination properties that resolve to `undefined`. Once a + * property is set, additional defaults of the same property will be ignored. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The destination object. + * @param {...Object} [source] The source objects. + * @param- {Object} [guard] Allows working with `_.reduce` without using its + * `key` and `object` arguments as sources. + * @returns {Object} Returns the destination object. + * @example + * + * var object = { 'name': 'barney' }; + * _.defaults(object, { 'name': 'fred', 'employer': 'slate' }); + * // => { 'name': 'barney', 'employer': 'slate' } + */ +var defaults = function(object, source, guard) { + var index, iterable = object, result = iterable; + if (!iterable) return result; + var args = arguments, + argsIndex = 0, + argsLength = typeof guard == 'number' ? 2 : args.length; + while (++argsIndex < argsLength) { + iterable = args[argsIndex]; + if (iterable && objectTypes[typeof iterable]) { + var ownIndex = -1, + ownProps = objectTypes[typeof iterable] && keys(iterable), + length = ownProps ? ownProps.length : 0; + + while (++ownIndex < length) { + index = ownProps[ownIndex]; + if (typeof result[index] == 'undefined') result[index] = iterable[index]; + } + } + } + return result +}; + +module.exports = defaults; + +},{"../internals/objectTypes":120,"./keys":163}],137:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forOwn = require('./forOwn'); + +/** + * This method is like `_.findIndex` except that it returns the key of the + * first element that passes the callback check, instead of the element itself. + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to search. + * @param {Function|Object|string} [callback=identity] The function called per + * iteration. If a property name or object is provided it will be used to + * create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {string|undefined} Returns the key of the found element, else `undefined`. + * @example + * + * var characters = { + * 'barney': { 'age': 36, 'blocked': false }, + * 'fred': { 'age': 40, 'blocked': true }, + * 'pebbles': { 'age': 1, 'blocked': false } + * }; + * + * _.findKey(characters, function(chr) { + * return chr.age < 40; + * }); + * // => 'barney' (property order is not guaranteed across environments) + * + * // using "_.where" callback shorthand + * _.findKey(characters, { 'age': 1 }); + * // => 'pebbles' + * + * // using "_.pluck" callback shorthand + * _.findKey(characters, 'blocked'); + * // => 'fred' + */ +function findKey(object, callback, thisArg) { + var result; + callback = createCallback(callback, thisArg, 3); + forOwn(object, function(value, key, object) { + if (callback(value, key, object)) { + result = key; + return false; + } + }); + return result; +} + +module.exports = findKey; + +},{"../functions/createCallback":76,"./forOwn":141}],138:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forOwnRight = require('./forOwnRight'); + +/** + * This method is like `_.findKey` except that it iterates over elements + * of a `collection` in the opposite order. + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to search. + * @param {Function|Object|string} [callback=identity] The function called per + * iteration. If a property name or object is provided it will be used to + * create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {string|undefined} Returns the key of the found element, else `undefined`. + * @example + * + * var characters = { + * 'barney': { 'age': 36, 'blocked': true }, + * 'fred': { 'age': 40, 'blocked': false }, + * 'pebbles': { 'age': 1, 'blocked': true } + * }; + * + * _.findLastKey(characters, function(chr) { + * return chr.age < 40; + * }); + * // => returns `pebbles`, assuming `_.findKey` returns `barney` + * + * // using "_.where" callback shorthand + * _.findLastKey(characters, { 'age': 40 }); + * // => 'fred' + * + * // using "_.pluck" callback shorthand + * _.findLastKey(characters, 'blocked'); + * // => 'pebbles' + */ +function findLastKey(object, callback, thisArg) { + var result; + callback = createCallback(callback, thisArg, 3); + forOwnRight(object, function(value, key, object) { + if (callback(value, key, object)) { + result = key; + return false; + } + }); + return result; +} + +module.exports = findLastKey; + +},{"../functions/createCallback":76,"./forOwnRight":142}],139:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreateCallback = require('../internals/baseCreateCallback'), + objectTypes = require('../internals/objectTypes'); + +/** + * Iterates over own and inherited enumerable properties of an object, + * executing the callback for each property. The callback is bound to `thisArg` + * and invoked with three arguments; (value, key, object). Callbacks may exit + * iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * Shape.prototype.move = function(x, y) { + * this.x += x; + * this.y += y; + * }; + * + * _.forIn(new Shape, function(value, key) { + * console.log(key); + * }); + * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments) + */ +var forIn = function(collection, callback, thisArg) { + var index, iterable = collection, result = iterable; + if (!iterable) return result; + if (!objectTypes[typeof iterable]) return result; + callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3); + for (index in iterable) { + if (callback(iterable[index], index, collection) === false) return result; + } + return result +}; + +module.exports = forIn; + +},{"../internals/baseCreateCallback":92,"../internals/objectTypes":120}],140:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreateCallback = require('../internals/baseCreateCallback'), + forIn = require('./forIn'); + +/** + * This method is like `_.forIn` except that it iterates over elements + * of a `collection` in the opposite order. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * Shape.prototype.move = function(x, y) { + * this.x += x; + * this.y += y; + * }; + * + * _.forInRight(new Shape, function(value, key) { + * console.log(key); + * }); + * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move' + */ +function forInRight(object, callback, thisArg) { + var pairs = []; + + forIn(object, function(value, key) { + pairs.push(key, value); + }); + + var length = pairs.length; + callback = baseCreateCallback(callback, thisArg, 3); + while (length--) { + if (callback(pairs[length--], pairs[length], object) === false) { + break; + } + } + return object; +} + +module.exports = forInRight; + +},{"../internals/baseCreateCallback":92,"./forIn":139}],141:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreateCallback = require('../internals/baseCreateCallback'), + keys = require('./keys'), + objectTypes = require('../internals/objectTypes'); + +/** + * Iterates over own enumerable properties of an object, executing the callback + * for each property. The callback is bound to `thisArg` and invoked with three + * arguments; (value, key, object). Callbacks may exit iteration early by + * explicitly returning `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. + * @example + * + * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { + * console.log(key); + * }); + * // => logs '0', '1', and 'length' (property order is not guaranteed across environments) + */ +var forOwn = function(collection, callback, thisArg) { + var index, iterable = collection, result = iterable; + if (!iterable) return result; + if (!objectTypes[typeof iterable]) return result; + callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3); + var ownIndex = -1, + ownProps = objectTypes[typeof iterable] && keys(iterable), + length = ownProps ? ownProps.length : 0; + + while (++ownIndex < length) { + index = ownProps[ownIndex]; + if (callback(iterable[index], index, collection) === false) return result; + } + return result +}; + +module.exports = forOwn; + +},{"../internals/baseCreateCallback":92,"../internals/objectTypes":120,"./keys":163}],142:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreateCallback = require('../internals/baseCreateCallback'), + keys = require('./keys'); + +/** + * This method is like `_.forOwn` except that it iterates over elements + * of a `collection` in the opposite order. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. + * @example + * + * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { + * console.log(key); + * }); + * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length' + */ +function forOwnRight(object, callback, thisArg) { + var props = keys(object), + length = props.length; + + callback = baseCreateCallback(callback, thisArg, 3); + while (length--) { + var key = props[length]; + if (callback(object[key], key, object) === false) { + break; + } + } + return object; +} + +module.exports = forOwnRight; + +},{"../internals/baseCreateCallback":92,"./keys":163}],143:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var forIn = require('./forIn'), + isFunction = require('./isFunction'); + +/** + * Creates a sorted array of property names of all enumerable properties, + * own and inherited, of `object` that have function values. + * + * @static + * @memberOf _ + * @alias methods + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns an array of property names that have function values. + * @example + * + * _.functions(_); + * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] + */ +function functions(object) { + var result = []; + forIn(object, function(value, key) { + if (isFunction(value)) { + result.push(key); + } + }); + return result.sort(); +} + +module.exports = functions; + +},{"./forIn":139,"./isFunction":154}],144:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Native method shortcuts */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Checks if the specified property name exists as a direct property of `object`, + * instead of an inherited property. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @param {string} key The name of the property to check. + * @returns {boolean} Returns `true` if key is a direct property, else `false`. + * @example + * + * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); + * // => true + */ +function has(object, key) { + return object ? hasOwnProperty.call(object, key) : false; +} + +module.exports = has; + +},{}],145:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var keys = require('./keys'); + +/** + * Creates an object composed of the inverted keys and values of the given object. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to invert. + * @returns {Object} Returns the created inverted object. + * @example + * + * _.invert({ 'first': 'fred', 'second': 'barney' }); + * // => { 'fred': 'first', 'barney': 'second' } + */ +function invert(object) { + var index = -1, + props = keys(object), + length = props.length, + result = {}; + + while (++index < length) { + var key = props[index]; + result[object[key]] = key; + } + return result; +} + +module.exports = invert; + +},{"./keys":163}],146:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** `Object#toString` result shortcuts */ +var argsClass = '[object Arguments]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** + * Checks if `value` is an `arguments` object. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`. + * @example + * + * (function() { return _.isArguments(arguments); })(1, 2, 3); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ +function isArguments(value) { + return value && typeof value == 'object' && typeof value.length == 'number' && + toString.call(value) == argsClass || false; +} + +module.exports = isArguments; + +},{}],147:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isNative = require('../internals/isNative'); + +/** `Object#toString` result shortcuts */ +var arrayClass = '[object Array]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray; + +/** + * Checks if `value` is an array. + * + * @static + * @memberOf _ + * @type Function + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is an array, else `false`. + * @example + * + * (function() { return _.isArray(arguments); })(); + * // => false + * + * _.isArray([1, 2, 3]); + * // => true + */ +var isArray = nativeIsArray || function(value) { + return value && typeof value == 'object' && typeof value.length == 'number' && + toString.call(value) == arrayClass || false; +}; + +module.exports = isArray; + +},{"../internals/isNative":114}],148:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** `Object#toString` result shortcuts */ +var boolClass = '[object Boolean]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** + * Checks if `value` is a boolean value. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`. + * @example + * + * _.isBoolean(null); + * // => false + */ +function isBoolean(value) { + return value === true || value === false || + value && typeof value == 'object' && toString.call(value) == boolClass || false; +} + +module.exports = isBoolean; + +},{}],149:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** `Object#toString` result shortcuts */ +var dateClass = '[object Date]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** + * Checks if `value` is a date. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a date, else `false`. + * @example + * + * _.isDate(new Date); + * // => true + */ +function isDate(value) { + return value && typeof value == 'object' && toString.call(value) == dateClass || false; +} + +module.exports = isDate; + +},{}],150:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Checks if `value` is a DOM element. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`. + * @example + * + * _.isElement(document.body); + * // => true + */ +function isElement(value) { + return value && value.nodeType === 1 || false; +} + +module.exports = isElement; + +},{}],151:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var forOwn = require('./forOwn'), + isFunction = require('./isFunction'); + +/** `Object#toString` result shortcuts */ +var argsClass = '[object Arguments]', + arrayClass = '[object Array]', + objectClass = '[object Object]', + stringClass = '[object String]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** + * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a + * length of `0` and objects with no own enumerable properties are considered + * "empty". + * + * @static + * @memberOf _ + * @category Objects + * @param {Array|Object|string} value The value to inspect. + * @returns {boolean} Returns `true` if the `value` is empty, else `false`. + * @example + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({}); + * // => true + * + * _.isEmpty(''); + * // => true + */ +function isEmpty(value) { + var result = true; + if (!value) { + return result; + } + var className = toString.call(value), + length = value.length; + + if ((className == arrayClass || className == stringClass || className == argsClass ) || + (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { + return !length; + } + forOwn(value, function() { + return (result = false); + }); + return result; +} + +module.exports = isEmpty; + +},{"./forOwn":141,"./isFunction":154}],152:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreateCallback = require('../internals/baseCreateCallback'), + baseIsEqual = require('../internals/baseIsEqual'); + +/** + * Performs a deep comparison between two values to determine if they are + * equivalent to each other. If a callback is provided it will be executed + * to compare values. If the callback returns `undefined` comparisons will + * be handled by the method instead. The callback is bound to `thisArg` and + * invoked with two arguments; (a, b). + * + * @static + * @memberOf _ + * @category Objects + * @param {*} a The value to compare. + * @param {*} b The other value to compare. + * @param {Function} [callback] The function to customize comparing values. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'name': 'fred' }; + * var copy = { 'name': 'fred' }; + * + * object == copy; + * // => false + * + * _.isEqual(object, copy); + * // => true + * + * var words = ['hello', 'goodbye']; + * var otherWords = ['hi', 'goodbye']; + * + * _.isEqual(words, otherWords, function(a, b) { + * var reGreet = /^(?:hello|hi)$/i, + * aGreet = _.isString(a) && reGreet.test(a), + * bGreet = _.isString(b) && reGreet.test(b); + * + * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined; + * }); + * // => true + */ +function isEqual(a, b, callback, thisArg) { + return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2)); +} + +module.exports = isEqual; + +},{"../internals/baseCreateCallback":92,"../internals/baseIsEqual":97}],153:[function(require,module,exports){ +var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeIsFinite = global.isFinite, + nativeIsNaN = global.isNaN; + +/** + * Checks if `value` is, or can be coerced to, a finite number. + * + * Note: This is not the same as native `isFinite` which will return true for + * booleans and empty strings. See http://es5.github.io/#x15.1.2.5. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is finite, else `false`. + * @example + * + * _.isFinite(-101); + * // => true + * + * _.isFinite('10'); + * // => true + * + * _.isFinite(true); + * // => false + * + * _.isFinite(''); + * // => false + * + * _.isFinite(Infinity); + * // => false + */ +function isFinite(value) { + return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value)); +} + +module.exports = isFinite; + +},{}],154:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Checks if `value` is a function. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + */ +function isFunction(value) { + return typeof value == 'function'; +} + +module.exports = isFunction; + +},{}],155:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isNumber = require('./isNumber'); + +/** + * Checks if `value` is `NaN`. + * + * Note: This is not the same as native `isNaN` which will return `true` for + * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`. + * @example + * + * _.isNaN(NaN); + * // => true + * + * _.isNaN(new Number(NaN)); + * // => true + * + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false + */ +function isNaN(value) { + // `NaN` as a primitive is the only value that is not equal to itself + // (perform the [[Class]] check first to avoid errors with some host objects in IE) + return isNumber(value) && value != +value; +} + +module.exports = isNaN; + +},{"./isNumber":157}],156:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Checks if `value` is `null`. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is `null`, else `false`. + * @example + * + * _.isNull(null); + * // => true + * + * _.isNull(undefined); + * // => false + */ +function isNull(value) { + return value === null; +} + +module.exports = isNull; + +},{}],157:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** `Object#toString` result shortcuts */ +var numberClass = '[object Number]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** + * Checks if `value` is a number. + * + * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a number, else `false`. + * @example + * + * _.isNumber(8.4 * 5); + * // => true + */ +function isNumber(value) { + return typeof value == 'number' || + value && typeof value == 'object' && toString.call(value) == numberClass || false; +} + +module.exports = isNumber; + +},{}],158:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var objectTypes = require('../internals/objectTypes'); + +/** + * Checks if `value` is the language type of Object. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ +function isObject(value) { + // check if the value is the ECMAScript language type of Object + // http://es5.github.io/#x8 + // and avoid a V8 bug + // http://code.google.com/p/v8/issues/detail?id=2291 + return !!(value && objectTypes[typeof value]); +} + +module.exports = isObject; + +},{"../internals/objectTypes":120}],159:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isNative = require('../internals/isNative'), + shimIsPlainObject = require('../internals/shimIsPlainObject'); + +/** `Object#toString` result shortcuts */ +var objectClass = '[object Object]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** Native method shortcuts */ +var getPrototypeOf = isNative(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf; + +/** + * Checks if `value` is an object created by the `Object` constructor. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * _.isPlainObject(new Shape); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true + */ +var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) { + if (!(value && toString.call(value) == objectClass)) { + return false; + } + var valueOf = value.valueOf, + objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto); + + return objProto + ? (value == objProto || getPrototypeOf(value) == objProto) + : shimIsPlainObject(value); +}; + +module.exports = isPlainObject; + +},{"../internals/isNative":114,"../internals/shimIsPlainObject":127}],160:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** `Object#toString` result shortcuts */ +var regexpClass = '[object RegExp]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** + * Checks if `value` is a regular expression. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`. + * @example + * + * _.isRegExp(/fred/); + * // => true + */ +function isRegExp(value) { + return value && typeof value == 'object' && toString.call(value) == regexpClass || false; +} + +module.exports = isRegExp; + +},{}],161:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** `Object#toString` result shortcuts */ +var stringClass = '[object String]'; + +/** Used for native method references */ +var objectProto = Object.prototype; + +/** Used to resolve the internal [[Class]] of values */ +var toString = objectProto.toString; + +/** + * Checks if `value` is a string. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a string, else `false`. + * @example + * + * _.isString('fred'); + * // => true + */ +function isString(value) { + return typeof value == 'string' || + value && typeof value == 'object' && toString.call(value) == stringClass || false; +} + +module.exports = isString; + +},{}],162:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Checks if `value` is `undefined`. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`. + * @example + * + * _.isUndefined(void 0); + * // => true + */ +function isUndefined(value) { + return typeof value == 'undefined'; +} + +module.exports = isUndefined; + +},{}],163:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isNative = require('../internals/isNative'), + isObject = require('./isObject'), + shimKeys = require('../internals/shimKeys'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys; + +/** + * Creates an array composed of the own enumerable property names of an object. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns an array of property names. + * @example + * + * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); + * // => ['one', 'two', 'three'] (property order is not guaranteed across environments) + */ +var keys = !nativeKeys ? shimKeys : function(object) { + if (!isObject(object)) { + return []; + } + return nativeKeys(object); +}; + +module.exports = keys; + +},{"../internals/isNative":114,"../internals/shimKeys":128,"./isObject":158}],164:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var createCallback = require('../functions/createCallback'), + forOwn = require('./forOwn'); + +/** + * Creates an object with the same keys as `object` and values generated by + * running each own enumerable property of `object` through the callback. + * The callback is bound to `thisArg` and invoked with three arguments; + * (value, key, object). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new object with values of the results of each `callback` execution. + * @example + * + * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; }); + * // => { 'a': 3, 'b': 6, 'c': 9 } + * + * var characters = { + * 'fred': { 'name': 'fred', 'age': 40 }, + * 'pebbles': { 'name': 'pebbles', 'age': 1 } + * }; + * + * // using "_.pluck" callback shorthand + * _.mapValues(characters, 'age'); + * // => { 'fred': 40, 'pebbles': 1 } + */ +function mapValues(object, callback, thisArg) { + var result = {}; + callback = createCallback(callback, thisArg, 3); + + forOwn(object, function(value, key, object) { + result[key] = callback(value, key, object); + }); + return result; +} + +module.exports = mapValues; + +},{"../functions/createCallback":76,"./forOwn":141}],165:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreateCallback = require('../internals/baseCreateCallback'), + baseMerge = require('../internals/baseMerge'), + getArray = require('../internals/getArray'), + isObject = require('./isObject'), + releaseArray = require('../internals/releaseArray'), + slice = require('../internals/slice'); + +/** + * Recursively merges own enumerable properties of the source object(s), that + * don't resolve to `undefined` into the destination object. Subsequent sources + * will overwrite property assignments of previous sources. If a callback is + * provided it will be executed to produce the merged values of the destination + * and source properties. If the callback returns `undefined` merging will + * be handled by the method instead. The callback is bound to `thisArg` and + * invoked with two arguments; (objectValue, sourceValue). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The destination object. + * @param {...Object} [source] The source objects. + * @param {Function} [callback] The function to customize merging properties. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the destination object. + * @example + * + * var names = { + * 'characters': [ + * { 'name': 'barney' }, + * { 'name': 'fred' } + * ] + * }; + * + * var ages = { + * 'characters': [ + * { 'age': 36 }, + * { 'age': 40 } + * ] + * }; + * + * _.merge(names, ages); + * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] } + * + * var food = { + * 'fruits': ['apple'], + * 'vegetables': ['beet'] + * }; + * + * var otherFood = { + * 'fruits': ['banana'], + * 'vegetables': ['carrot'] + * }; + * + * _.merge(food, otherFood, function(a, b) { + * return _.isArray(a) ? a.concat(b) : undefined; + * }); + * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] } + */ +function merge(object) { + var args = arguments, + length = 2; + + if (!isObject(object)) { + return object; + } + // allows working with `_.reduce` and `_.reduceRight` without using + // their `index` and `collection` arguments + if (typeof args[2] != 'number') { + length = args.length; + } + if (length > 3 && typeof args[length - 2] == 'function') { + var callback = baseCreateCallback(args[--length - 1], args[length--], 2); + } else if (length > 2 && typeof args[length - 1] == 'function') { + callback = args[--length]; + } + var sources = slice(arguments, 1, length), + index = -1, + stackA = getArray(), + stackB = getArray(); + + while (++index < length) { + baseMerge(object, sources[index], callback, stackA, stackB); + } + releaseArray(stackA); + releaseArray(stackB); + return object; +} + +module.exports = merge; + +},{"../internals/baseCreateCallback":92,"../internals/baseMerge":98,"../internals/getArray":110,"../internals/releaseArray":124,"../internals/slice":129,"./isObject":158}],166:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseDifference = require('../internals/baseDifference'), + baseFlatten = require('../internals/baseFlatten'), + createCallback = require('../functions/createCallback'), + forIn = require('./forIn'); + +/** + * Creates a shallow clone of `object` excluding the specified properties. + * Property names may be specified as individual arguments or as arrays of + * property names. If a callback is provided it will be executed for each + * property of `object` omitting the properties the callback returns truey + * for. The callback is bound to `thisArg` and invoked with three arguments; + * (value, key, object). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The source object. + * @param {Function|...string|string[]} [callback] The properties to omit or the + * function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns an object without the omitted properties. + * @example + * + * _.omit({ 'name': 'fred', 'age': 40 }, 'age'); + * // => { 'name': 'fred' } + * + * _.omit({ 'name': 'fred', 'age': 40 }, function(value) { + * return typeof value == 'number'; + * }); + * // => { 'name': 'fred' } + */ +function omit(object, callback, thisArg) { + var result = {}; + if (typeof callback != 'function') { + var props = []; + forIn(object, function(value, key) { + props.push(key); + }); + props = baseDifference(props, baseFlatten(arguments, true, false, 1)); + + var index = -1, + length = props.length; + + while (++index < length) { + var key = props[index]; + result[key] = object[key]; + } + } else { + callback = createCallback(callback, thisArg, 3); + forIn(object, function(value, key, object) { + if (!callback(value, key, object)) { + result[key] = value; + } + }); + } + return result; +} + +module.exports = omit; + +},{"../functions/createCallback":76,"../internals/baseDifference":94,"../internals/baseFlatten":95,"./forIn":139}],167:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var keys = require('./keys'); + +/** + * Creates a two dimensional array of an object's key-value pairs, + * i.e. `[[key1, value1], [key2, value2]]`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns new array of key-value pairs. + * @example + * + * _.pairs({ 'barney': 36, 'fred': 40 }); + * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments) + */ +function pairs(object) { + var index = -1, + props = keys(object), + length = props.length, + result = Array(length); + + while (++index < length) { + var key = props[index]; + result[index] = [key, object[key]]; + } + return result; +} + +module.exports = pairs; + +},{"./keys":163}],168:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseFlatten = require('../internals/baseFlatten'), + createCallback = require('../functions/createCallback'), + forIn = require('./forIn'), + isObject = require('./isObject'); + +/** + * Creates a shallow clone of `object` composed of the specified properties. + * Property names may be specified as individual arguments or as arrays of + * property names. If a callback is provided it will be executed for each + * property of `object` picking the properties the callback returns truey + * for. The callback is bound to `thisArg` and invoked with three arguments; + * (value, key, object). + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The source object. + * @param {Function|...string|string[]} [callback] The function called per + * iteration or property names to pick, specified as individual property + * names or arrays of property names. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns an object composed of the picked properties. + * @example + * + * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name'); + * // => { 'name': 'fred' } + * + * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) { + * return key.charAt(0) != '_'; + * }); + * // => { 'name': 'fred' } + */ +function pick(object, callback, thisArg) { + var result = {}; + if (typeof callback != 'function') { + var index = -1, + props = baseFlatten(arguments, true, false, 1), + length = isObject(object) ? props.length : 0; + + while (++index < length) { + var key = props[index]; + if (key in object) { + result[key] = object[key]; + } + } + } else { + callback = createCallback(callback, thisArg, 3); + forIn(object, function(value, key, object) { + if (callback(value, key, object)) { + result[key] = value; + } + }); + } + return result; +} + +module.exports = pick; + +},{"../functions/createCallback":76,"../internals/baseFlatten":95,"./forIn":139,"./isObject":158}],169:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCreate = require('../internals/baseCreate'), + createCallback = require('../functions/createCallback'), + forEach = require('../collections/forEach'), + forOwn = require('./forOwn'), + isArray = require('./isArray'); + +/** + * An alternative to `_.reduce` this method transforms `object` to a new + * `accumulator` object which is the result of running each of its own + * enumerable properties through a callback, with each callback execution + * potentially mutating the `accumulator` object. The callback is bound to + * `thisArg` and invoked with four arguments; (accumulator, value, key, object). + * Callbacks may exit iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Array|Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [accumulator] The custom accumulator value. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the accumulated value. + * @example + * + * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) { + * num *= num; + * if (num % 2) { + * return result.push(num) < 3; + * } + * }); + * // => [1, 9, 25] + * + * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { + * result[key] = num * 3; + * }); + * // => { 'a': 3, 'b': 6, 'c': 9 } + */ +function transform(object, callback, accumulator, thisArg) { + var isArr = isArray(object); + if (accumulator == null) { + if (isArr) { + accumulator = []; + } else { + var ctor = object && object.constructor, + proto = ctor && ctor.prototype; + + accumulator = baseCreate(proto); + } + } + if (callback) { + callback = createCallback(callback, thisArg, 4); + (isArr ? forEach : forOwn)(object, function(value, index, object) { + return callback(accumulator, value, index, object); + }); + } + return accumulator; +} + +module.exports = transform; + +},{"../collections/forEach":51,"../functions/createCallback":76,"../internals/baseCreate":91,"./forOwn":141,"./isArray":147}],170:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var keys = require('./keys'); + +/** + * Creates an array composed of the own enumerable property values of `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns an array of property values. + * @example + * + * _.values({ 'one': 1, 'two': 2, 'three': 3 }); + * // => [1, 2, 3] (property order is not guaranteed across environments) + */ +function values(object) { + var index = -1, + props = keys(object), + length = props.length, + result = Array(length); + + while (++index < length) { + result[index] = object[props[index]]; + } + return result; +} + +module.exports = values; + +},{"./keys":163}],171:[function(require,module,exports){ +var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isNative = require('./internals/isNative'); + +/** Used to detect functions containing a `this` reference */ +var reThis = /\bthis\b/; + +/** + * An object used to flag environments features. + * + * @static + * @memberOf _ + * @type Object + */ +var support = {}; + +/** + * Detect if functions can be decompiled by `Function#toString` + * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps). + * + * @memberOf _.support + * @type boolean + */ +support.funcDecomp = !isNative(global.WinRTError) && reThis.test(function() { return this; }); + +/** + * Detect if `Function#name` is supported (all but IE). + * + * @memberOf _.support + * @type boolean + */ +support.funcNames = typeof Function.name == 'string'; + +module.exports = support; + +},{"./internals/isNative":114}],172:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +module.exports = { + 'constant': require('./utilities/constant'), + 'createCallback': require('./functions/createCallback'), + 'escape': require('./utilities/escape'), + 'identity': require('./utilities/identity'), + 'mixin': require('./utilities/mixin'), + 'noConflict': require('./utilities/noConflict'), + 'noop': require('./utilities/noop'), + 'now': require('./utilities/now'), + 'parseInt': require('./utilities/parseInt'), + 'property': require('./utilities/property'), + 'random': require('./utilities/random'), + 'result': require('./utilities/result'), + 'template': require('./utilities/template'), + 'templateSettings': require('./utilities/templateSettings'), + 'times': require('./utilities/times'), + 'unescape': require('./utilities/unescape'), + 'uniqueId': require('./utilities/uniqueId') +}; + +},{"./functions/createCallback":76,"./utilities/constant":173,"./utilities/escape":174,"./utilities/identity":175,"./utilities/mixin":176,"./utilities/noConflict":177,"./utilities/noop":178,"./utilities/now":179,"./utilities/parseInt":180,"./utilities/property":181,"./utilities/random":182,"./utilities/result":183,"./utilities/template":184,"./utilities/templateSettings":185,"./utilities/times":186,"./utilities/unescape":187,"./utilities/uniqueId":188}],173:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Creates a function that returns `value`. + * + * @static + * @memberOf _ + * @category Utilities + * @param {*} value The value to return from the new function. + * @returns {Function} Returns the new function. + * @example + * + * var object = { 'name': 'fred' }; + * var getter = _.constant(object); + * getter() === object; + * // => true + */ +function constant(value) { + return function() { + return value; + }; +} + +module.exports = constant; + +},{}],174:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var escapeHtmlChar = require('../internals/escapeHtmlChar'), + keys = require('../objects/keys'), + reUnescapedHtml = require('../internals/reUnescapedHtml'); + +/** + * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their + * corresponding HTML entities. + * + * @static + * @memberOf _ + * @category Utilities + * @param {string} string The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escape('Fred, Wilma, & Pebbles'); + * // => 'Fred, Wilma, & Pebbles' + */ +function escape(string) { + return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar); +} + +module.exports = escape; + +},{"../internals/escapeHtmlChar":108,"../internals/reUnescapedHtml":123,"../objects/keys":163}],175:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * This method returns the first argument provided to it. + * + * @static + * @memberOf _ + * @category Utilities + * @param {*} value Any value. + * @returns {*} Returns `value`. + * @example + * + * var object = { 'name': 'fred' }; + * _.identity(object) === object; + * // => true + */ +function identity(value) { + return value; +} + +module.exports = identity; + +},{}],176:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var forEach = require('../collections/forEach'), + functions = require('../objects/functions'), + isFunction = require('../objects/isFunction'), + isObject = require('../objects/isObject'); + +/** + * Used for `Array` method references. + * + * Normally `Array.prototype` would suffice, however, using an array literal + * avoids issues in Narwhal. + */ +var arrayRef = []; + +/** Native method shortcuts */ +var push = arrayRef.push; + +/** + * Adds function properties of a source object to the destination object. + * If `object` is a function methods will be added to its prototype as well. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Function|Object} [object=lodash] object The destination object. + * @param {Object} source The object of functions to add. + * @param {Object} [options] The options object. + * @param {boolean} [options.chain=true] Specify whether the functions added are chainable. + * @example + * + * function capitalize(string) { + * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); + * } + * + * _.mixin({ 'capitalize': capitalize }); + * _.capitalize('fred'); + * // => 'Fred' + * + * _('fred').capitalize().value(); + * // => 'Fred' + * + * _.mixin({ 'capitalize': capitalize }, { 'chain': false }); + * _('fred').capitalize(); + * // => 'Fred' + */ +function mixin(object, source, options) { + var chain = true, + methodNames = source && functions(source); + + if (options === false) { + chain = false; + } else if (isObject(options) && 'chain' in options) { + chain = options.chain; + } + var ctor = object, + isFunc = isFunction(ctor); + + forEach(methodNames, function(methodName) { + var func = object[methodName] = source[methodName]; + if (isFunc) { + ctor.prototype[methodName] = function() { + var chainAll = this.__chain__, + value = this.__wrapped__, + args = [value]; + + push.apply(args, arguments); + var result = func.apply(object, args); + if (chain || chainAll) { + if (value === result && isObject(result)) { + return this; + } + result = new ctor(result); + result.__chain__ = chainAll; + } + return result; + }; + } + }); +} + +module.exports = mixin; + +},{"../collections/forEach":51,"../objects/functions":143,"../objects/isFunction":154,"../objects/isObject":158}],177:[function(require,module,exports){ +var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used to restore the original `_` reference in `noConflict` */ +var oldDash = global._; + +/** + * Reverts the '_' variable to its previous value and returns a reference to + * the `lodash` function. + * + * @static + * @memberOf _ + * @category Utilities + * @returns {Function} Returns the `lodash` function. + * @example + * + * var lodash = _.noConflict(); + */ +function noConflict() { + global._ = oldDash; + return this; +} + +module.exports = noConflict; + +},{}],178:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * A no-operation function. + * + * @static + * @memberOf _ + * @category Utilities + * @example + * + * var object = { 'name': 'fred' }; + * _.noop(object) === undefined; + * // => true + */ +function noop() { + // no operation performed +} + +module.exports = noop; + +},{}],179:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isNative = require('../internals/isNative'); + +/** + * Gets the number of milliseconds that have elapsed since the Unix epoch + * (1 January 1970 00:00:00 UTC). + * + * @static + * @memberOf _ + * @category Utilities + * @example + * + * var stamp = _.now(); + * _.defer(function() { console.log(_.now() - stamp); }); + * // => logs the number of milliseconds it took for the deferred function to be called + */ +var now = isNative(now = Date.now) && now || function() { + return new Date().getTime(); +}; + +module.exports = now; + +},{"../internals/isNative":114}],180:[function(require,module,exports){ +var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isString = require('../objects/isString'); + +/** Used to detect and test whitespace */ +var whitespace = ( + // whitespace + ' \t\x0B\f\xA0\ufeff' + + + // line terminators + '\n\r\u2028\u2029' + + + // unicode category "Zs" space separators + '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000' +); + +/** Used to match leading whitespace and zeros to be removed */ +var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeParseInt = global.parseInt; + +/** + * Converts the given value into an integer of the specified radix. + * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the + * `value` is a hexadecimal, in which case a `radix` of `16` is used. + * + * Note: This method avoids differences in native ES3 and ES5 `parseInt` + * implementations. See http://es5.github.io/#E. + * + * @static + * @memberOf _ + * @category Utilities + * @param {string} value The value to parse. + * @param {number} [radix] The radix used to interpret the value to parse. + * @returns {number} Returns the new integer value. + * @example + * + * _.parseInt('08'); + * // => 8 + */ +var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) { + // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt` + return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0); +}; + +module.exports = parseInt; + +},{"../objects/isString":161}],181:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Creates a "_.pluck" style function, which returns the `key` value of a + * given object. + * + * @static + * @memberOf _ + * @category Utilities + * @param {string} key The name of the property to retrieve. + * @returns {Function} Returns the new function. + * @example + * + * var characters = [ + * { 'name': 'fred', 'age': 40 }, + * { 'name': 'barney', 'age': 36 } + * ]; + * + * var getName = _.property('name'); + * + * _.map(characters, getName); + * // => ['barney', 'fred'] + * + * _.sortBy(characters, getName); + * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] + */ +function property(key) { + return function(object) { + return object[key]; + }; +} + +module.exports = property; + +},{}],182:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseRandom = require('../internals/baseRandom'); + +/* Native method shortcuts for methods with the same name as other `lodash` methods */ +var nativeMin = Math.min, + nativeRandom = Math.random; + +/** + * Produces a random number between `min` and `max` (inclusive). If only one + * argument is provided a number between `0` and the given number will be + * returned. If `floating` is truey or either `min` or `max` are floats a + * floating-point number will be returned instead of an integer. + * + * @static + * @memberOf _ + * @category Utilities + * @param {number} [min=0] The minimum possible value. + * @param {number} [max=1] The maximum possible value. + * @param {boolean} [floating=false] Specify returning a floating-point number. + * @returns {number} Returns a random number. + * @example + * + * _.random(0, 5); + * // => an integer between 0 and 5 + * + * _.random(5); + * // => also an integer between 0 and 5 + * + * _.random(5, true); + * // => a floating-point number between 0 and 5 + * + * _.random(1.2, 5.2); + * // => a floating-point number between 1.2 and 5.2 + */ +function random(min, max, floating) { + var noMin = min == null, + noMax = max == null; + + if (floating == null) { + if (typeof min == 'boolean' && noMax) { + floating = min; + min = 1; + } + else if (!noMax && typeof max == 'boolean') { + floating = max; + noMax = true; + } + } + if (noMin && noMax) { + max = 1; + } + min = +min || 0; + if (noMax) { + max = min; + min = 0; + } else { + max = +max || 0; + } + if (floating || min % 1 || max % 1) { + var rand = nativeRandom(); + return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max); + } + return baseRandom(min, max); +} + +module.exports = random; + +},{"../internals/baseRandom":99}],183:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isFunction = require('../objects/isFunction'); + +/** + * Resolves the value of property `key` on `object`. If `key` is a function + * it will be invoked with the `this` binding of `object` and its result returned, + * else the property value is returned. If `object` is falsey then `undefined` + * is returned. + * + * @static + * @memberOf _ + * @category Utilities + * @param {Object} object The object to inspect. + * @param {string} key The name of the property to resolve. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { + * 'cheese': 'crumpets', + * 'stuff': function() { + * return 'nonsense'; + * } + * }; + * + * _.result(object, 'cheese'); + * // => 'crumpets' + * + * _.result(object, 'stuff'); + * // => 'nonsense' + */ +function result(object, key) { + if (object) { + var value = object[key]; + return isFunction(value) ? object[key]() : value; + } +} + +module.exports = result; + +},{"../objects/isFunction":154}],184:[function(require,module,exports){ +/** + * Lo-Dash 2.4.1 (Custom Build) + * Build: `lodash modularize modern exports="node" -o ./modern/` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var defaults = require('../objects/defaults'), + escape = require('./escape'), + escapeStringChar = require('../internals/escapeStringChar'), + keys = require('../objects/keys'), + reInterpolate = require('../internals/reInterpolate'), + templateSettings = require('./templateSettings'), + values = require('../objects/values'); + +/** Used to match empty string literals in compiled template source */ +var reEmptyStringLeading = /\b__p \+= '';/g, + reEmptyStringMiddle = /\b(__p \+=) '' \+/g, + reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; + +/** + * Used to match ES6 template delimiters + * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals + */ +var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; + +/** Used to ensure capturing order of template delimiters */ +var reNoMatch = /($^)/; + +/** Used to match unescaped characters in compiled string literals */ +var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g; + +/** + * A micro-templating method that handles arbitrary delimiters, preserves + * whitespace, and correctly escapes quotes within interpolated code. + * + * Note: In the development build, `_.template` utilizes sourceURLs for easier + * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl + * + * For more information on precompiling templates see: + * http://lodash.com/custom-builds + * + * For more information on Chrome extension sandboxes see: + * http://developer.chrome.com/stable/extensions/sandboxingEval.html + * + * @static + * @memberOf _ + * @category Utilities + * @param {string} text The template text. + * @param {Object} data The data object used to populate the text. + * @param {Object} [options] The options object. + * @param {RegExp} [options.escape] The "escape" delimiter. + * @param {RegExp} [options.evaluate] The "evaluate" delimiter. + * @param {Object} [options.imports] An object to import into the template as local variables. + * @param {RegExp} [options.interpolate] The "interpolate" delimiter. + * @param {string} [sourceURL] The sourceURL of the template's compiled source. + * @param {string} [variable] The data object variable name. + * @returns {Function|string} Returns a compiled function when no `data` object + * is given, else it returns the interpolated text. + * @example + * + * // using the "interpolate" delimiter to create a compiled template + * var compiled = _.template('hello <%= name %>'); + * compiled({ 'name': 'fred' }); + * // => 'hello fred' + * + * // using the "escape" delimiter to escape HTML in data property values + * _.template('<%- value %>', { 'value': ' + + + + + + + + + + + + + + +
    +
    + + +
    +

    jQuery MiniColors 2.1

    +

    + Now with Bootstrap 3 support! +

    +

    + A project by A Beautiful Site, + originally developed for Surreal CMS. +

    +
    + + +

    Contents

    + + + +

    Download

    +

    + This project is on GitHub. Feel free to post bug reports, feature requests, and code + improvements on the official project page. +

    +

    + Download on GitHub +

    + + +

    Demos

    +

    + This is the main demo page, which uses Bootstrap 3, + but this plugin works without Bootstrap as well. +

    +

    + View Demo Without Bootstrap +

    + + +

    Control Types

    +
    +
    +
    + +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +

    Input Modes

    +
    +
    +
    +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + +

    Positions

    +
    +

    + Valid positions include bottom left, bottom right, top + left, and top right. +

    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    +
    + + +

    …and more!

    +
    +
    +
    +
    + +
    + + + Opacity can be assigned by including the data-opacity attribute + or by setting the opacity option to true. + +
    +
    +
    +
    + +
    + + + This field has a default value assigned to it, so it will never be empty. + +
    +
    +
    +
    + +
    + + + This field will always be uppercase. + +
    +
    +
    +
    + + +

    API

    + + +

    Instantiation

    +

    + Instantiate like any other jQuery plugin: +

    +
    $('INPUT.minicolors').minicolors(settings);
    + + +

    Settings

    + +

    + Default settings are as follows: +

    +
    +$.minicolors = {
    +    defaults: {
    +        animationSpeed: 50,
    +        animationEasing: 'swing',
    +        change: null,
    +        changeDelay: 0,
    +        control: 'hue',
    +        dataUris: true,
    +        defaultValue: '',
    +        hide: null,
    +        hideSpeed: 100,
    +        inline: false,
    +        letterCase: 'lowercase',
    +        opacity: false,
    +        position: 'bottom left',
    +        show: null,
    +        showSpeed: 100,
    +        theme: 'default'
    +    }
    +};
    +
    +

    + For convenience, you can change default settings globally by assigning new values: +

    +
    +$.minicolors.defaults.changeDelay = 200;
    +
    +

    + To change multiple properties at once, use $.extend(): +

    +
    +$.minicolors.defaults = $.extend($.minicolors.defaults, {
    +    changeDelay: 200,
    +    letterCase: 'uppercase',
    +    theme: 'bootstrap'
    +});
    +
    +

    + Note: Changing default settings will not affect controls that + are already initialized. +

    + + + +
    +
    animationSpeed
    +
    +

    + The animation speed of the sliders when the user taps or clicks a new color. Set to + 0 for no animation. +

    +
    + +
    animationEasing
    +
    +

    + The easing to use when animating the sliders. +

    +
    + +
    changeDelay
    +
    +

    + The time, in milliseconds, to defer the change event from firing while + the user makes a selection. This is useful for preventing the change event + from firing frequently as the user drags the color picker around. +

    +

    + The default value is 0 (no delay). If your change callback + features something resource-intensive (such as an AJAX request), you’ll probably want + to set this to at least 200. +

    +
    + +
    control
    +
    +

    + Determines the type of control. Valid options are hue, brightness, + saturation, and wheel. +

    +
    + +
    dataUris
    +
    +

    + Set this to false if you require IE7 support. This setting will force + the plugin to load an external image instead of using dataURIs. +

    +
    + +
    defaultValue
    +
    +

    + To force a default color, set this to a valid hex string. When the user clears the + control, it will revert to this color. +

    +
    + +
    hideSpeed & showSpeed
    +
    +

    + The speed at which to hide and show the color picker. +

    +
    + +
    inline
    +
    +

    + Set to true to force the color picker to appear inline. +

    +
    + +
    letterCase
    +
    +

    + Determines the letter case of the hex code value. Valid options are uppercase + or lowercase. +

    +
    + +
    opacity
    +
    +

    + Set to true to enable the opacity slider. (Use the input element's + data-opacity attribute to set a preset value.) +

    +
    + +
    position
    +
    +

    + Sets the position of the dropdown. Valid options are bottom left, + bottom right, top left, and top right. +

    +

    + The swatchPosition setting has been removed in version 2.1. The position + of the swatch is now determined by position. +

    +
    + +
    theme
    +
    +

    + A string containing the name of the custom theme to be applied. In your CSS, prefix + your selectors like this: +

    +
    +.minicolors-theme-yourThemeName { ... }
    +
    +

    + If you are using the default theme, you will probably need to adjust the swatch + styles depending on your existing stylesheet rules. Version 2.1 removes as much + styling on the input element as possible, which means it’s up to + you to adjust your CSS to make sure the swatch aligns properly. +

    +

    + To adjust the swatch, override these styles: +

    +
    +.minicolors-theme-default .minicolors-swatch {
    +    top: 5px;
    +    left: 5px;
    +    width: 18px;
    +    height: 18px;
    +}
    +.minicolors-theme-default.minicolors-position-right .minicolors-swatch {
    +    left: auto;
    +    right: 5px;
    +}
    +
    +
    + + + + +

    Methods

    +

    Use this syntax for calling methods:

    +
    $(selector).minicolors('method', [data]);
    +
    +
    create
    +
    +

    + Initializes the control for all items matching your selector. This is the default + method, so data may be passed in as the only argument. +

    +

    + To set a preset color value, populate the value attribute of the original + input element. +

    +
    + +
    destroy
    +
    +

    + Returns the input element to its original, uninitialized state. +

    +
    + +
    opacity
    +
    +

    + Gets or sets a control's opacity level. To use this method as a setter, pass data in + as a value between 0 and 1. (You can also obtain this value by checking the input + element's data-opacity attribute.) +

    +

    + To set a preset opacity value, populate the data-opacity attribute of the + original input element. +

    +
    + +
    rgbObject
    +
    +

    + Returns an object containing red, green, blue, and alpha properties that correspond to + the control's current value. Example: +

    +
    { r: 0, g: 82, b: 148, a: 0.75 }
    +
    + +
    rgbString & rgbaString
    +
    +

    + Returns an RGB or RGBA string suitable for use in your CSS. Examples: +

    +
    +rgb(0, 82, 148)
    +rgba(0, 82, 148, .75)
    +
    +
    + +
    settings
    +
    +

    + Gets or sets a control's settings. If new settings are passed in, the control will + destroy and re-initialize itself with any new settings overriding the old ones. +

    +
    + +
    value
    +
    +

    + Gets or sets a control's color value. To use this method as a setter, pass + data in as a hex value. (You can also obtain this value by checking the + input element's value attribute.) +

    +
    +
    + + +

    Events

    +
    +
    change
    +
    +

    Fires when the value of the color picker changes. The this keyword will reference the original input element. +

    +$(selector).minicolors({
    +    change: function(hex, opacity) {
    +        console.log(hex + ' - ' + opacity);
    +    }
    +});
    +
    +

    + Warning! This event will fire a lot when the user drags the + color picker around. Use the changeDelay setting to reduce its + frequency. +

    +
    + +
    hide
    +
    +

    + Fires when the color picker is hidden. The this keyword will reference + the original input element. +

    +
    +$(selector).minicolors({
    +    hide: function() {
    +    console.log('Hide event triggered!');
    +    }
    +});
    +
    +
    + +
    show
    +
    +

    + Fires when the color picker is shown. The this keyword will reference + the original input element. +

    +
    +$(selector).minicolors({
    +    show: function() {
    +        console.log('Show event triggered!');
    +    }
    +});
    +
    +
    +
    + +

    License

    +

    + Licensed under the MIT license, + same as jQuery. +

    +

    + ©2013 A Beautiful Site, LLC. +

    +
    +
    + + \ No newline at end of file diff --git a/vendor/assets/components/jquery-minicolors/jquery.minicolors.css b/vendor/assets/components/jquery-minicolors/jquery.minicolors.css new file mode 100644 index 000000000..36832eec9 --- /dev/null +++ b/vendor/assets/components/jquery-minicolors/jquery.minicolors.css @@ -0,0 +1,268 @@ +.minicolors { + position: relative; +} + +.minicolors-sprite { + background-image: url(data:image/png;base64,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); +} + +.minicolors-no-data-uris .minicolors-sprite { + background-image: url(jquery.minicolors.png); +} + +.minicolors-swatch { + position: absolute; + vertical-align: middle; + background-position: -80px 0; + border: solid 1px #ccc; + cursor: text; + padding: 0; + margin: 0; + display: inline-block; +} + +.minicolors-swatch-color { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.minicolors input[type=hidden] + .minicolors-swatch { + width: 28px; + position: static; + cursor: pointer; +} + +/* Panel */ +.minicolors-panel { + position: absolute; + width: 173px; + height: 152px; + background: white; + border: solid 1px #CCC; + box-shadow: 0 0 20px rgba(0, 0, 0, .2); + z-index: 99999; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; + display: none; +} + +.minicolors-panel.minicolors-visible { + display: block; +} + +/* Panel positioning */ +.minicolors-position-top .minicolors-panel { + top: -154px; +} + +.minicolors-position-right .minicolors-panel { + right: 0; +} + +.minicolors-position-bottom .minicolors-panel { + top: auto; +} + +.minicolors-position-left .minicolors-panel { + left: 0; +} + +.minicolors-with-opacity .minicolors-panel { + width: 194px; +} + +.minicolors .minicolors-grid { + position: absolute; + top: 1px; + left: 1px; + width: 150px; + height: 150px; + background-position: -120px 0; + cursor: crosshair; +} + +.minicolors .minicolors-grid-inner { + position: absolute; + top: 0; + left: 0; + width: 150px; + height: 150px; +} + +.minicolors-slider-saturation .minicolors-grid { + background-position: -420px 0; +} + +.minicolors-slider-saturation .minicolors-grid-inner { + background-position: -270px 0; + background-image: inherit; +} + +.minicolors-slider-brightness .minicolors-grid { + background-position: -570px 0; +} + +.minicolors-slider-brightness .minicolors-grid-inner { + background-color: black; +} + +.minicolors-slider-wheel .minicolors-grid { + background-position: -720px 0; +} + +.minicolors-slider, +.minicolors-opacity-slider { + position: absolute; + top: 1px; + left: 152px; + width: 20px; + height: 150px; + background-color: white; + background-position: 0 0; + cursor: row-resize; +} + +.minicolors-slider-saturation .minicolors-slider { + background-position: -60px 0; +} + +.minicolors-slider-brightness .minicolors-slider { + background-position: -20px 0; +} + +.minicolors-slider-wheel .minicolors-slider { + background-position: -20px 0; +} + +.minicolors-opacity-slider { + left: 173px; + background-position: -40px 0; + display: none; +} + +.minicolors-with-opacity .minicolors-opacity-slider { + display: block; +} + +/* Pickers */ +.minicolors-grid .minicolors-picker { + position: absolute; + top: 70px; + left: 70px; + width: 12px; + height: 12px; + border: solid 1px black; + border-radius: 10px; + margin-top: -6px; + margin-left: -6px; + background: none; +} + +.minicolors-grid .minicolors-picker > div { + position: absolute; + top: 0; + left: 0; + width: 8px; + height: 8px; + border-radius: 8px; + border: solid 2px white; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +.minicolors-picker { + position: absolute; + top: 0; + left: 0; + width: 18px; + height: 2px; + background: white; + border: solid 1px black; + margin-top: -2px; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +/* Inline controls */ +.minicolors-inline { + display: inline-block; +} + +.minicolors-inline .minicolors-input { + display: none !important; +} + +.minicolors-inline .minicolors-panel { + position: relative; + top: auto; + left: auto; + box-shadow: none; + z-index: auto; + display: inline-block; +} + +/* Default theme */ +.minicolors-theme-default .minicolors-swatch { + top: 5px; + left: 5px; + width: 18px; + height: 18px; +} +.minicolors-theme-default.minicolors-position-right .minicolors-swatch { + left: auto; + right: 5px; +} +.minicolors-theme-default.minicolors { + width: auto; + display: inline-block; +} +.minicolors-theme-default .minicolors-input { + height: 20px; + width: auto; + display: inline-block; + padding-left: 26px; +} +.minicolors-theme-default.minicolors-position-right .minicolors-input { + padding-right: 26px; + padding-left: inherit; +} + +/* Bootstrap theme */ +.minicolors-theme-bootstrap .minicolors-swatch { + top: 3px; + left: 3px; + width: 28px; + height: 28px; + border-radius: 3px; +} +.minicolors-theme-bootstrap .minicolors-swatch-color { + border-radius: inherit; +} +.minicolors-theme-bootstrap.minicolors-position-right .minicolors-swatch { + left: auto; + right: 3px; +} +.minicolors-theme-bootstrap .minicolors-input { + padding-left: 44px; +} +.minicolors-theme-bootstrap.minicolors-position-right .minicolors-input { + padding-right: 44px; + padding-left: 12px; +} +.minicolors-theme-bootstrap .minicolors-input.input-lg + .minicolors-swatch { + top: 4px; + left: 4px; + width: 37px; + height: 37px; + border-radius: 5px; +} +.minicolors-theme-bootstrap .minicolors-input.input-sm + .minicolors-swatch { + width: 24px; + height: 24px; +} diff --git a/vendor/assets/components/jquery-minicolors/jquery.minicolors.js b/vendor/assets/components/jquery-minicolors/jquery.minicolors.js new file mode 100644 index 000000000..4418e513c --- /dev/null +++ b/vendor/assets/components/jquery-minicolors/jquery.minicolors.js @@ -0,0 +1,844 @@ +/* + * jQuery MiniColors: A tiny color picker built on jQuery + * + * Copyright Cory LaViska for A Beautiful Site, LLC. (http://www.abeautifulsite.net/) + * + * Licensed under the MIT license: http://opensource.org/licenses/MIT + * + */ +if(jQuery) (function($) { + + // Defaults + $.minicolors = { + defaults: { + animationSpeed: 50, + animationEasing: 'swing', + change: null, + changeDelay: 0, + control: 'hue', + dataUris: true, + defaultValue: '', + hide: null, + hideSpeed: 100, + inline: false, + letterCase: 'lowercase', + opacity: false, + position: 'bottom left', + show: null, + showSpeed: 100, + theme: 'default' + } + }; + + // Public methods + $.extend($.fn, { + minicolors: function(method, data) { + + switch(method) { + + // Destroy the control + case 'destroy': + $(this).each( function() { + destroy($(this)); + }); + return $(this); + + // Hide the color picker + case 'hide': + hide(); + return $(this); + + // Get/set opacity + case 'opacity': + // Getter + if( data === undefined ) { + // Getter + return $(this).attr('data-opacity'); + } else { + // Setter + $(this).each( function() { + updateFromInput($(this).attr('data-opacity', data)); + }); + } + return $(this); + + // Get an RGB(A) object based on the current color/opacity + case 'rgbObject': + return rgbObject($(this), method === 'rgbaObject'); + + // Get an RGB(A) string based on the current color/opacity + case 'rgbString': + case 'rgbaString': + return rgbString($(this), method === 'rgbaString'); + + // Get/set settings on the fly + case 'settings': + if( data === undefined ) { + return $(this).data('minicolors-settings'); + } else { + // Setter + $(this).each( function() { + var settings = $(this).data('minicolors-settings') || {}; + destroy($(this)); + $(this).minicolors($.extend(true, settings, data)); + }); + } + return $(this); + + // Show the color picker + case 'show': + show( $(this).eq(0) ); + return $(this); + + // Get/set the hex color value + case 'value': + if( data === undefined ) { + // Getter + return $(this).val(); + } else { + // Setter + $(this).each( function() { + updateFromInput($(this).val(data)); + }); + } + return $(this); + + // Initializes the control + default: + if( method !== 'create' ) data = method; + $(this).each( function() { + init($(this), data); + }); + return $(this); + + } + + } + }); + + // Initialize input elements + function init(input, settings) { + + var minicolors = $('
    '), + defaults = $.minicolors.defaults; + + // Do nothing if already initialized + if( input.data('minicolors-initialized') ) return; + + // Handle settings + settings = $.extend(true, {}, defaults, settings); + + // The wrapper + minicolors + .addClass('minicolors-theme-' + settings.theme) + .toggleClass('minicolors-with-opacity', settings.opacity) + .toggleClass('minicolors-no-data-uris', settings.dataUris !== true); + + // Custom positioning + if( settings.position !== undefined ) { + $.each(settings.position.split(' '), function() { + minicolors.addClass('minicolors-position-' + this); + }); + } + + // The input + input + .addClass('minicolors-input') + .data('minicolors-initialized', false) + .data('minicolors-settings', settings) + .prop('size', 7) + .wrap(minicolors) + .after( + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + ); + + // The swatch + if( !settings.inline ) { + input.after(''); + input.next('.minicolors-swatch').on('click', function(event) { + event.preventDefault(); + input.focus(); + }); + } + + // Prevent text selection in IE + input.parent().find('.minicolors-panel').on('selectstart', function() { return false; }).end(); + + // Inline controls + if( settings.inline ) input.parent().addClass('minicolors-inline'); + + updateFromInput(input, false); + + input.data('minicolors-initialized', true); + + } + + // Returns the input back to its original state + function destroy(input) { + + var minicolors = input.parent(); + + // Revert the input element + input + .removeData('minicolors-initialized') + .removeData('minicolors-settings') + .removeProp('size') + .removeClass('minicolors-input'); + + // Remove the wrap and destroy whatever remains + minicolors.before(input).remove(); + + } + + // Shows the specified dropdown panel + function show(input) { + + var minicolors = input.parent(), + panel = minicolors.find('.minicolors-panel'), + settings = input.data('minicolors-settings'); + + // Do nothing if uninitialized, disabled, inline, or already open + if( !input.data('minicolors-initialized') || + input.prop('disabled') || + minicolors.hasClass('minicolors-inline') || + minicolors.hasClass('minicolors-focus') + ) return; + + hide(); + + minicolors.addClass('minicolors-focus'); + panel + .stop(true, true) + .fadeIn(settings.showSpeed, function() { + if( settings.show ) settings.show.call(input.get(0)); + }); + + } + + // Hides all dropdown panels + function hide() { + + $('.minicolors-focus').each( function() { + + var minicolors = $(this), + input = minicolors.find('.minicolors-input'), + panel = minicolors.find('.minicolors-panel'), + settings = input.data('minicolors-settings'); + + panel.fadeOut(settings.hideSpeed, function() { + if( settings.hide ) settings.hide.call(input.get(0)); + minicolors.removeClass('minicolors-focus'); + }); + + }); + } + + // Moves the selected picker + function move(target, event, animate) { + + var input = target.parents('.minicolors').find('.minicolors-input'), + settings = input.data('minicolors-settings'), + picker = target.find('[class$=-picker]'), + offsetX = target.offset().left, + offsetY = target.offset().top, + x = Math.round(event.pageX - offsetX), + y = Math.round(event.pageY - offsetY), + duration = animate ? settings.animationSpeed : 0, + wx, wy, r, phi; + + // Touch support + if( event.originalEvent.changedTouches ) { + x = event.originalEvent.changedTouches[0].pageX - offsetX; + y = event.originalEvent.changedTouches[0].pageY - offsetY; + } + + // Constrain picker to its container + if( x < 0 ) x = 0; + if( y < 0 ) y = 0; + if( x > target.width() ) x = target.width(); + if( y > target.height() ) y = target.height(); + + // Constrain color wheel values to the wheel + if( target.parent().is('.minicolors-slider-wheel') && picker.parent().is('.minicolors-grid') ) { + wx = 75 - x; + wy = 75 - y; + r = Math.sqrt(wx * wx + wy * wy); + phi = Math.atan2(wy, wx); + if( phi < 0 ) phi += Math.PI * 2; + if( r > 75 ) { + r = 75; + x = 75 - (75 * Math.cos(phi)); + y = 75 - (75 * Math.sin(phi)); + } + x = Math.round(x); + y = Math.round(y); + } + + // Move the picker + if( target.is('.minicolors-grid') ) { + picker + .stop(true) + .animate({ + top: y + 'px', + left: x + 'px' + }, duration, settings.animationEasing, function() { + updateFromControl(input, target); + }); + } else { + picker + .stop(true) + .animate({ + top: y + 'px' + }, duration, settings.animationEasing, function() { + updateFromControl(input, target); + }); + } + + } + + // Sets the input based on the color picker values + function updateFromControl(input, target) { + + function getCoords(picker, container) { + + var left, top; + if( !picker.length || !container ) return null; + left = picker.offset().left; + top = picker.offset().top; + + return { + x: left - container.offset().left + (picker.outerWidth() / 2), + y: top - container.offset().top + (picker.outerHeight() / 2) + }; + + } + + var hue, saturation, brightness, x, y, r, phi, + + hex = input.val(), + opacity = input.attr('data-opacity'), + + // Helpful references + minicolors = input.parent(), + settings = input.data('minicolors-settings'), + swatch = minicolors.find('.minicolors-swatch'), + + // Panel objects + grid = minicolors.find('.minicolors-grid'), + slider = minicolors.find('.minicolors-slider'), + opacitySlider = minicolors.find('.minicolors-opacity-slider'), + + // Picker objects + gridPicker = grid.find('[class$=-picker]'), + sliderPicker = slider.find('[class$=-picker]'), + opacityPicker = opacitySlider.find('[class$=-picker]'), + + // Picker positions + gridPos = getCoords(gridPicker, grid), + sliderPos = getCoords(sliderPicker, slider), + opacityPos = getCoords(opacityPicker, opacitySlider); + + // Handle colors + if( target.is('.minicolors-grid, .minicolors-slider') ) { + + // Determine HSB values + switch(settings.control) { + + case 'wheel': + // Calculate hue, saturation, and brightness + x = (grid.width() / 2) - gridPos.x; + y = (grid.height() / 2) - gridPos.y; + r = Math.sqrt(x * x + y * y); + phi = Math.atan2(y, x); + if( phi < 0 ) phi += Math.PI * 2; + if( r > 75 ) { + r = 75; + gridPos.x = 69 - (75 * Math.cos(phi)); + gridPos.y = 69 - (75 * Math.sin(phi)); + } + saturation = keepWithin(r / 0.75, 0, 100); + hue = keepWithin(phi * 180 / Math.PI, 0, 360); + brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); + hex = hsb2hex({ + h: hue, + s: saturation, + b: brightness + }); + + // Update UI + slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 })); + break; + + case 'saturation': + // Calculate hue, saturation, and brightness + hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360); + saturation = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); + brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); + hex = hsb2hex({ + h: hue, + s: saturation, + b: brightness + }); + + // Update UI + slider.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: brightness })); + minicolors.find('.minicolors-grid-inner').css('opacity', saturation / 100); + break; + + case 'brightness': + // Calculate hue, saturation, and brightness + hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360); + saturation = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); + brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); + hex = hsb2hex({ + h: hue, + s: saturation, + b: brightness + }); + + // Update UI + slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 })); + minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (brightness / 100)); + break; + + default: + // Calculate hue, saturation, and brightness + hue = keepWithin(360 - parseInt(sliderPos.y * (360 / slider.height()), 10), 0, 360); + saturation = keepWithin(Math.floor(gridPos.x * (100 / grid.width())), 0, 100); + brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); + hex = hsb2hex({ + h: hue, + s: saturation, + b: brightness + }); + + // Update UI + grid.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: 100 })); + break; + + } + + // Adjust case + input.val( convertCase(hex, settings.letterCase) ); + + } + + // Handle opacity + if( target.is('.minicolors-opacity-slider') ) { + if( settings.opacity ) { + opacity = parseFloat(1 - (opacityPos.y / opacitySlider.height())).toFixed(2); + } else { + opacity = 1; + } + if( settings.opacity ) input.attr('data-opacity', opacity); + } + + // Set swatch color + swatch.find('SPAN').css({ + backgroundColor: hex, + opacity: opacity + }); + + // Handle change event + doChange(input, hex, opacity); + + } + + // Sets the color picker values from the input + function updateFromInput(input, preserveInputValue) { + + var hex, + hsb, + opacity, + x, y, r, phi, + + // Helpful references + minicolors = input.parent(), + settings = input.data('minicolors-settings'), + swatch = minicolors.find('.minicolors-swatch'), + + // Panel objects + grid = minicolors.find('.minicolors-grid'), + slider = minicolors.find('.minicolors-slider'), + opacitySlider = minicolors.find('.minicolors-opacity-slider'), + + // Picker objects + gridPicker = grid.find('[class$=-picker]'), + sliderPicker = slider.find('[class$=-picker]'), + opacityPicker = opacitySlider.find('[class$=-picker]'); + + // Determine hex/HSB values + hex = convertCase(parseHex(input.val(), true), settings.letterCase); + if( !hex ){ + hex = convertCase(parseHex(settings.defaultValue, true), settings.letterCase); + } + hsb = hex2hsb(hex); + + // Update input value + if( !preserveInputValue ) input.val(hex); + + // Determine opacity value + if( settings.opacity ) { + // Get from data-opacity attribute and keep within 0-1 range + opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1); + if( isNaN(opacity) ) opacity = 1; + input.attr('data-opacity', opacity); + swatch.find('SPAN').css('opacity', opacity); + + // Set opacity picker position + y = keepWithin(opacitySlider.height() - (opacitySlider.height() * opacity), 0, opacitySlider.height()); + opacityPicker.css('top', y + 'px'); + } + + // Update swatch + swatch.find('SPAN').css('backgroundColor', hex); + + // Determine picker locations + switch(settings.control) { + + case 'wheel': + // Set grid position + r = keepWithin(Math.ceil(hsb.s * 0.75), 0, grid.height() / 2); + phi = hsb.h * Math.PI / 180; + x = keepWithin(75 - Math.cos(phi) * r, 0, grid.width()); + y = keepWithin(75 - Math.sin(phi) * r, 0, grid.height()); + gridPicker.css({ + top: y + 'px', + left: x + 'px' + }); + + // Set slider position + y = 150 - (hsb.b / (100 / grid.height())); + if( hex === '' ) y = 0; + sliderPicker.css('top', y + 'px'); + + // Update panel color + slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 })); + break; + + case 'saturation': + // Set grid position + x = keepWithin((5 * hsb.h) / 12, 0, 150); + y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height()); + gridPicker.css({ + top: y + 'px', + left: x + 'px' + }); + + // Set slider position + y = keepWithin(slider.height() - (hsb.s * (slider.height() / 100)), 0, slider.height()); + sliderPicker.css('top', y + 'px'); + + // Update UI + slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: hsb.b })); + minicolors.find('.minicolors-grid-inner').css('opacity', hsb.s / 100); + break; + + case 'brightness': + // Set grid position + x = keepWithin((5 * hsb.h) / 12, 0, 150); + y = keepWithin(grid.height() - Math.ceil(hsb.s / (100 / grid.height())), 0, grid.height()); + gridPicker.css({ + top: y + 'px', + left: x + 'px' + }); + + // Set slider position + y = keepWithin(slider.height() - (hsb.b * (slider.height() / 100)), 0, slider.height()); + sliderPicker.css('top', y + 'px'); + + // Update UI + slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 })); + minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (hsb.b / 100)); + break; + + default: + // Set grid position + x = keepWithin(Math.ceil(hsb.s / (100 / grid.width())), 0, grid.width()); + y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height()); + gridPicker.css({ + top: y + 'px', + left: x + 'px' + }); + + // Set slider position + y = keepWithin(slider.height() - (hsb.h / (360 / slider.height())), 0, slider.height()); + sliderPicker.css('top', y + 'px'); + + // Update panel color + grid.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: 100 })); + break; + + } + + // Fire change event, but only if minicolors is fully initialized + if( input.data('minicolors-initialized') ) { + doChange(input, hex, opacity); + } + + } + + // Runs the change and changeDelay callbacks + function doChange(input, hex, opacity) { + + var settings = input.data('minicolors-settings'), + lastChange = input.data('minicolors-lastChange'); + + // Only run if it actually changed + if( !lastChange || lastChange.hex !== hex || lastChange.opacity !== opacity ) { + + // Remember last-changed value + input.data('minicolors-lastChange', { + hex: hex, + opacity: opacity + }); + + // Fire change event + if( settings.change ) { + if( settings.changeDelay ) { + // Call after a delay + clearTimeout(input.data('minicolors-changeTimeout')); + input.data('minicolors-changeTimeout', setTimeout( function() { + settings.change.call(input.get(0), hex, opacity); + }, settings.changeDelay)); + } else { + // Call immediately + settings.change.call(input.get(0), hex, opacity); + } + } + input.trigger('change').trigger('input'); + } + + } + + // Generates an RGB(A) object based on the input's value + function rgbObject(input) { + var hex = parseHex($(input).val(), true), + rgb = hex2rgb(hex), + opacity = $(input).attr('data-opacity'); + if( !rgb ) return null; + if( opacity !== undefined ) $.extend(rgb, { a: parseFloat(opacity) }); + return rgb; + } + + // Genearates an RGB(A) string based on the input's value + function rgbString(input, alpha) { + var hex = parseHex($(input).val(), true), + rgb = hex2rgb(hex), + opacity = $(input).attr('data-opacity'); + if( !rgb ) return null; + if( opacity === undefined ) opacity = 1; + if( alpha ) { + return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')'; + } else { + return 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')'; + } + } + + // Converts to the letter case specified in settings + function convertCase(string, letterCase) { + return letterCase === 'uppercase' ? string.toUpperCase() : string.toLowerCase(); + } + + // Parses a string and returns a valid hex string when possible + function parseHex(string, expand) { + string = string.replace(/[^A-F0-9]/ig, ''); + if( string.length !== 3 && string.length !== 6 ) return ''; + if( string.length === 3 && expand ) { + string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2]; + } + return '#' + string; + } + + // Keeps value within min and max + function keepWithin(value, min, max) { + if( value < min ) value = min; + if( value > max ) value = max; + return value; + } + + // Converts an HSB object to an RGB object + function hsb2rgb(hsb) { + var rgb = {}; + var h = Math.round(hsb.h); + var s = Math.round(hsb.s * 255 / 100); + var v = Math.round(hsb.b * 255 / 100); + if(s === 0) { + rgb.r = rgb.g = rgb.b = v; + } else { + var t1 = v; + var t2 = (255 - s) * v / 255; + var t3 = (t1 - t2) * (h % 60) / 60; + if( h === 360 ) h = 0; + if( h < 60 ) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; } + else if( h < 120 ) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; } + else if( h < 180 ) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; } + else if( h < 240 ) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; } + else if( h < 300 ) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; } + else if( h < 360 ) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; } + else { rgb.r = 0; rgb.g = 0; rgb.b = 0; } + } + return { + r: Math.round(rgb.r), + g: Math.round(rgb.g), + b: Math.round(rgb.b) + }; + } + + // Converts an RGB object to a hex string + function rgb2hex(rgb) { + var hex = [ + rgb.r.toString(16), + rgb.g.toString(16), + rgb.b.toString(16) + ]; + $.each(hex, function(nr, val) { + if (val.length === 1) hex[nr] = '0' + val; + }); + return '#' + hex.join(''); + } + + // Converts an HSB object to a hex string + function hsb2hex(hsb) { + return rgb2hex(hsb2rgb(hsb)); + } + + // Converts a hex string to an HSB object + function hex2hsb(hex) { + var hsb = rgb2hsb(hex2rgb(hex)); + if( hsb.s === 0 ) hsb.h = 360; + return hsb; + } + + // Converts an RGB object to an HSB object + function rgb2hsb(rgb) { + var hsb = { h: 0, s: 0, b: 0 }; + var min = Math.min(rgb.r, rgb.g, rgb.b); + var max = Math.max(rgb.r, rgb.g, rgb.b); + var delta = max - min; + hsb.b = max; + hsb.s = max !== 0 ? 255 * delta / max : 0; + if( hsb.s !== 0 ) { + if( rgb.r === max ) { + hsb.h = (rgb.g - rgb.b) / delta; + } else if( rgb.g === max ) { + hsb.h = 2 + (rgb.b - rgb.r) / delta; + } else { + hsb.h = 4 + (rgb.r - rgb.g) / delta; + } + } else { + hsb.h = -1; + } + hsb.h *= 60; + if( hsb.h < 0 ) { + hsb.h += 360; + } + hsb.s *= 100/255; + hsb.b *= 100/255; + return hsb; + } + + // Converts a hex string to an RGB object + function hex2rgb(hex) { + hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16); + return { + r: hex >> 16, + g: (hex & 0x00FF00) >> 8, + b: (hex & 0x0000FF) + }; + } + + // Handle events + $(document) + // Hide on clicks outside of the control + .on('mousedown.minicolors touchstart.minicolors', function(event) { + if( !$(event.target).parents().add(event.target).hasClass('minicolors') ) { + hide(); + } + }) + // Start moving + .on('mousedown.minicolors touchstart.minicolors', '.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider', function(event) { + var target = $(this); + event.preventDefault(); + $(document).data('minicolors-target', target); + move(target, event, true); + }) + // Move pickers + .on('mousemove.minicolors touchmove.minicolors', function(event) { + var target = $(document).data('minicolors-target'); + if( target ) move(target, event); + }) + // Stop moving + .on('mouseup.minicolors touchend.minicolors', function() { + $(this).removeData('minicolors-target'); + }) + // Show panel when swatch is clicked + .on('mousedown.minicolors touchstart.minicolors', '.minicolors-swatch', function(event) { + var input = $(this).parent().find('.minicolors-input'); + event.preventDefault(); + show(input); + }) + // Show on focus + .on('focus.minicolors', '.minicolors-input', function() { + var input = $(this); + if( !input.data('minicolors-initialized') ) return; + show(input); + }) + // Fix hex on blur + .on('blur.minicolors', '.minicolors-input', function() { + var input = $(this), + settings = input.data('minicolors-settings'); + if( !input.data('minicolors-initialized') ) return; + + // Parse Hex + input.val(parseHex(input.val(), true)); + + // Is it blank? + if( input.val() === '' ) input.val(parseHex(settings.defaultValue, true)); + + // Adjust case + input.val( convertCase(input.val(), settings.letterCase) ); + + }) + // Handle keypresses + .on('keydown.minicolors', '.minicolors-input', function(event) { + var input = $(this); + if( !input.data('minicolors-initialized') ) return; + switch(event.keyCode) { + case 9: // tab + hide(); + break; + case 13: // enter + case 27: // esc + hide(); + input.blur(); + break; + } + }) + // Update on keyup + .on('keyup.minicolors', '.minicolors-input', function() { + var input = $(this); + if( !input.data('minicolors-initialized') ) return; + updateFromInput(input, true); + }) + // Update on paste + .on('paste.minicolors', '.minicolors-input', function() { + var input = $(this); + if( !input.data('minicolors-initialized') ) return; + setTimeout( function() { + updateFromInput(input, true); + }, 1); + }); + +})(jQuery); \ No newline at end of file diff --git a/vendor/assets/components/jquery-minicolors/jquery.minicolors.min.js b/vendor/assets/components/jquery-minicolors/jquery.minicolors.min.js new file mode 100644 index 000000000..b8a83f58b --- /dev/null +++ b/vendor/assets/components/jquery-minicolors/jquery.minicolors.min.js @@ -0,0 +1,9 @@ +/* + * jQuery MiniColors: A tiny color picker built on jQuery + * + * Copyright Cory LaViska for A Beautiful Site, LLC. (http://www.abeautifulsite.net/) + * + * Licensed under the MIT license: http://opensource.org/licenses/MIT + * + */ +if(jQuery)(function($){$.minicolors={defaults:{animationSpeed:50,animationEasing:'swing',change:null,changeDelay:0,control:'hue',dataUris:true,defaultValue:'',hide:null,hideSpeed:100,inline:false,letterCase:'lowercase',opacity:false,position:'bottom left',show:null,showSpeed:100,theme:'default'}};$.extend($.fn,{minicolors:function(method,data){switch(method){case'destroy':$(this).each(function(){destroy($(this))});return $(this);case'hide':hide();return $(this);case'opacity':if(data===undefined){return $(this).attr('data-opacity')}else{$(this).each(function(){updateFromInput($(this).attr('data-opacity',data))})}return $(this);case'rgbObject':return rgbObject($(this),method==='rgbaObject');case'rgbString':case'rgbaString':return rgbString($(this),method==='rgbaString');case'settings':if(data===undefined){return $(this).data('minicolors-settings')}else{$(this).each(function(){var settings=$(this).data('minicolors-settings')||{};destroy($(this));$(this).minicolors($.extend(true,settings,data))})}return $(this);case'show':show($(this).eq(0));return $(this);case'value':if(data===undefined){return $(this).val()}else{$(this).each(function(){updateFromInput($(this).val(data))})}return $(this);default:if(method!=='create')data=method;$(this).each(function(){init($(this),data)});return $(this)}}});function init(input,settings){var minicolors=$('
    '),defaults=$.minicolors.defaults;if(input.data('minicolors-initialized'))return;settings=$.extend(true,{},defaults,settings);minicolors.addClass('minicolors-theme-'+settings.theme).toggleClass('minicolors-with-opacity',settings.opacity).toggleClass('minicolors-no-data-uris',settings.dataUris!==true);if(settings.position!==undefined){$.each(settings.position.split(' '),function(){minicolors.addClass('minicolors-position-'+this)})}input.addClass('minicolors-input').data('minicolors-initialized',false).data('minicolors-settings',settings).prop('size',7).wrap(minicolors).after('
    '+'
    '+'
    '+'
    '+'
    '+'
    '+'
    '+'
    '+'
    '+'
    '+'
    '+'
    ');if(!settings.inline){input.after('');input.next('.minicolors-swatch').on('click',function(event){event.preventDefault();input.focus()})}input.parent().find('.minicolors-panel').on('selectstart',function(){return false}).end();if(settings.inline)input.parent().addClass('minicolors-inline');updateFromInput(input,false);input.data('minicolors-initialized',true)}function destroy(input){var minicolors=input.parent();input.removeData('minicolors-initialized').removeData('minicolors-settings').removeProp('size').removeClass('minicolors-input');minicolors.before(input).remove()}function show(input){var minicolors=input.parent(),panel=minicolors.find('.minicolors-panel'),settings=input.data('minicolors-settings');if(!input.data('minicolors-initialized')||input.prop('disabled')||minicolors.hasClass('minicolors-inline')||minicolors.hasClass('minicolors-focus'))return;hide();minicolors.addClass('minicolors-focus');panel.stop(true,true).fadeIn(settings.showSpeed,function(){if(settings.show)settings.show.call(input.get(0))})}function hide(){$('.minicolors-focus').each(function(){var minicolors=$(this),input=minicolors.find('.minicolors-input'),panel=minicolors.find('.minicolors-panel'),settings=input.data('minicolors-settings');panel.fadeOut(settings.hideSpeed,function(){if(settings.hide)settings.hide.call(input.get(0));minicolors.removeClass('minicolors-focus')})})}function move(target,event,animate){var input=target.parents('.minicolors').find('.minicolors-input'),settings=input.data('minicolors-settings'),picker=target.find('[class$=-picker]'),offsetX=target.offset().left,offsetY=target.offset().top,x=Math.round(event.pageX-offsetX),y=Math.round(event.pageY-offsetY),duration=animate?settings.animationSpeed:0,wx,wy,r,phi;if(event.originalEvent.changedTouches){x=event.originalEvent.changedTouches[0].pageX-offsetX;y=event.originalEvent.changedTouches[0].pageY-offsetY}if(x<0)x=0;if(y<0)y=0;if(x>target.width())x=target.width();if(y>target.height())y=target.height();if(target.parent().is('.minicolors-slider-wheel')&&picker.parent().is('.minicolors-grid')){wx=75-x;wy=75-y;r=Math.sqrt(wx*wx+wy*wy);phi=Math.atan2(wy,wx);if(phi<0)phi+=Math.PI*2;if(r>75){r=75;x=75-(75*Math.cos(phi));y=75-(75*Math.sin(phi))}x=Math.round(x);y=Math.round(y)}if(target.is('.minicolors-grid')){picker.stop(true).animate({top:y+'px',left:x+'px'},duration,settings.animationEasing,function(){updateFromControl(input,target)})}else{picker.stop(true).animate({top:y+'px'},duration,settings.animationEasing,function(){updateFromControl(input,target)})}}function updateFromControl(input,target){function getCoords(picker,container){var left,top;if(!picker.length||!container)return null;left=picker.offset().left;top=picker.offset().top;return{x:left-container.offset().left+(picker.outerWidth()/2),y:top-container.offset().top+(picker.outerHeight()/2)}}var hue,saturation,brightness,x,y,r,phi,hex=input.val(),opacity=input.attr('data-opacity'),minicolors=input.parent(),settings=input.data('minicolors-settings'),swatch=minicolors.find('.minicolors-swatch'),grid=minicolors.find('.minicolors-grid'),slider=minicolors.find('.minicolors-slider'),opacitySlider=minicolors.find('.minicolors-opacity-slider'),gridPicker=grid.find('[class$=-picker]'),sliderPicker=slider.find('[class$=-picker]'),opacityPicker=opacitySlider.find('[class$=-picker]'),gridPos=getCoords(gridPicker,grid),sliderPos=getCoords(sliderPicker,slider),opacityPos=getCoords(opacityPicker,opacitySlider);if(target.is('.minicolors-grid, .minicolors-slider')){switch(settings.control){case'wheel':x=(grid.width()/2)-gridPos.x;y=(grid.height()/2)-gridPos.y;r=Math.sqrt(x*x+y*y);phi=Math.atan2(y,x);if(phi<0)phi+=Math.PI*2;if(r>75){r=75;gridPos.x=69-(75*Math.cos(phi));gridPos.y=69-(75*Math.sin(phi))}saturation=keepWithin(r/0.75,0,100);hue=keepWithin(phi*180/Math.PI,0,360);brightness=keepWithin(100-Math.floor(sliderPos.y*(100/slider.height())),0,100);hex=hsb2hex({h:hue,s:saturation,b:brightness});slider.css('backgroundColor',hsb2hex({h:hue,s:saturation,b:100}));break;case'saturation':hue=keepWithin(parseInt(gridPos.x*(360/grid.width()),10),0,360);saturation=keepWithin(100-Math.floor(sliderPos.y*(100/slider.height())),0,100);brightness=keepWithin(100-Math.floor(gridPos.y*(100/grid.height())),0,100);hex=hsb2hex({h:hue,s:saturation,b:brightness});slider.css('backgroundColor',hsb2hex({h:hue,s:100,b:brightness}));minicolors.find('.minicolors-grid-inner').css('opacity',saturation/100);break;case'brightness':hue=keepWithin(parseInt(gridPos.x*(360/grid.width()),10),0,360);saturation=keepWithin(100-Math.floor(gridPos.y*(100/grid.height())),0,100);brightness=keepWithin(100-Math.floor(sliderPos.y*(100/slider.height())),0,100);hex=hsb2hex({h:hue,s:saturation,b:brightness});slider.css('backgroundColor',hsb2hex({h:hue,s:saturation,b:100}));minicolors.find('.minicolors-grid-inner').css('opacity',1-(brightness/100));break;default:hue=keepWithin(360-parseInt(sliderPos.y*(360/slider.height()),10),0,360);saturation=keepWithin(Math.floor(gridPos.x*(100/grid.width())),0,100);brightness=keepWithin(100-Math.floor(gridPos.y*(100/grid.height())),0,100);hex=hsb2hex({h:hue,s:saturation,b:brightness});grid.css('backgroundColor',hsb2hex({h:hue,s:100,b:100}));break}input.val(convertCase(hex,settings.letterCase))}if(target.is('.minicolors-opacity-slider')){if(settings.opacity){opacity=parseFloat(1-(opacityPos.y/opacitySlider.height())).toFixed(2)}else{opacity=1}if(settings.opacity)input.attr('data-opacity',opacity)}swatch.find('SPAN').css({backgroundColor:hex,opacity:opacity});doChange(input,hex,opacity)}function updateFromInput(input,preserveInputValue){var hex,hsb,opacity,x,y,r,phi,minicolors=input.parent(),settings=input.data('minicolors-settings'),swatch=minicolors.find('.minicolors-swatch'),grid=minicolors.find('.minicolors-grid'),slider=minicolors.find('.minicolors-slider'),opacitySlider=minicolors.find('.minicolors-opacity-slider'),gridPicker=grid.find('[class$=-picker]'),sliderPicker=slider.find('[class$=-picker]'),opacityPicker=opacitySlider.find('[class$=-picker]');hex=convertCase(parseHex(input.val(),true),settings.letterCase);if(!hex){hex=convertCase(parseHex(settings.defaultValue,true),settings.letterCase)}hsb=hex2hsb(hex);if(!preserveInputValue)input.val(hex);if(settings.opacity){opacity=input.attr('data-opacity')===''?1:keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2),0,1);if(isNaN(opacity))opacity=1;input.attr('data-opacity',opacity);swatch.find('SPAN').css('opacity',opacity);y=keepWithin(opacitySlider.height()-(opacitySlider.height()*opacity),0,opacitySlider.height());opacityPicker.css('top',y+'px')}swatch.find('SPAN').css('backgroundColor',hex);switch(settings.control){case'wheel':r=keepWithin(Math.ceil(hsb.s*0.75),0,grid.height()/2);phi=hsb.h*Math.PI/180;x=keepWithin(75-Math.cos(phi)*r,0,grid.width());y=keepWithin(75-Math.sin(phi)*r,0,grid.height());gridPicker.css({top:y+'px',left:x+'px'});y=150-(hsb.b/(100/grid.height()));if(hex==='')y=0;sliderPicker.css('top',y+'px');slider.css('backgroundColor',hsb2hex({h:hsb.h,s:hsb.s,b:100}));break;case'saturation':x=keepWithin((5*hsb.h)/12,0,150);y=keepWithin(grid.height()-Math.ceil(hsb.b/(100/grid.height())),0,grid.height());gridPicker.css({top:y+'px',left:x+'px'});y=keepWithin(slider.height()-(hsb.s*(slider.height()/100)),0,slider.height());sliderPicker.css('top',y+'px');slider.css('backgroundColor',hsb2hex({h:hsb.h,s:100,b:hsb.b}));minicolors.find('.minicolors-grid-inner').css('opacity',hsb.s/100);break;case'brightness':x=keepWithin((5*hsb.h)/12,0,150);y=keepWithin(grid.height()-Math.ceil(hsb.s/(100/grid.height())),0,grid.height());gridPicker.css({top:y+'px',left:x+'px'});y=keepWithin(slider.height()-(hsb.b*(slider.height()/100)),0,slider.height());sliderPicker.css('top',y+'px');slider.css('backgroundColor',hsb2hex({h:hsb.h,s:hsb.s,b:100}));minicolors.find('.minicolors-grid-inner').css('opacity',1-(hsb.b/100));break;default:x=keepWithin(Math.ceil(hsb.s/(100/grid.width())),0,grid.width());y=keepWithin(grid.height()-Math.ceil(hsb.b/(100/grid.height())),0,grid.height());gridPicker.css({top:y+'px',left:x+'px'});y=keepWithin(slider.height()-(hsb.h/(360/slider.height())),0,slider.height());sliderPicker.css('top',y+'px');grid.css('backgroundColor',hsb2hex({h:hsb.h,s:100,b:100}));break}if(input.data('minicolors-initialized')){doChange(input,hex,opacity)}}function doChange(input,hex,opacity){var settings=input.data('minicolors-settings'),lastChange=input.data('minicolors-lastChange');if(!lastChange||lastChange.hex!==hex||lastChange.opacity!==opacity){input.data('minicolors-lastChange',{hex:hex,opacity:opacity});if(settings.change){if(settings.changeDelay){clearTimeout(input.data('minicolors-changeTimeout'));input.data('minicolors-changeTimeout',setTimeout(function(){settings.change.call(input.get(0),hex,opacity)},settings.changeDelay))}else{settings.change.call(input.get(0),hex,opacity)}}input.trigger('change').trigger('input')}}function rgbObject(input){var hex=parseHex($(input).val(),true),rgb=hex2rgb(hex),opacity=$(input).attr('data-opacity');if(!rgb)return null;if(opacity!==undefined)$.extend(rgb,{a:parseFloat(opacity)});return rgb}function rgbString(input,alpha){var hex=parseHex($(input).val(),true),rgb=hex2rgb(hex),opacity=$(input).attr('data-opacity');if(!rgb)return null;if(opacity===undefined)opacity=1;if(alpha){return'rgba('+rgb.r+', '+rgb.g+', '+rgb.b+', '+parseFloat(opacity)+')'}else{return'rgb('+rgb.r+', '+rgb.g+', '+rgb.b+')'}}function convertCase(string,letterCase){return letterCase==='uppercase'?string.toUpperCase():string.toLowerCase()}function parseHex(string,expand){string=string.replace(/[^A-F0-9]/ig,'');if(string.length!==3&&string.length!==6)return'';if(string.length===3&&expand){string=string[0]+string[0]+string[1]+string[1]+string[2]+string[2]}return'#'+string}function keepWithin(value,min,max){if(valuemax)value=max;return value}function hsb2rgb(hsb){var rgb={};var h=Math.round(hsb.h);var s=Math.round(hsb.s*255/100);var v=Math.round(hsb.b*255/100);if(s===0){rgb.r=rgb.g=rgb.b=v}else{var t1=v;var t2=(255-s)*v/255;var t3=(t1-t2)*(h%60)/60;if(h===360)h=0;if(h<60){rgb.r=t1;rgb.b=t2;rgb.g=t2+t3}else if(h<120){rgb.g=t1;rgb.b=t2;rgb.r=t1-t3}else if(h<180){rgb.g=t1;rgb.r=t2;rgb.b=t2+t3}else if(h<240){rgb.b=t1;rgb.r=t2;rgb.g=t1-t3}else if(h<300){rgb.b=t1;rgb.g=t2;rgb.r=t2+t3}else if(h<360){rgb.r=t1;rgb.g=t2;rgb.b=t1-t3}else{rgb.r=0;rgb.g=0;rgb.b=0}}return{r:Math.round(rgb.r),g:Math.round(rgb.g),b:Math.round(rgb.b)}}function rgb2hex(rgb){var hex=[rgb.r.toString(16),rgb.g.toString(16),rgb.b.toString(16)];$.each(hex,function(nr,val){if(val.length===1)hex[nr]='0'+val});return'#'+hex.join('')}function hsb2hex(hsb){return rgb2hex(hsb2rgb(hsb))}function hex2hsb(hex){var hsb=rgb2hsb(hex2rgb(hex));if(hsb.s===0)hsb.h=360;return hsb}function rgb2hsb(rgb){var hsb={h:0,s:0,b:0};var min=Math.min(rgb.r,rgb.g,rgb.b);var max=Math.max(rgb.r,rgb.g,rgb.b);var delta=max-min;hsb.b=max;hsb.s=max!==0?255*delta/max:0;if(hsb.s!==0){if(rgb.r===max){hsb.h=(rgb.g-rgb.b)/delta}else if(rgb.g===max){hsb.h=2+(rgb.b-rgb.r)/delta}else{hsb.h=4+(rgb.r-rgb.g)/delta}}else{hsb.h=-1}hsb.h*=60;if(hsb.h<0){hsb.h+=360}hsb.s*=100/255;hsb.b*=100/255;return hsb}function hex2rgb(hex){hex=parseInt(((hex.indexOf('#')>-1)?hex.substring(1):hex),16);return{r:hex>>16,g:(hex&0x00FF00)>>8,b:(hex&0x0000FF)}}$(document).on('mousedown.minicolors touchstart.minicolors',function(event){if(!$(event.target).parents().add(event.target).hasClass('minicolors')){hide()}}).on('mousedown.minicolors touchstart.minicolors','.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider',function(event){var target=$(this);event.preventDefault();$(document).data('minicolors-target',target);move(target,event,true)}).on('mousemove.minicolors touchmove.minicolors',function(event){var target=$(document).data('minicolors-target');if(target)move(target,event)}).on('mouseup.minicolors touchend.minicolors',function(){$(this).removeData('minicolors-target')}).on('mousedown.minicolors touchstart.minicolors','.minicolors-swatch',function(event){var input=$(this).parent().find('.minicolors-input');event.preventDefault();show(input)}).on('focus.minicolors','.minicolors-input',function(){var input=$(this);if(!input.data('minicolors-initialized'))return;show(input)}).on('blur.minicolors','.minicolors-input',function(){var input=$(this),settings=input.data('minicolors-settings');if(!input.data('minicolors-initialized'))return;input.val(parseHex(input.val(),true));if(input.val()==='')input.val(parseHex(settings.defaultValue,true));input.val(convertCase(input.val(),settings.letterCase))}).on('keydown.minicolors','.minicolors-input',function(event){var input=$(this);if(!input.data('minicolors-initialized'))return;switch(event.keyCode){case 9:hide();break;case 13:case 27:hide();input.blur();break}}).on('keyup.minicolors','.minicolors-input',function(){var input=$(this);if(!input.data('minicolors-initialized'))return;updateFromInput(input,true)}).on('paste.minicolors','.minicolors-input',function(){var input=$(this);if(!input.data('minicolors-initialized'))return;setTimeout(function(){updateFromInput(input,true)},1)})})(jQuery); \ No newline at end of file diff --git a/vendor/assets/components/jquery-minicolors/jquery.minicolors.png b/vendor/assets/components/jquery-minicolors/jquery.minicolors.png new file mode 100644 index 0000000000000000000000000000000000000000..8fa1e9d9062696b96641450a0b0dcc8669835782 GIT binary patch literal 77459 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 JPDHLkV1o8UQl|g_ literal 0 HcmV?d00001 diff --git a/vendor/assets/components/jquery-minicolors/readme.md b/vendor/assets/components/jquery-minicolors/readme.md new file mode 100755 index 000000000..cc39d0577 --- /dev/null +++ b/vendor/assets/components/jquery-minicolors/readme.md @@ -0,0 +1,9 @@ +# jQuery MiniColors: A tiny color picker built on jQuery + +_Copyright Cory LaViska for A Beautiful Site, LLC. (http://www.abeautifulsite.net/)_ + +_Licensed under the MIT license_ + +## Demo & Documentation + +http://labs.abeautifulsite.net/jquery-minicolors/ \ No newline at end of file diff --git a/vendor/assets/components/jquery-minicolors/without-bootstrap.html b/vendor/assets/components/jquery-minicolors/without-bootstrap.html new file mode 100644 index 000000000..1080da96e --- /dev/null +++ b/vendor/assets/components/jquery-minicolors/without-bootstrap.html @@ -0,0 +1,154 @@ + + + + jQuery MiniColors + + + + + + + + + + + + + + + +

    MiniColors Demo (without Bootstrap)

    +

    + « Back to the Bootstrap demo +

    + + +

    Control Types

    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + + +

    Input Modes

    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    + + +

    Positions

    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + + +

    …and more!

    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    + + + \ No newline at end of file diff --git a/vendor/assets/components/jquery/.bower.json b/vendor/assets/components/jquery/.bower.json index 13561dd95..106e5daa1 100644 --- a/vendor/assets/components/jquery/.bower.json +++ b/vendor/assets/components/jquery/.bower.json @@ -1,11 +1,12 @@ { "name": "jquery", - "version": "2.1.3", + "version": "2.1.4", "main": "dist/jquery.js", "license": "MIT", "ignore": [ "**/.*", "build", + "dist/cdn", "speed", "test", "*.md", @@ -25,13 +26,13 @@ "library" ], "homepage": "https://github.com/jquery/jquery", - "_release": "2.1.3", + "_release": "2.1.4", "_resolution": { "type": "version", - "tag": "2.1.3", - "commit": "8f2a9d9272d6ed7f32d3a484740ab342c02541e0" + "tag": "2.1.4", + "commit": "7751e69b615c6eca6f783a81e292a55725af6b85" }, "_source": "git://github.com/jquery/jquery.git", - "_target": ">=2.0.0", + "_target": ">=1.7.1", "_originalSource": "jquery" } \ No newline at end of file diff --git a/vendor/assets/components/jquery/bower.json b/vendor/assets/components/jquery/bower.json index 61c948729..0c80cd53a 100644 --- a/vendor/assets/components/jquery/bower.json +++ b/vendor/assets/components/jquery/bower.json @@ -1,11 +1,12 @@ { "name": "jquery", - "version": "2.1.3", + "version": "2.1.4", "main": "dist/jquery.js", "license": "MIT", "ignore": [ "**/.*", "build", + "dist/cdn", "speed", "test", "*.md", diff --git a/vendor/assets/components/jquery/dist/jquery.js b/vendor/assets/components/jquery/dist/jquery.js index 79d631ff4..eed17778c 100644 --- a/vendor/assets/components/jquery/dist/jquery.js +++ b/vendor/assets/components/jquery/dist/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v2.1.3 + * jQuery JavaScript Library v2.1.4 * http://jquery.com/ * * Includes Sizzle.js @@ -9,7 +9,7 @@ * Released under the MIT license * http://jquery.org/license * - * Date: 2014-12-18T15:11Z + * Date: 2015-04-28T16:01Z */ (function( global, factory ) { @@ -67,7 +67,7 @@ var // Use the correct document accordingly with window argument (sandbox) document = window.document, - version = "2.1.3", + version = "2.1.4", // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -531,7 +531,12 @@ jQuery.each("Boolean Number String Function Array Date RegExp Object Error".spli }); function isArraylike( obj ) { - var length = obj.length, + + // Support: iOS 8.2 (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = "length" in obj && obj.length, type = jQuery.type( obj ); if ( type === "function" || jQuery.isWindow( obj ) ) { diff --git a/vendor/assets/components/jquery/dist/jquery.min.js b/vendor/assets/components/jquery/dist/jquery.min.js index 18bdbed7f..fad9ab123 100644 --- a/vendor/assets/components/jquery/dist/jquery.min.js +++ b/vendor/assets/components/jquery/dist/jquery.min.js @@ -1,5 +1,5 @@ -/*! jQuery v2.1.3 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.3",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=hb(),z=hb(),A=hb(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},eb=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fb){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function gb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+rb(o[l]);w=ab.test(a)&&pb(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function hb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ib(a){return a[u]=!0,a}function jb(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function kb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function lb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function nb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function ob(a){return ib(function(b){return b=+b,ib(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pb(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=gb.support={},f=gb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=gb.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",eb,!1):e.attachEvent&&e.attachEvent("onunload",eb)),p=!f(g),c.attributes=jb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=jb(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=jb(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(jb(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),jb(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&jb(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return lb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?lb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},gb.matches=function(a,b){return gb(a,null,null,b)},gb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return gb(b,n,null,[a]).length>0},gb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},gb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},gb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},gb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=gb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=gb.selectors={cacheLength:50,createPseudo:ib,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||gb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&gb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=gb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||gb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ib(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ib(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ib(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ib(function(a){return function(b){return gb(a,b).length>0}}),contains:ib(function(a){return a=a.replace(cb,db),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ib(function(a){return W.test(a||"")||gb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:ob(function(){return[0]}),last:ob(function(a,b){return[b-1]}),eq:ob(function(a,b,c){return[0>c?c+b:c]}),even:ob(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:ob(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:ob(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:ob(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function tb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ub(a,b,c){for(var d=0,e=b.length;e>d;d++)gb(a,b[d],c);return c}function vb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wb(a,b,c,d,e,f){return d&&!d[u]&&(d=wb(d)),e&&!e[u]&&(e=wb(e,f)),ib(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ub(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:vb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=vb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=vb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sb(function(a){return a===b},h,!0),l=sb(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sb(tb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wb(i>1&&tb(m),i>1&&rb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xb(a.slice(i,e)),f>e&&xb(a=a.slice(e)),f>e&&rb(a))}m.push(c)}return tb(m)}function yb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=vb(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&gb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ib(f):f}return h=gb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,yb(e,d)),f.selector=a}return f},i=gb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&pb(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&rb(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&pb(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=jb(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),jb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||kb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&jb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||kb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),jb(function(a){return null==a.getAttribute("disabled")})||kb(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),gb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c) -},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n(" -

    Air Mode - Bootstrap v3.1.1 - font-awesome v4.0.3 - CodeMirror v3.20.0 -

    - -

    Summernote with Form submit. - Bootstrap v3.0.1 - font-awesome v4.0.3 - Textarea -

    - -

    Right to left - Bootstrap v3.0.1 - font-awesome v4.0.3 -

    - -

    Lang (ko-KR) - Bootstrap v3.0.1 - font-awesome v4.0.3 -

    - -

    Plugin - Bootstrap v3.0.3 - font-awesome v4.0.3 -

    - -

    Old library - Bootstrap v2.3.1 - font-awesome v3.1.1 -

    - -

    Old library 2 - Bootstrap v2.3.1 - font-awesome v4.0.3 -

    - -
    - - diff --git a/vendor/assets/components/summernote/examples/jquery-custom-event.html b/vendor/assets/components/summernote/examples/jquery-custom-event.html index 9ff27a433..a6f8efc35 100644 --- a/vendor/assets/components/summernote/examples/jquery-custom-event.html +++ b/vendor/assets/components/summernote/examples/jquery-custom-event.html @@ -3,7 +3,7 @@ jquery old - + @@ -32,21 +32,16 @@ diff --git a/vendor/assets/components/summernote/examples/lang.html b/vendor/assets/components/summernote/examples/lang.html index 54786d8dc..6866b69c2 100644 --- a/vendor/assets/components/summernote/examples/lang.html +++ b/vendor/assets/components/summernote/examples/lang.html @@ -5,7 +5,7 @@ summernote - + diff --git a/vendor/assets/components/summernote/examples/nativestyle.html b/vendor/assets/components/summernote/examples/nativestyle.html index 9758a75b3..73b29d5a4 100644 --- a/vendor/assets/components/summernote/examples/nativestyle.html +++ b/vendor/assets/components/summernote/examples/nativestyle.html @@ -5,7 +5,7 @@ summernote - + diff --git a/vendor/assets/components/summernote/examples/ondialog-multitab.html b/vendor/assets/components/summernote/examples/ondialog-multitab.html index f3fa92256..25d6d3fd5 100644 --- a/vendor/assets/components/summernote/examples/ondialog-multitab.html +++ b/vendor/assets/components/summernote/examples/ondialog-multitab.html @@ -6,7 +6,7 @@ summernote - + @@ -25,10 +25,10 @@ $('.dropping').destroy(); }); - $('.nav.nav-tabs a').click(function (e) { - e.preventDefault() - $(this).tab('show') - }) + $('.nav.nav-tabs a').click(function (e) { + e.preventDefault() + $(this).tab('show') + }) }); @@ -38,33 +38,26 @@ diff --git a/vendor/assets/components/summernote/examples/plugin-fontstyle.html b/vendor/assets/components/summernote/examples/plugin-fontstyle.html deleted file mode 100644 index 225d435a0..000000000 --- a/vendor/assets/components/summernote/examples/plugin-fontstyle.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - summernote - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vendor/assets/components/summernote/examples/plugin-hello.html b/vendor/assets/components/summernote/examples/plugin-hello.html index 186585196..71595cc2c 100644 --- a/vendor/assets/components/summernote/examples/plugin-hello.html +++ b/vendor/assets/components/summernote/examples/plugin-hello.html @@ -5,46 +5,33 @@ summernote - + - - - - - - - - - + - + diff --git a/vendor/assets/components/summernote/examples/plugin-video.html b/vendor/assets/components/summernote/examples/plugin-video.html deleted file mode 100644 index 1a49c9f4b..000000000 --- a/vendor/assets/components/summernote/examples/plugin-video.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - summernote - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vendor/assets/components/summernote/examples/rtl.html b/vendor/assets/components/summernote/examples/rtl.html index ad281a3b5..c3bf399a7 100644 --- a/vendor/assets/components/summernote/examples/rtl.html +++ b/vendor/assets/components/summernote/examples/rtl.html @@ -5,7 +5,7 @@ summernote - + diff --git a/vendor/assets/components/summernote/examples/textarea.html b/vendor/assets/components/summernote/examples/textarea.html index c35260959..32efbb4a4 100644 --- a/vendor/assets/components/summernote/examples/textarea.html +++ b/vendor/assets/components/summernote/examples/textarea.html @@ -5,7 +5,7 @@ summernote - + @@ -24,14 +24,22 @@ $('form').on('submit', function (e) { e.preventDefault(); - alert($('.summernote').code()); + alert($('.summernote').summernote('code')); + alert($('.summernote').val()); }); }); -
    - + +
    + + +
    +
    + + +
    diff --git a/vendor/assets/components/summernote/grunts/grunt-build.js b/vendor/assets/components/summernote/grunts/grunt-build.js deleted file mode 100644 index ef7264471..000000000 --- a/vendor/assets/components/summernote/grunts/grunt-build.js +++ /dev/null @@ -1,78 +0,0 @@ -module.exports = function (grunt) { - 'use strict'; - - var requirejs = require('requirejs'); - var path = require('path'); - - var rDefineStart = /define\([^{]*?{/; - var rDefineEndWithReturn = /\s*return\s+[^\}]+(\}\);[^\w\}]*)$/; - var rDefineEnd = /\}\);[^}\w]*$/; - - grunt.registerMultiTask('build', 'concatenate source: summernote.js', function () { - - /** - * Strip all definitions generated by requirejs - * - * @param {String} name - * @param {String} path - * @param {String} contents The contents to be written (including their AMD wrappers) - */ - var convert = function (name, path, contents) { - contents = contents.replace(rDefineStart, ''); - - if (rDefineEndWithReturn.test(contents)) { - contents = contents.replace(rDefineEndWithReturn, ''); - } else { - contents = contents.replace(rDefineEnd, ''); - } - return contents; - }; - - var outputPath = this.data.outFile; - /** - * Handle final output from the optimizer - */ - var out = function (compiled) { - // 01. Embed version - var version = grunt.config('pkg.version'); - compiled = compiled.replace(/@VERSION/g, version); - - // 02. Embed Date - var date = (new Date()).toISOString().replace(/:\d+\.\d+Z$/, 'Z'); - compiled = compiled.replace(/@DATE/g, date); - - grunt.file.write(outputPath, compiled); - }; - - var config = { - name: 'summernote/summernote', - baseUrl: this.data.baseUrl, - out: out, - optimize: 'none', - wrap: { - startFile: path.join(this.data.baseUrl, this.data.startFile), - endFile: path.join(this.data.baseUrl, this.data.endFile) - }, - findNestedDependencies: true, - skipSemiColonInsertion: true, - onBuildWrite: convert, - excludeShallow: ['jquery', 'CodeMirror', 'app'], - paths: { - jquery: 'empty:', - CodeMirror: 'empty:' - }, - packages: [{ - name: 'summernote', - location: './', - main: 'summernote' - }] - }; - - var done = this.async(); - requirejs.optimize(config, function () { - done(); - }, function (err) { - done(err); - }); - }); -}; diff --git a/vendor/assets/components/summernote/ie8.html b/vendor/assets/components/summernote/ie8.html new file mode 100644 index 000000000..460781795 --- /dev/null +++ b/vendor/assets/components/summernote/ie8.html @@ -0,0 +1,23 @@ + + + + + + + + + summernote + + +
    +

    Summernote + Bootstrap v3.3.5 + font-awesome v4.3.0 +

    + +

    Hello World

    +
    + + + + diff --git a/vendor/assets/components/summernote/index.html b/vendor/assets/components/summernote/index.html index 677a8d173..29b9a440f 100644 --- a/vendor/assets/components/summernote/index.html +++ b/vendor/assets/components/summernote/index.html @@ -3,44 +3,19 @@ - summernote - - - - - - - - - - - + + + + summernote -
    -

    Lately library - Bootstrap v3.1.1 - font-awesome v4.0.3 - CodeMirror v3.20.0 -

    -
    -
    - - +
    +

    Summernote + Bootstrap v3.3.5 + font-awesome v4.3.0 +

    +

    Hello World

    +
    + diff --git a/vendor/assets/components/summernote/lang/summernote-ar-AR.js b/vendor/assets/components/summernote/lang/summernote-ar-AR.js index 3bf1959a0..1b4919f35 100644 --- a/vendor/assets/components/summernote/lang/summernote-ar-AR.js +++ b/vendor/assets/components/summernote/lang/summernote-ar-AR.js @@ -25,6 +25,13 @@ url: 'رابط الصورة', remove: 'حذف الصورة' }, + video: { + video: 'فيديو', + videoLink: 'رابط الفيديو', + insert: 'إدراج الفيديو', + url: 'رابط الفيديو', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion ou Youku)' + }, link: { link: 'رابط رابط', insert: 'إدراج', diff --git a/vendor/assets/components/summernote/lang/summernote-bg-BG.js b/vendor/assets/components/summernote/lang/summernote-bg-BG.js new file mode 100644 index 000000000..2370c143a --- /dev/null +++ b/vendor/assets/components/summernote/lang/summernote-bg-BG.js @@ -0,0 +1,99 @@ +(function ($) { + $.extend($.summernote.lang, { + 'bg-BG': { + font: { + bold: 'Удебелен', + italic: 'Наклонен', + underline: 'Подчертан', + clear: 'Изчисти стиловете', + height: 'Височина', + name: 'Шрифт', + strikethrough: 'Задраскано', + subscript: 'Долен индекс', + superscript: 'Горен индекс', + size: 'Размер на шрифта' + }, + image: { + image: 'Изображение', + insert: 'Постави картинка', + resizeFull: 'Цял размер', + resizeHalf: 'Размер на 50%', + resizeQuarter: 'Размер на 25%', + floatLeft: 'Подравни в ляво', + floatRight: 'Подравни в дясно', + floatNone: 'Без подравняване', + dragImageHere: 'Пуснете изображението тук', + selectFromFiles: 'Изберете файл', + url: 'URL адрес на изображение', + remove: 'Премахни изображение' + }, + link: { + link: 'Връзка', + insert: 'Добави връзка', + unlink: 'Премахни връзка', + edit: 'Промени', + textToDisplay: 'Текст за показване', + url: 'URL адрес', + openInNewWindow: 'Отвори в нов прозорец' + }, + table: { + table: 'Таблица' + }, + hr: { + insert: 'Добави хоризонтална линия' + }, + style: { + style: 'Стил', + normal: 'Нормален', + blockquote: 'Цитат', + pre: 'Код', + h1: 'Заглавие 1', + h2: 'Заглавие 2', + h3: 'Заглавие 3', + h4: 'Заглавие 4', + h5: 'Заглавие 5', + h6: 'Заглавие 6' + }, + lists: { + unordered: 'Символен списък', + ordered: 'Цифров списък' + }, + options: { + help: 'Помощ', + fullscreen: 'На цял екран', + codeview: 'Преглед на код' + }, + paragraph: { + paragraph: 'Параграф', + outdent: 'Намаляване на отстъпа', + indent: 'Абзац', + left: 'Подравняване в ляво', + center: 'Център', + right: 'Подравняване в дясно', + justify: 'Разтягане по ширина' + }, + color: { + recent: 'Последния избран цвят', + more: 'Още цветове', + background: 'Цвят на фона', + foreground: 'Цвят на шрифта', + transparent: 'Прозрачен', + setTransparent: 'Направете прозрачен', + reset: 'Възстанови', + resetToDefault: 'Възстанови оригиналните' + }, + shortcut: { + shortcuts: 'Клавишни комбинации', + close: 'Затвори', + textFormatting: 'Форматиране на текста', + action: 'Действие', + paragraphFormatting: 'Форматиране на параграф', + documentStyle: 'Стил на документа' + }, + history: { + undo: 'Назад', + redo: 'Напред' + } + } + }); +})(jQuery); diff --git a/vendor/assets/components/summernote/lang/summernote-ca-ES.js b/vendor/assets/components/summernote/lang/summernote-ca-ES.js index b39b1d1e8..bab1b56b3 100644 --- a/vendor/assets/components/summernote/lang/summernote-ca-ES.js +++ b/vendor/assets/components/summernote/lang/summernote-ca-ES.js @@ -7,7 +7,10 @@ underline: 'Subratllat', clear: 'Treure estil de lletra', height: 'Alçada de línia', + name: 'Font', strikethrough: 'Ratllat', + subscript: 'Subíndex', + superscript: 'Superíndex', size: 'Mida de lletra' }, image: { @@ -16,12 +19,27 @@ resizeFull: 'Redimensionar a mida completa', resizeHalf: 'Redimensionar a la meitat', resizeQuarter: 'Redimensionar a un quart', - floatLeft: 'Surar a l%27esquerra', - floatRight: 'Surar a la dreta', - floatNone: 'No surar', - dragImageHere: 'Arrossegueu una imatge aquí', + floatLeft: 'Alinear a l\'esquerra', + floatRight: 'Alinear a la dreta', + floatNone: 'No alinear', + shapeRounded: 'Forma: Arrodonit', + shapeCircle: 'Forma: Cercle', + shapeThumbnail: 'Forma: Marc', + shapeNone: 'Forma: Cap', + dragImageHere: 'Arrossegueu una imatge o text aquí', + dropImage: 'Deixa anar aquí una imatge o un text', selectFromFiles: 'Seleccioneu des dels arxius', - url: 'URL de la imatge' + maximumFileSize: 'Mida màxima de l\'arxiu', + maximumFileSizeError: 'La mida màxima de l\'arxiu s\'ha superat.', + url: 'URL de la imatge', + remove: 'Eliminar imatge' + }, + video: { + video: 'Vídeo', + videoLink: 'Enllaç del vídeo', + insert: 'Inserir vídeo', + url: 'URL del vídeo?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, o Youku)' }, link: { link: 'Enllaç', @@ -84,12 +102,46 @@ textFormatting: 'Format de text', action: 'Acció', paragraphFormatting: 'Format de paràgraf', - documentStyle: 'Estil del document' + documentStyle: 'Estil del document', + extraKeys: 'Tecles adicionals' + }, + help : { + 'insertParagraph': 'Inserir paràgraf', + 'undo': 'Desfer l\'última acció', + 'redo': 'Refer l\'última acció', + 'tab': 'Tabular', + 'untab': 'Eliminar tabulació', + 'bold': 'Establir estil negreta', + 'italic': 'Establir estil cursiva', + 'underline': 'Establir estil subratllat', + 'strikethrough': 'Establir estil ratllat', + 'removeFormat': 'Netejar estil', + 'justifyLeft': 'Alinear a l\'esquerra', + 'justifyCenter': 'Alinear al centre', + 'justifyRight': 'Alinear a la dreta', + 'justifyFull': 'Justificar', + 'insertUnorderedList': 'Inserir llista desendreçada', + 'insertOrderedList': 'Inserir llista endreçada', + 'outdent': 'Reduïr tabulació del paràgraf', + 'indent': 'Augmentar tabulació del paràgraf', + 'formatPara': 'Canviar l\'estil del bloc com a un paràgraf (etiqueta P)', + 'formatH1': 'Canviar l\'estil del bloc com a un H1', + 'formatH2': 'Canviar l\'estil del bloc com a un H2', + 'formatH3': 'Canviar l\'estil del bloc com a un H3', + 'formatH4': 'Canviar l\'estil del bloc com a un H4', + 'formatH5': 'Canviar l\'estil del bloc com a un H5', + 'formatH6': 'Canviar l\'estil del bloc com a un H6', + 'insertHorizontalRule': 'Inserir una línia horitzontal', + 'linkDialog.show': 'Mostrar panel d\'enllaços' }, history: { undo: 'Desfer', redo: 'Refer' + }, + specialChar: { + specialChar: 'CARÀCTERS ESPECIALS', + select: 'Selecciona caràcters especials' } } }); -})(jQuery); +})(jQuery); \ No newline at end of file diff --git a/vendor/assets/components/summernote/lang/summernote-cs-CZ.js b/vendor/assets/components/summernote/lang/summernote-cs-CZ.js index d4c654f89..65b20211a 100644 --- a/vendor/assets/components/summernote/lang/summernote-cs-CZ.js +++ b/vendor/assets/components/summernote/lang/summernote-cs-CZ.js @@ -23,6 +23,13 @@ selectFromFiles: 'Vybrat soubor', url: 'URL obrázku' }, + video: { + video: 'Video', + videoLink: 'Odkaz videa', + insert: 'Vložit video', + url: 'URL videa?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion nebo Youku)' + }, link: { link: 'Odkaz', insert: 'Vytvořit odkaz', diff --git a/vendor/assets/components/summernote/lang/summernote-da-DK.js b/vendor/assets/components/summernote/lang/summernote-da-DK.js index 14be381fa..b7d4689f9 100644 --- a/vendor/assets/components/summernote/lang/summernote-da-DK.js +++ b/vendor/assets/components/summernote/lang/summernote-da-DK.js @@ -34,6 +34,13 @@ url: 'Billede URL', remove: 'Fjern billede' }, + video: { + video: 'Video', + videoLink: 'Video Link', + insert: 'Indsæt Video', + url: 'Video URL?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion eller Youku)' + }, link: { link: 'Link', insert: 'Indsæt link', diff --git a/vendor/assets/components/summernote/lang/summernote-de-DE.js b/vendor/assets/components/summernote/lang/summernote-de-DE.js index 8e18c0811..e2248d0c5 100644 --- a/vendor/assets/components/summernote/lang/summernote-de-DE.js +++ b/vendor/assets/components/summernote/lang/summernote-de-DE.js @@ -19,11 +19,24 @@ floatLeft: 'Linksbündig', floatRight: 'Rechtsbündig', floatNone: 'Kein Textfluss', + shapeRounded: 'Rahmen: Abgerundet', + shapeCircle: 'Rahmen: Kreisförmig', + shapeThumbnail: 'Rahmen: Thumbnail', + shapeNone: 'Kein Rahmen', dragImageHere: 'Ziehen Sie ein Bild mit der Maus hierher', selectFromFiles: 'Wählen Sie eine Datei aus', + maximumFileSize: 'Maximale Dateigröße', + maximumFileSizeError: 'Maximale Dateigröße überschritten', url: 'Grafik URL', remove: 'Grafik entfernen' }, + video: { + video: 'Video', + videoLink: 'Video Link', + insert: 'Video einfügen', + url: 'Video URL?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, oder Youku)' + }, link: { link: 'Link', insert: 'Link einfügen', diff --git a/vendor/assets/components/summernote/lang/summernote-es-ES.js b/vendor/assets/components/summernote/lang/summernote-es-ES.js index e065e2437..80304f86d 100644 --- a/vendor/assets/components/summernote/lang/summernote-es-ES.js +++ b/vendor/assets/components/summernote/lang/summernote-es-ES.js @@ -22,9 +22,24 @@ floatLeft: 'Flotar a la izquierda', floatRight: 'Flotar a la derecha', floatNone: 'No flotar', - dragImageHere: 'Arrastrar una imagen aquí', + shapeRounded: 'Forma: Redondeado', + shapeCircle: 'Forma: Círculo', + shapeThumbnail: 'Forma: Marco', + shapeNone: 'Forma: Ninguna', + dragImageHere: 'Arrastrar una imagen o texto aquí', + dropImage: 'Suelta la imagen o texto', selectFromFiles: 'Seleccionar desde los archivos', - url: 'URL de la imagen' + maximumFileSize: 'Tamaño máximo del archivo', + maximumFileSizeError: 'Has superado el tamaño máximo del archivo.', + url: 'URL de la imagen', + remove: 'Eliminar imagen' + }, + video: { + video: 'Vídeo', + videoLink: 'Link del vídeo', + insert: 'Insertar vídeo', + url: '¿URL del vídeo?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, o Youku)' }, link: { link: 'Link', @@ -87,12 +102,46 @@ textFormatting: 'Formato de texto', action: 'Acción', paragraphFormatting: 'Formato de párrafo', - documentStyle: 'Estilo de documento' + documentStyle: 'Estilo de documento', + extraKeys: 'Teclas adicionales' + }, + help : { + 'insertParagraph': 'Insertar párrafo', + 'undo': 'Deshacer última acción', + 'redo': 'Rehacer última acción', + 'tab': 'Tabular', + 'untab': 'Eliminar tabulación', + 'bold': 'Establecer estilo negrita', + 'italic': 'Establecer estilo cursiva', + 'underline': 'Establecer estilo subrayado', + 'strikethrough': 'Establecer estilo tachado', + 'removeFormat': 'Limpiar estilo', + 'justifyLeft': 'Alinear a la izquierda', + 'justifyCenter': 'Alinear al centro', + 'justifyRight': 'Alinear a la derecha', + 'justifyFull': 'Justificar', + 'insertUnorderedList': 'Insertar lista desordenada', + 'insertOrderedList': 'Insertar lista ordenada', + 'outdent': 'Reducir tabulación del párrafo', + 'indent': 'Aumentar tabulación del párrafo', + 'formatPara': 'Cambiar estilo del bloque a párrafo (etiqueta P)', + 'formatH1': 'Cambiar estilo del bloque a H1', + 'formatH2': 'Cambiar estilo del bloque a H2', + 'formatH3': 'Cambiar estilo del bloque a H3', + 'formatH4': 'Cambiar estilo del bloque a H4', + 'formatH5': 'Cambiar estilo del bloque a H5', + 'formatH6': 'Cambiar estilo del bloque a H6', + 'insertHorizontalRule': 'Insertar línea horizontal', + 'linkDialog.show': 'Mostrar panel enlaces' }, history: { undo: 'Deshacer', redo: 'Rehacer' + }, + specialChar: { + specialChar: 'CARACTERES ESPECIALES', + select: 'Selecciona Caracteres especiales' } } }); -})(jQuery); +})(jQuery); \ No newline at end of file diff --git a/vendor/assets/components/summernote/lang/summernote-es-EU.js b/vendor/assets/components/summernote/lang/summernote-es-EU.js index ccdd9f519..e8b7b2f35 100644 --- a/vendor/assets/components/summernote/lang/summernote-es-EU.js +++ b/vendor/assets/components/summernote/lang/summernote-es-EU.js @@ -24,6 +24,13 @@ selectFromFiles: 'Zure fitxategi bat aukeratu', url: 'Irudiaren URL helbidea' }, + video: { + video: 'Bideoa', + videoLink: 'Bideorako esteka', + insert: 'Bideo berri bat txertatu', + url: 'Bideoaren URL helbidea', + providers: '(YouTube, Vimeo, Vine, Instagram, edo DailyMotion)' + }, link: { link: 'Esteka', insert: 'Esteka bat txertatu', diff --git a/vendor/assets/components/summernote/lang/summernote-fa-IR.js b/vendor/assets/components/summernote/lang/summernote-fa-IR.js index 07bda1394..ac9ae078b 100644 --- a/vendor/assets/components/summernote/lang/summernote-fa-IR.js +++ b/vendor/assets/components/summernote/lang/summernote-fa-IR.js @@ -25,6 +25,13 @@ url: 'آدرس تصویر', remove: 'حذف تصویر' }, + video: { + video: 'ویدیو', + videoLink: 'لینک ویدیو', + insert: 'افزودن ویدیو', + url: 'آدرس ویدیو ؟', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, یا Youku)' + }, link: { link: 'لینک', insert: 'اضافه کردن لینک', diff --git a/vendor/assets/components/summernote/lang/summernote-fi-FI.js b/vendor/assets/components/summernote/lang/summernote-fi-FI.js index c51c0435a..3f1bfe810 100644 --- a/vendor/assets/components/summernote/lang/summernote-fi-FI.js +++ b/vendor/assets/components/summernote/lang/summernote-fi-FI.js @@ -25,6 +25,13 @@ url: 'URL-osoitteen mukaan', remove: 'Poista kuva' }, + video: { + video: 'Video', + videoLink: 'Linkki videoon', + insert: 'Lisää video', + url: 'Videon URL-osoite?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion tai Youku)' + }, link: { link: 'Linkki', insert: 'Lisää linkki', diff --git a/vendor/assets/components/summernote/lang/summernote-fr-FR.js b/vendor/assets/components/summernote/lang/summernote-fr-FR.js index 8c3ce1136..d4f280f46 100644 --- a/vendor/assets/components/summernote/lang/summernote-fr-FR.js +++ b/vendor/assets/components/summernote/lang/summernote-fr-FR.js @@ -10,7 +10,7 @@ name: 'Famille de police', strikethrough: 'Barré', superscript: 'Exposant', - subscript: 'Indicé', + subscript: 'Indice', size: 'Taille de police' }, image: { @@ -22,11 +22,25 @@ floatLeft: 'Aligné à gauche', floatRight: 'Aligné à droite', floatNone: 'Pas d\'alignement', - dragImageHere: 'Faites glisser une image avec la souris dans ce cadre', + shapeRounded: 'Forme: Rectangle arrondie', + shapeCircle: 'Forme: Cercle', + shapeThumbnail: 'Forme: Vignette', + shapeNone: 'Forme: Aucune', + dragImageHere: 'Faites glisser une image ou un texte dans ce cadre', + dropImage: 'Lachez l\'image ou le texte', selectFromFiles: 'Choisir un fichier', + maximumFileSize: 'Taille de fichier maximale', + maximumFileSizeError: 'Taille maximale du fichier dépassée', url: 'URL de l\'image', remove: 'Supprimer l\'image' }, + video: { + video: 'Vidéo', + videoLink: 'Lien vidéo', + insert: 'Insérer une vidéo', + url: 'URL de la vidéo', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion ou Youku)' + }, link: { link: 'Lien', insert: 'Insérer un lien', @@ -40,7 +54,7 @@ table: 'Tableau' }, hr: { - insert: 'Insérer une ligne horizontale de séparation' + insert: 'Insérer une ligne horizontale' }, style: { style: 'Style', @@ -88,7 +102,8 @@ textFormatting: 'Mise en forme du texte', action: 'Action', paragraphFormatting: 'Mise en forme des paragraphes', - documentStyle: 'Style du document' + documentStyle: 'Style du document', + extraKeys: 'Touches supplémentaires' }, history: { undo: 'Annuler la dernière action', diff --git a/vendor/assets/components/summernote/lang/summernote-he-IL.js b/vendor/assets/components/summernote/lang/summernote-he-IL.js index 316809cbd..fe72c84cb 100644 --- a/vendor/assets/components/summernote/lang/summernote-he-IL.js +++ b/vendor/assets/components/summernote/lang/summernote-he-IL.js @@ -27,6 +27,13 @@ url: 'נתיב לתמונה', remove: 'הסר תמונה' }, + video: { + video: 'סרטון', + videoLink: 'קישור לסרטון', + insert: 'הוסף סרטון', + url: 'קישור לסרטון', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion או Youku)' + }, link: { link: 'קישור', insert: 'הוסף קישור', diff --git a/vendor/assets/components/summernote/lang/summernote-hu-HU.js b/vendor/assets/components/summernote/lang/summernote-hu-HU.js index 9a205d47f..ab84172a8 100644 --- a/vendor/assets/components/summernote/lang/summernote-hu-HU.js +++ b/vendor/assets/components/summernote/lang/summernote-hu-HU.js @@ -25,6 +25,13 @@ url: 'Kép URL címe', remove: 'Kép törlése' }, + video: { + video: 'Videó', + videoLink: 'Videó hivatkozás', + insert: 'Videó beszúrása', + url: 'Videó URL címe', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, vagy Youku)' + }, link: { link: 'Hivatkozás', insert: 'Hivatkozás beszúrása', diff --git a/vendor/assets/components/summernote/lang/summernote-id-ID.js b/vendor/assets/components/summernote/lang/summernote-id-ID.js index 5c36c34db..474038a6e 100644 --- a/vendor/assets/components/summernote/lang/summernote-id-ID.js +++ b/vendor/assets/components/summernote/lang/summernote-id-ID.js @@ -24,6 +24,13 @@ url: 'URL gambar', remove: 'Hapus Gambar' }, + video: { + video: 'Video', + videoLink: 'Link video', + insert: 'Sisipkan video', + url: 'Tautan video', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, atau Youku)' + }, link: { link: 'Tautan', insert: 'Tambah tautan', diff --git a/vendor/assets/components/summernote/lang/summernote-it-IT.js b/vendor/assets/components/summernote/lang/summernote-it-IT.js index 3eb3e4ab5..a7929c96c 100644 --- a/vendor/assets/components/summernote/lang/summernote-it-IT.js +++ b/vendor/assets/components/summernote/lang/summernote-it-IT.js @@ -25,6 +25,13 @@ url: 'URL dell\'immagine', remove: 'Rimuovi immagine' }, + video: { + video: 'Video', + videoLink: 'Collegamento ad un Video', + insert: 'Inserisci Video', + url: 'URL del Video', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion o Youku)' + }, link: { link: 'Collegamento', insert: 'Inserisci Collegamento', diff --git a/vendor/assets/components/summernote/lang/summernote-ja-JP.js b/vendor/assets/components/summernote/lang/summernote-ja-JP.js index 2b4dbb70b..d7aeff69a 100644 --- a/vendor/assets/components/summernote/lang/summernote-ja-JP.js +++ b/vendor/assets/components/summernote/lang/summernote-ja-JP.js @@ -25,6 +25,13 @@ url: 'URLから画像を挿入する', remove: '画像を削除する' }, + video: { + video: '動画', + videoLink: '動画リンク', + insert: '動画挿入', + url: '動画のURL', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, Youku)' + }, link: { link: 'リンク', insert: 'リンク挿入', diff --git a/vendor/assets/components/summernote/lang/summernote-ko-KR.js b/vendor/assets/components/summernote/lang/summernote-ko-KR.js index 15f7fbc7f..c078764d0 100644 --- a/vendor/assets/components/summernote/lang/summernote-ko-KR.js +++ b/vendor/assets/components/summernote/lang/summernote-ko-KR.js @@ -1,5 +1,4 @@ (function ($) { - console.log('hit'); $.extend($.summernote.lang, { 'ko-KR': { font: { @@ -17,7 +16,7 @@ image: { image: '사진', insert: '사진 추가', - resizeFull: '원본 크기로 변경', + resizeFull: '100% 크기로 변경', resizeHalf: '50% 크기로 변경', resizeQuarter: '25% 크기로 변경', floatLeft: '왼쪽 정렬', @@ -27,11 +26,19 @@ shapeCircle: '스타일: 원형', shapeThumbnail: '스타일: 액자', shapeNone: '스타일: 없음', - dragImageHere: '사진을 이곳으로 끌어오세요', + dragImageHere: '텍스트 혹은 사진을 이곳으로 끌어오세요', + dropImage: '텍스트 혹은 사진을 내려놓으세요', selectFromFiles: '파일 선택', url: '사진 URL', remove: '사진 삭제' }, + video: { + video: '동영상', + videoLink: '동영상 링크', + insert: '동영상 추가', + url: '동영상 URL', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, Youku 사용 가능)' + }, link: { link: '링크', insert: '링크 추가', @@ -98,6 +105,10 @@ history: { undo: '실행 취소', redo: '다시 실행' + }, + specialChar: { + specialChar: '특수문자', + select: '특수문자를 선택하세요' } } }); diff --git a/vendor/assets/components/summernote/lang/summernote-lt-LT.js b/vendor/assets/components/summernote/lang/summernote-lt-LT.js new file mode 100644 index 000000000..33852b78d --- /dev/null +++ b/vendor/assets/components/summernote/lang/summernote-lt-LT.js @@ -0,0 +1,107 @@ +(function ($) { + $.extend($.summernote.lang, { + 'lt-LT': { + font: { + bold: 'Paryškintas', + italic: 'Kursyvas', + underline: 'Pabrėžtas', + clear: 'Be formatavimo', + height: 'Eilutės aukštis', + name: 'Šrifto pavadinimas', + strikethrough: 'Perbrauktas', + superscript: 'Viršutinis', + subscript: 'Indeksas', + size: 'Šrifto dydis' + }, + image: { + image: 'Paveikslėlis', + insert: 'Įterpti paveikslėlį', + resizeFull: 'Pilnas dydis', + resizeHalf: 'Sumažinti dydį 50%', + resizeQuarter: 'Sumažinti dydį 25%', + floatLeft: 'Kairinis lygiavimas', + floatRight: 'Dešininis lygiavimas', + floatNone: 'Jokio lygiavimo', + shapeRounded: 'Forma: apvalūs kraštai', + shapeCircle: 'Forma: apskritimas', + shapeThumbnail: 'Forma: miniatiūra', + shapeNone: 'Forma: jokia', + dragImageHere: 'Vilkite paveikslėlį čia', + selectFromFiles: 'Pasirinkite failą', + maximumFileSize: 'Maskimalus failo dydis', + maximumFileSizeError: 'Maskimalus failo dydis viršytas!', + url: 'Paveikslėlio URL adresas', + remove: 'Ištrinti paveikslėlį' + }, + link: { + link: 'Nuoroda', + insert: 'Įterpti nuorodą', + unlink: 'Pašalinti nuorodą', + edit: 'Redaguoti', + textToDisplay: 'Rodomas tekstas', + url: 'Koks URL adresas yra susietas?', + openInNewWindow: 'Atidaryti naujame lange' + }, + table: { + table: 'Lentelė' + }, + hr: { + insert: 'Įterpti horizontalią liniją' + }, + style: { + style: 'Stilius', + normal: 'Normalus', + blockquote: 'Citata', + pre: 'Kodas', + h1: 'Antraštė 1', + h2: 'Antraštė 2', + h3: 'Antraštė 3', + h4: 'Antraštė 4', + h5: 'Antraštė 5', + h6: 'Antraštė 6' + }, + lists: { + unordered: 'Suženklintasis sąrašas', + ordered: 'Sunumeruotas sąrašas' + }, + options: { + help: 'Pagalba', + fullscreen: 'Viso ekrano režimas', + codeview: 'HTML kodo peržiūra' + }, + paragraph: { + paragraph: 'Pastraipa', + outdent: 'Sumažinti įtrauką', + indent: 'Padidinti įtrauką', + left: 'Kairinė lygiuotė', + center: 'Centrinė lygiuotė', + right: 'Dešininė lygiuotė', + justify: 'Abipusis išlyginimas' + }, + color: { + recent: 'Paskutinė naudota spalva', + more: 'Daugiau spalvų', + background: 'Fono spalva', + foreground: 'Šrifto spalva', + transparent: 'Permatoma', + setTransparent: 'Nustatyti skaidrumo intensyvumą', + reset: 'Atkurti', + resetToDefault: 'Atstatyti numatytąją spalvą' + }, + shortcut: { + shortcuts: 'Spartieji klavišai', + close: 'Uždaryti', + textFormatting: 'Teksto formatavimas', + action: 'Veiksmas', + paragraphFormatting: 'Pastraipos formatavimas', + documentStyle: 'Dokumento stilius', + extraKeys: 'Papildomi klavišų deriniai' + }, + history: { + undo: 'Anuliuoti veiksmą', + redo: 'Perdaryti veiksmą' + } + + } + }); +})(jQuery); diff --git a/vendor/assets/components/summernote/lang/summernote-nb-NO.js b/vendor/assets/components/summernote/lang/summernote-nb-NO.js index f2be0a7e7..80bd32e61 100644 --- a/vendor/assets/components/summernote/lang/summernote-nb-NO.js +++ b/vendor/assets/components/summernote/lang/summernote-nb-NO.js @@ -25,6 +25,13 @@ url: 'Bilde-URL', remove: 'Fjern bilde' }, + video: { + video: 'Video', + videoLink: 'Videolenke', + insert: 'Sett inn video', + url: 'Video-URL', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion eller Youku)' + }, link: { link: 'Lenke', insert: 'Sett inn lenke', diff --git a/vendor/assets/components/summernote/lang/summernote-nl-NL.js b/vendor/assets/components/summernote/lang/summernote-nl-NL.js index 919c419e7..38f6352e8 100644 --- a/vendor/assets/components/summernote/lang/summernote-nl-NL.js +++ b/vendor/assets/components/summernote/lang/summernote-nl-NL.js @@ -25,6 +25,13 @@ url: 'URL van de afbeelding', remove: 'Verwijder afbeelding' }, + video: { + video: 'Video', + videoLink: 'Video link', + insert: 'Video invoegen', + url: 'URL van de video', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion of Youku)' + }, link: { link: 'Link', insert: 'Link invoegen', diff --git a/vendor/assets/components/summernote/lang/summernote-pl-PL.js b/vendor/assets/components/summernote/lang/summernote-pl-PL.js index 4a4c8717c..1ac0339ac 100644 --- a/vendor/assets/components/summernote/lang/summernote-pl-PL.js +++ b/vendor/assets/components/summernote/lang/summernote-pl-PL.js @@ -32,6 +32,13 @@ url: 'Adres URL grafiki', remove: 'Usuń grafikę' }, + video: { + video: 'Wideo', + videoLink: 'Adres wideo', + insert: 'Wstaw wideo', + url: 'Adres wideo', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, lub Youku)' + }, link: { link: 'Odnośnik', insert: 'Wstaw odnośnik', diff --git a/vendor/assets/components/summernote/lang/summernote-pt-BR.js b/vendor/assets/components/summernote/lang/summernote-pt-BR.js index b7745e1e0..894c19460 100644 --- a/vendor/assets/components/summernote/lang/summernote-pt-BR.js +++ b/vendor/assets/components/summernote/lang/summernote-pt-BR.js @@ -24,6 +24,13 @@ selectFromFiles: 'Selecione a partir dos arquivos', url: 'URL da image' }, + video: { + video: 'Vídeo', + videoLink: 'Link para vídeo', + insert: 'Inserir vídeo', + url: 'URL do vídeo?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, ou Youku)' + }, link: { link: 'Link', insert: 'Inserir link', diff --git a/vendor/assets/components/summernote/lang/summernote-pt-PT.js b/vendor/assets/components/summernote/lang/summernote-pt-PT.js new file mode 100644 index 000000000..00a2a19f4 --- /dev/null +++ b/vendor/assets/components/summernote/lang/summernote-pt-PT.js @@ -0,0 +1,96 @@ +(function ($) { + $.extend($.summernote.lang, { + 'pt-PT': { + font: { + bold: 'Negrito', + italic: 'Itálico', + underline: 'Sublinhado', + clear: 'Remover estilo da fonte', + height: 'Altura da linha', + name: 'Fonte', + strikethrough: 'Riscado', + size: 'Tamanho da fonte' + }, + image: { + image: 'Imagem', + insert: 'Inserir imagem', + resizeFull: 'Redimensionar Completo', + resizeHalf: 'Redimensionar Metade', + resizeQuarter: 'Redimensionar Um Quarto', + floatLeft: 'Float Esquerda', + floatRight: 'Float Direita', + floatNone: 'Sem Float', + dragImageHere: 'Arraste uma imagem para aqui', + selectFromFiles: 'Selecione a partir dos arquivos', + url: 'Endereço da imagem' + }, + link: { + link: 'Link', + insert: 'Inserir ligação', + unlink: 'Remover ligação', + edit: 'Editar', + textToDisplay: 'Texto para exibir', + url: 'Que endereço esta licação leva?', + openInNewWindow: 'Abrir numa nova janela' + }, + table: { + table: 'Tabela' + }, + hr: { + insert: 'Inserir linha horizontal' + }, + style: { + style: 'Estilo', + normal: 'Normal', + blockquote: 'Citação', + pre: 'Código', + h1: 'Título 1', + h2: 'Título 2', + h3: 'Título 3', + h4: 'Título 4', + h5: 'Título 5', + h6: 'Título 6' + }, + lists: { + unordered: 'Lista com marcadores', + ordered: 'Lista numerada' + }, + options: { + help: 'Ajuda', + fullscreen: 'Janela Completa', + codeview: 'Ver código-fonte' + }, + paragraph: { + paragraph: 'Parágrafo', + outdent: 'Menor tabulação', + indent: 'Maior tabulação', + left: 'Alinhar à esquerda', + center: 'Alinhar ao centro', + right: 'Alinha à direita', + justify: 'Justificado' + }, + color: { + recent: 'Cor recente', + more: 'Mais cores', + background: 'Fundo', + foreground: 'Fonte', + transparent: 'Transparente', + setTransparent: 'Fundo transparente', + reset: 'Restaurar', + resetToDefault: 'Restaurar padrão' + }, + shortcut: { + shortcuts: 'Atalhos do teclado', + close: 'Fechar', + textFormatting: 'Formatação de texto', + action: 'Ação', + paragraphFormatting: 'Formatação de parágrafo', + documentStyle: 'Estilo de documento' + }, + history: { + undo: 'Desfazer', + redo: 'Refazer' + } + } + }); +})(jQuery); diff --git a/vendor/assets/components/summernote/lang/summernote-ro-RO.js b/vendor/assets/components/summernote/lang/summernote-ro-RO.js index 0c84cbc97..e65433d57 100644 --- a/vendor/assets/components/summernote/lang/summernote-ro-RO.js +++ b/vendor/assets/components/summernote/lang/summernote-ro-RO.js @@ -23,6 +23,13 @@ selectFromFiles: 'Alege din fişiere', url: 'URL imagine' }, + video: { + video: 'Video', + videoLink: 'Link video', + insert: 'Inserează video', + url: 'URL video?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion, sau Youku)' + }, link: { link: 'Link', insert: 'Inserează link', diff --git a/vendor/assets/components/summernote/lang/summernote-ru-RU.js b/vendor/assets/components/summernote/lang/summernote-ru-RU.js index d6147f42a..878e2f18a 100644 --- a/vendor/assets/components/summernote/lang/summernote-ru-RU.js +++ b/vendor/assets/components/summernote/lang/summernote-ru-RU.js @@ -22,11 +22,23 @@ floatLeft: 'Расположить слева', floatRight: 'Расположить справа', floatNone: 'Расположение по-умолчанию', + shapeRounded: 'Форма: Закругленная', + shapeCircle: 'Форма: Круг', + shapeThumbnail: 'Форма: Миниатюра', + shapeNone: 'Форма: Нет', dragImageHere: 'Перетащите сюда картинку', + dropImage: 'Перетащите картинку', selectFromFiles: 'Выбрать из файлов', url: 'URL картинки', remove: 'Удалить картинку' }, + video: { + video: 'Видео', + videoLink: 'Ссылка на видео', + insert: 'Вставить видео', + url: 'URL видео', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion или Youku)' + }, link: { link: 'Ссылка', insert: 'Вставить ссылку', diff --git a/vendor/assets/components/summernote/lang/summernote-sk-SK.js b/vendor/assets/components/summernote/lang/summernote-sk-SK.js index ac2a23b25..57793f4cb 100644 --- a/vendor/assets/components/summernote/lang/summernote-sk-SK.js +++ b/vendor/assets/components/summernote/lang/summernote-sk-SK.js @@ -23,6 +23,13 @@ selectFromFiles: 'Vybrať súbor', url: 'URL obrázku' }, + video: { + video: 'Video', + videoLink: 'Odkaz videa', + insert: 'Vložiť video', + url: 'URL videa?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion nebo Youku)' + }, link: { link: 'Odkaz', insert: 'Vytvoriť odkaz', diff --git a/vendor/assets/components/summernote/lang/summernote-sl-SI.js b/vendor/assets/components/summernote/lang/summernote-sl-SI.js index 819183d8e..d1fb146d5 100644 --- a/vendor/assets/components/summernote/lang/summernote-sl-SI.js +++ b/vendor/assets/components/summernote/lang/summernote-sl-SI.js @@ -27,6 +27,13 @@ url: 'URL naslov slike', remove: 'Odstrani sliko' }, + video: { + video: 'Video', + videoLink: 'Video povezava', + insert: 'Vstavi video', + url: 'Povezava do videa', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion ali Youku)' + }, link: { link: 'Povezava', insert: 'Vstavi povezavo', diff --git a/vendor/assets/components/summernote/lang/summernote-sr-RS-Latin.js b/vendor/assets/components/summernote/lang/summernote-sr-RS-Latin.js index b5f74e761..36f7b28e9 100644 --- a/vendor/assets/components/summernote/lang/summernote-sr-RS-Latin.js +++ b/vendor/assets/components/summernote/lang/summernote-sr-RS-Latin.js @@ -24,6 +24,13 @@ url: 'Adresa slike', remove: 'Ukloni sliku' }, + video: { + video: 'Video', + videoLink: 'Veza ka videu', + insert: 'Umetni video', + url: 'URL video', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion ili Youku)' + }, link: { link: 'Veza', insert: 'Umetni vezu', diff --git a/vendor/assets/components/summernote/lang/summernote-sr-RS.js b/vendor/assets/components/summernote/lang/summernote-sr-RS.js index dda6c7bd9..a822cda93 100644 --- a/vendor/assets/components/summernote/lang/summernote-sr-RS.js +++ b/vendor/assets/components/summernote/lang/summernote-sr-RS.js @@ -24,6 +24,13 @@ url: 'Адреса слике', remove: 'Уклони слику' }, + video: { + video: 'Видео', + videoLink: 'Веза ка видеу', + insert: 'Уметни видео', + url: 'URL видео', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion или Youku)' + }, link: { link: 'Веза', insert: 'Уметни везу', diff --git a/vendor/assets/components/summernote/lang/summernote-sv-SE.js b/vendor/assets/components/summernote/lang/summernote-sv-SE.js index 2d7edb5d5..e87b1c698 100644 --- a/vendor/assets/components/summernote/lang/summernote-sv-SE.js +++ b/vendor/assets/components/summernote/lang/summernote-sv-SE.js @@ -25,6 +25,13 @@ url: 'Länk till bild', remove: 'Ta bort bild' }, + video: { + video: 'Filmklipp', + videoLink: 'Länk till filmklipp', + insert: 'Infoga filmklipp', + url: 'Länk till filmklipp', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion eller Youku)' + }, link: { link: 'Länk', insert: 'Infoga länk', diff --git a/vendor/assets/components/summernote/lang/summernote-th-TH.js b/vendor/assets/components/summernote/lang/summernote-th-TH.js index 5326d5e55..dc777aae5 100644 --- a/vendor/assets/components/summernote/lang/summernote-th-TH.js +++ b/vendor/assets/components/summernote/lang/summernote-th-TH.js @@ -8,10 +8,10 @@ clear: 'ล้างรูปแบบตัวอักษร', height: 'ความสูงบรรทัด', name: 'แบบตัวอักษร', - strikethrough: 'ขีดฆ่า', - subscript: 'ตัวห้อย', - superscript: 'ตัวยก', - size: 'ขนาดตัวอักษร' + strikethrough: 'ขีดฆ่า', + subscript: 'ตัวห้อย', + superscript: 'ตัวยก', + size: 'ขนาดตัวอักษร' }, image: { image: 'รูปภาพ', @@ -27,6 +27,13 @@ url: 'ที่อยู่ URL ของรูปภาพ', remove: 'ลบรูปภาพ' }, + video: { + video: 'วีดีโอ', + videoLink: 'ลิงก์ของวีดีโอ', + insert: 'แทรกวีดีโอ', + url: 'ที่อยู่ URL ของวีดีโอ?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion หรือ Youku)' + }, link: { link: 'ตัวเชื่อมโยง', insert: 'แทรกตัวเชื่อมโยง', diff --git a/vendor/assets/components/summernote/lang/summernote-tr-TR.js b/vendor/assets/components/summernote/lang/summernote-tr-TR.js index ecb510410..bf6c32f3b 100644 --- a/vendor/assets/components/summernote/lang/summernote-tr-TR.js +++ b/vendor/assets/components/summernote/lang/summernote-tr-TR.js @@ -9,8 +9,8 @@ height: 'Satır yüksekliği', name: 'Yazı Tipi', strikethrough: 'Üstü çizili', - subscript: 'Subscript', - superscript: 'Superscript', + subscript: 'Alt Simge', + superscript: 'Üst Simge', size: 'Yazı tipi boyutu' }, image: { @@ -31,6 +31,13 @@ url: 'Resim bağlantısı', remove: 'Resimi Kaldır' }, + video: { + video: 'Video', + videoLink: 'Video bağlantısı', + insert: 'Video ekle', + url: 'Video bağlantısı?', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion veya Youku)' + }, link: { link: 'Bağlantı', insert: 'Bağlantı ekle', diff --git a/vendor/assets/components/summernote/lang/summernote-uk-UA.js b/vendor/assets/components/summernote/lang/summernote-uk-UA.js index c257f7284..3957be28b 100644 --- a/vendor/assets/components/summernote/lang/summernote-uk-UA.js +++ b/vendor/assets/components/summernote/lang/summernote-uk-UA.js @@ -22,11 +22,23 @@ floatLeft: 'Розташувати ліворуч', floatRight: 'Розташувати праворуч', floatNone: 'Початкове розташування', + shapeRounded: 'Форма: Заокруглена', + shapeCircle: 'Форма: Коло', + shapeThumbnail: 'Форма: Мініатюра', + shapeNone: 'Форма: Немає', dragImageHere: 'Перетягніть сюди картинку', + dropImage: 'Перетягніть картинку', selectFromFiles: 'Вибрати з файлів', url: 'URL картинки', remove: 'Видалити картинку' }, + video: { + video: 'Відео', + videoLink: 'Посилання на відео', + insert: 'Вставити відео', + url: 'URL відео', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion чи Youku)' + }, link: { link: 'Посилання', insert: 'Вставити посилання', diff --git a/vendor/assets/components/summernote/lang/summernote-vi-VN.js b/vendor/assets/components/summernote/lang/summernote-vi-VN.js index 0451f1359..cb848e65e 100644 --- a/vendor/assets/components/summernote/lang/summernote-vi-VN.js +++ b/vendor/assets/components/summernote/lang/summernote-vi-VN.js @@ -25,6 +25,13 @@ url: 'URL', remove: 'Ghỡ Bỏ' }, + video: { + video: 'Video', + videoLink: 'Đường Dẫn đến Video', + insert: 'Chèn Video', + url: 'URL', + providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion và Youku)' + }, link: { link: 'Đường Dẫn', insert: 'Chèn Đường Dẫn', diff --git a/vendor/assets/components/summernote/lang/summernote-zh-CN.js b/vendor/assets/components/summernote/lang/summernote-zh-CN.js index 50281c616..15e24d419 100644 --- a/vendor/assets/components/summernote/lang/summernote-zh-CN.js +++ b/vendor/assets/components/summernote/lang/summernote-zh-CN.js @@ -9,20 +9,36 @@ height: '行高', name: '字体', strikethrough: '删除线', + subscript: '下标', + superscript: '上标', size: '字号' }, image: { image: '图片', insert: '插入图片', - resizeFull: '调整至 100%', - resizeHalf: '调整至 50%', - resizeQuarter: '调整至 25%', - floatLeft: '左浮动', - floatRight: '右浮动', - floatNone: '不浮动', - dragImageHere: '将图片拖至此处', + resizeFull: '缩放至 100%', + resizeHalf: '缩放至 50%', + resizeQuarter: '缩放至 25%', + floatLeft: '靠左浮动', + floatRight: '靠右浮动', + floatNone: '取消浮动', + shapeRounded: '形状: 圆角', + shapeCircle: '形状: 圆', + shapeThumbnail: '形状: 缩略图', + shapeNone: '形状: 无', + dragImageHere: '将图片拖拽至此处', selectFromFiles: '从本地上传', - url: '图片地址' + maximumFileSize: '文件大小最大值', + maximumFileSizeError: '文件大小超出最大值。', + url: '图片地址', + remove: '移除图片' + }, + video: { + video: '视频', + videoLink: '视频链接', + insert: '插入视频', + url: '视频地址', + providers: '(优酷, Instagram, DailyMotion, Youtube等)' }, link: { link: '链接', @@ -85,7 +101,8 @@ textFormatting: '文本格式', action: '动作', paragraphFormatting: '段落格式', - documentStyle: '文档样式' + documentStyle: '文档样式', + extraKeys: '额外按键' }, history: { undo: '撤销', diff --git a/vendor/assets/components/summernote/lang/summernote-zh-TW.js b/vendor/assets/components/summernote/lang/summernote-zh-TW.js index 72a99c420..1cf203970 100644 --- a/vendor/assets/components/summernote/lang/summernote-zh-TW.js +++ b/vendor/assets/components/summernote/lang/summernote-zh-TW.js @@ -7,8 +7,11 @@ underline: '底線', clear: '清除格式', height: '行高', + name: '字體', strikethrough: '刪除線', - size: '字體大小' + subscript: '下標', + superscript: '上標', + size: '字號' }, image: { image: '圖片', @@ -19,9 +22,23 @@ floatLeft: '靠左浮動', floatRight: '靠右浮動', floatNone: '取消浮動', + shapeRounded: '形狀: 圓角', + shapeCircle: '形狀: 圓', + shapeThumbnail: '形狀: 縮略圖', + shapeNone: '形狀: 無', dragImageHere: '將圖片拖曳至此處', selectFromFiles: '從本機上傳', - url: '圖片網址' + maximumFileSize: '文件大小最大值', + maximumFileSizeError: '文件大小超出最大值。', + url: '圖片網址', + remove: '移除圖片' + }, + video: { + video: '影片', + videoLink: '影片連結', + insert: '插入影片', + url: '影片網址', + providers: '(優酷, Instagram, DailyMotion, Youtube等)' }, link: { link: '連結', @@ -84,7 +101,8 @@ textFormatting: '文字格式', action: '動作', paragraphFormatting: '段落格式', - documentStyle: '文件格式' + documentStyle: '文件格式', + extraKeys: '額外按鍵' }, history: { undo: '復原', diff --git a/vendor/assets/components/summernote/lite.html b/vendor/assets/components/summernote/lite.html new file mode 100644 index 000000000..4db44824b --- /dev/null +++ b/vendor/assets/components/summernote/lite.html @@ -0,0 +1,14 @@ + + + + + + + summernote-lite + + +

    Summernote standalone

    +
    + + + diff --git a/vendor/assets/components/summernote/package.json b/vendor/assets/components/summernote/package.json index e5b088d60..70f995914 100644 --- a/vendor/assets/components/summernote/package.json +++ b/vendor/assets/components/summernote/package.json @@ -1,10 +1,10 @@ { "name": "summernote", - "description": "Super Simple WYSIWYG Editor on Bootstrap", - "version": "0.6.6", + "description": "Super simple WYSIWYG editor", + "version": "0.7.3", + "license": "MIT", "keywords": [ "editor", - "bootstrap", "WYSIWYG" ], "repository": { @@ -12,22 +12,41 @@ "url": "https://github.com/summernote/summernote.git" }, "author": { - "name": "Hackerwins", + "name": "hackerwins", "email": "" }, + "main": "dist/summernote.js", "devDependencies": { - "load-grunt-tasks": "0.2.0", - "requirejs": "2.1.9", + "chai": "^3.2.0", "grunt": "*", + "grunt-contrib-clean": "^0.7.0", + "grunt-contrib-compress": "*", + "grunt-contrib-connect": "*", + "grunt-contrib-copy": "^0.8.2", "grunt-contrib-jshint": "0.7.2", - "grunt-contrib-qunit": "0.5.2", "grunt-contrib-uglify": "~0.2.2", "grunt-contrib-watch": "*", - "grunt-contrib-connect": "*", - "grunt-contrib-compress": "*", - "grunt-recess": "*", + "grunt-coveralls": "^1.0.0", "grunt-exec": "^0.4.6", - "connect-livereload": "*", - "open": "0.0.4" + "grunt-jscs": "^2.6.0", + "grunt-karma": "^0.12.0", + "grunt-recess": "*", + "grunt-saucelabs": "*", + "karma": "^0.13.3", + "karma-chrome-launcher": "^0.2.0", + "karma-coverage": "^0.5.3", + "karma-firefox-launcher": "^0.1.6", + "karma-ie-launcher": "^0.2.0", + "karma-mocha": "^0.2.0", + "karma-opera-launcher": "^0.3.0", + "karma-phantomjs-launcher": "^0.2.0", + "karma-requirejs": "^0.2.2", + "karma-safari-launcher": "^0.1.1", + "karma-sauce-launcher": "^0.3.0", + "load-grunt-tasks": "0.2.0", + "mocha": "^2.3.4", + "open": "0.0.4", + "phantomjs": "^1.9.17", + "requirejs": "^2.1.22" } } diff --git a/vendor/assets/components/summernote/plugin/hello/summernote-ext-hello.js b/vendor/assets/components/summernote/plugin/hello/summernote-ext-hello.js new file mode 100644 index 000000000..7c6434a44 --- /dev/null +++ b/vendor/assets/components/summernote/plugin/hello/summernote-ext-hello.js @@ -0,0 +1,82 @@ +(function (factory) { + /* global define */ + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = factory(require('jquery')); + } else { + // Browser globals + factory(window.jQuery); + } +}(function ($) { + + // Extends plugins for adding hello. + // - plugin is external module for customizing. + $.extend($.summernote.plugins, { + /** + * @param {Object} context - context object has status of editor. + */ + 'hello': function (context) { + var self = this; + + // ui has renders to build ui elements. + // - you can create a button with `ui.button` + var ui = $.summernote.ui; + + // add hello button + context.memo('button.hello', function () { + // create button + var button = ui.button({ + contents: ' Hello', + tooltip: 'hello', + click: function () { + self.$panel.show(); + self.$panel.hide(500); + // invoke insertText method with 'hello' on editor module. + context.invoke('editor.insertText', 'hello'); + } + }); + + // create jQuery object from button instance. + var $hello = button.render(); + return $hello; + }); + + // This events will be attached when editor is initialized. + this.events = { + // This will be called after modules are initialized. + 'summernote.init': function (we, e) { + console.log('summernote initialized', we, e); + }, + // This will be called when user releases a key on editable. + 'summernote.keyup': function (we, e) { + console.log('summernote keyup', we, e); + } + }; + + // This method will be called when editor is initialized by $('..').summernote(); + // You can create elements for plugin + this.initialize = function () { + this.$panel = $('
    ').css({ + position: 'absolute', + width: 100, + height: 100, + left: '50%', + top: '50%', + background: 'red' + }).hide(); + + this.$panel.appendTo('body'); + }; + + // This methods will be called when editor is destroyed by $('..').summernote('destroy'); + // You should remove elements on `initialize`. + this.destroy = function () { + this.$panel.remove(); + this.$panel = null; + }; + } + }); +})); diff --git a/vendor/assets/components/summernote/plugin/specialchars/summernote-ext-specialchars.js b/vendor/assets/components/summernote/plugin/specialchars/summernote-ext-specialchars.js new file mode 100644 index 000000000..0362e702f --- /dev/null +++ b/vendor/assets/components/summernote/plugin/specialchars/summernote-ext-specialchars.js @@ -0,0 +1,316 @@ +(function (factory) { + /* global define */ + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = factory(require('jquery')); + } else { + // Browser globals + factory(window.jQuery); + } +}(function ($) { + $.extend($.summernote.plugins, { + 'specialchars': function (context) { + var self = this; + var ui = $.summernote.ui; + + var $editor = context.layoutInfo.editor; + var options = context.options; + var lang = options.langInfo; + + var KEY = { + UP: 38, + DOWN: 40, + LEFT: 37, + RIGHT: 39, + ENTER: 13 + }; + var COLUMN_LENGTH = 15; + var COLUMN_WIDTH = 35; + + var currentColumn, currentRow, totalColumn, totalRow = 0; + + // special characters data set + var specialCharDataSet = [ + '"', '&', '<', '>', '¡', '¢', + '£', '¤', '¥', '¦', '§', + '¨', '©', 'ª', '«', '¬', + '®', '¯', '°', '±', '²', + '³', '´', 'µ', '¶', '·', + '¸', '¹', 'º', '»', '¼', + '½', '¾', '¿', '×', '÷', + 'ƒ', 'ˆ', '˜', '–', '—', + '‘', '’', '‚', '“', '”', + '„', '†', '‡', '•', '…', + '‰', '′', '″', '‹', '›', + '‾', '⁄', '€', 'ℑ', '℘', + 'ℜ', '™', 'ℵ', '←', '↑', + '→', '↓', '↔', '↵', '⇐', + '⇑', '⇒', '⇓', '⇔', '∀', + '∂', '∃', '∅', '∇', '∈', + '∉', '∋', '∏', '∑', '−', + '∗', '√', '∝', '∞', '∠', + '∧', '∨', '∩', '∪', '∫', + '∴', '∼', '≅', '≈', '≠', + '≡', '≤', '≥', '⊂', '⊃', + '⊄', '⊆', '⊇', '⊕', '⊗', + '⊥', '⋅', '⌈', '⌉', '⌊', + '⌋', '◊', '♠', '♣', '♥', + '♦' + ]; + + context.memo('button.specialCharacter', function () { + return ui.button({ + contents: '', + tooltip: lang.specialChar.specialChar, + click: function () { + self.show(); + } + }).render(); + }); + + /** + * Make Special Characters Table + * + * @member plugin.specialChar + * @private + * @return {jQuery} + */ + this.makeSpecialCharSetTable = function () { + var $table = $(''); + $.each(specialCharDataSet, function (idx, text) { + var $td = $('') : $table.find('tr').last(); + + var $button = ui.button({ + callback : function ($node) { + $node.html(text); + $node.attr('title', text); + $node.attr('data-value', encodeURIComponent(text)); + $node.css({ + width: COLUMN_WIDTH, + 'margin-right' : '2px', + 'margin-bottom' : '2px' + }); + } + }).render(); + + $td.append($button); + + $tr.append($td); + if (idx % COLUMN_LENGTH === 0) { + $table.append($tr); + } + }); + + totalRow = $table.find('tr').length; + totalColumn = COLUMN_LENGTH; + + return $table; + }; + + this.initialize = function () { + var $container = options.dialogsInBody ? $(document.body) : $editor; + + var body = '
    ' + this.makeSpecialCharSetTable()[0].outerHTML + '
    '; + + this.$dialog = ui.dialog({ + title: lang.specialChar.select, + body: body + }).render().appendTo($container); + }; + + this.show = function () { + var text = context.invoke('editor.getSelectedText'); + context.invoke('editor.saveRange'); + this.showSpecialCharDialog(text).then(function (selectChar) { + context.invoke('editor.restoreRange'); + + // build node + var $node = $('').html(selectChar)[0]; + + if ($node) { + // insert video node + context.invoke('editor.insertNode', $node); + } + }).fail(function () { + context.invoke('editor.restoreRange'); + }); + }; + + /** + * show image dialog + * + * @param {jQuery} $dialog + * @return {Promise} + */ + this.showSpecialCharDialog = function (text) { + return $.Deferred(function (deferred) { + var $specialCharDialog = self.$dialog; + var $specialCharNode = $specialCharDialog.find('.note-specialchar-node'); + var $selectedNode = null; + var ARROW_KEYS = [KEY.UP, KEY.DOWN, KEY.LEFT, KEY.RIGHT]; + var ENTER_KEY = KEY.ENTER; + + function addActiveClass($target) { + if (!$target) { + return; + } + $target.find('button').addClass('active'); + $selectedNode = $target; + } + + function removeActiveClass($target) { + $target.find('button').removeClass('active'); + $selectedNode = null; + } + + // find next node + function findNextNode(row, column) { + var findNode = null; + $.each($specialCharNode, function (idx, $node) { + var findRow = Math.ceil((idx + 1) / COLUMN_LENGTH); + var findColumn = ((idx + 1) % COLUMN_LENGTH === 0) ? COLUMN_LENGTH : (idx + 1) % COLUMN_LENGTH; + if (findRow === row && findColumn === column) { + findNode = $node; + return false; + } + }); + return $(findNode); + } + + function arrowKeyHandler(keyCode) { + // left, right, up, down key + var $nextNode; + var lastRowColumnLength = $specialCharNode.length % totalColumn; + + if (KEY.LEFT === keyCode) { + + if (currentColumn > 1) { + currentColumn = currentColumn - 1; + } else if (currentRow === 1 && currentColumn === 1) { + currentColumn = lastRowColumnLength; + currentRow = totalRow; + } else { + currentColumn = totalColumn; + currentRow = currentRow - 1; + } + + } else if (KEY.RIGHT === keyCode) { + + if (currentRow === totalRow && lastRowColumnLength === currentColumn) { + currentColumn = 1; + currentRow = 1; + } else if (currentColumn < totalColumn) { + currentColumn = currentColumn + 1; + } else { + currentColumn = 1; + currentRow = currentRow + 1; + } + + } else if (KEY.UP === keyCode) { + if (currentRow === 1 && lastRowColumnLength < currentColumn) { + currentRow = totalRow - 1; + } else { + currentRow = currentRow - 1; + } + } else if (KEY.DOWN === keyCode) { + currentRow = currentRow + 1; + } + + if (currentRow === totalRow && currentColumn > lastRowColumnLength) { + currentRow = 1; + } else if (currentRow > totalRow) { + currentRow = 1; + } else if (currentRow < 1) { + currentRow = totalRow; + } + + $nextNode = findNextNode(currentRow, currentColumn); + + if ($nextNode) { + removeActiveClass($selectedNode); + addActiveClass($nextNode); + } + } + + function enterKeyHandler() { + if (!$selectedNode) { + return; + } + + deferred.resolve(decodeURIComponent($selectedNode.find('button').data('value'))); + $specialCharDialog.modal('hide'); + } + + function keyDownEventHandler(event) { + event.preventDefault(); + var keyCode = event.keyCode; + if (keyCode === undefined || keyCode === null) { + return; + } + // check arrowKeys match + if (ARROW_KEYS.indexOf(keyCode) > -1) { + if ($selectedNode === null) { + addActiveClass($specialCharNode.eq(0)); + currentColumn = 1; + currentRow = 1; + return; + } + arrowKeyHandler(keyCode); + } else if (keyCode === ENTER_KEY) { + enterKeyHandler(); + } + return false; + } + + // remove class + removeActiveClass($specialCharNode); + + // find selected node + if (text) { + for (var i = 0; i < $specialCharNode.length; i++) { + var $checkNode = $($specialCharNode[i]); + if ($checkNode.text() === text) { + addActiveClass($checkNode); + currentRow = Math.ceil((i + 1) / COLUMN_LENGTH); + currentColumn = (i + 1) % COLUMN_LENGTH; + } + } + } + + ui.onDialogShown(self.$dialog, function () { + + $(document).on('keydown', keyDownEventHandler); + + self.$dialog.find('button').tooltip(); + + $specialCharNode.on('click', function (event) { + event.preventDefault(); + deferred.resolve(decodeURIComponent($(event.currentTarget).find('button').data('value'))); + ui.hideDialog(self.$dialog); + }); + + + }); + + ui.onDialogHidden(self.$dialog, function () { + $specialCharNode.off('click'); + + self.$dialog.find('button').tooltip('destroy'); + + $(document).off('keydown', keyDownEventHandler); + + if (deferred.state() === 'pending') { + deferred.reject(); + } + }); + + ui.showDialog(self.$dialog); + }); + }; + } + }); +})); diff --git a/vendor/assets/components/summernote/plugin/summernote-ext-hello.js b/vendor/assets/components/summernote/plugin/summernote-ext-hello.js deleted file mode 100644 index cb13fc7a5..000000000 --- a/vendor/assets/components/summernote/plugin/summernote-ext-hello.js +++ /dev/null @@ -1,89 +0,0 @@ -(function (factory) { - /* global define */ - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['jquery'], factory); - } else { - // Browser globals: jQuery - factory(window.jQuery); - } -}(function ($) { - // template - var tmpl = $.summernote.renderer.getTemplate(); - - /** - * @class plugin.hello - * - * Hello Plugin - */ - $.summernote.addPlugin({ - /** @property {String} name name of plugin */ - name: 'hello', - /** - * @property {Object} buttons - * @property {Function} buttons.hello function to make button - * @property {Function} buttons.helloDropdown function to make button - * @property {Function} buttons.helloImage function to make button - */ - buttons: { // buttons - hello: function () { - - return tmpl.iconButton('fa fa-header', { - event : 'hello', - title: 'hello', - hide: true - }); - }, - helloDropdown: function () { - - - var list = '
  • summernote
  • '; - list += '
  • Code Mirror
  • '; - var dropdown = ''; - - return tmpl.iconButton('fa fa-header', { - title: 'hello', - hide: true, - dropdown : dropdown - }); - }, - helloImage : function () { - return tmpl.iconButton('fa fa-file-image-o', { - event : 'helloImage', - title: 'helloImage', - hide: true - }); - } - - }, - - /** - * @property {Object} events - * @property {Function} events.hello run function when button that has a 'hello' event name fires click - * @property {Function} events.helloDropdown run function when button that has a 'helloDropdown' event name fires click - * @property {Function} events.helloImage run function when button that has a 'helloImage' event name fires click - */ - events: { // events - hello: function (event, editor, layoutInfo) { - // Get current editable node - var $editable = layoutInfo.editable(); - - // Call insertText with 'hello' - editor.insertText($editable, 'hello '); - }, - helloDropdown: function (event, editor, layoutInfo, value) { - // Get current editable node - var $editable = layoutInfo.editable(); - - // Call insertText with 'hello' - editor.insertText($editable, 'hello ' + value + '!!!!'); - }, - helloImage : function (event, editor, layoutInfo) { - var $editable = layoutInfo.editable(); - - var img = $(''); - editor.insertNode($editable, img[0]); - } - } - }); -})); diff --git a/vendor/assets/components/summernote/plugin/summernote-ext-video.js b/vendor/assets/components/summernote/plugin/summernote-ext-video.js deleted file mode 100644 index 4f71bc738..000000000 --- a/vendor/assets/components/summernote/plugin/summernote-ext-video.js +++ /dev/null @@ -1,573 +0,0 @@ -(function (factory) { - /* global define */ - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['jquery'], factory); - } else { - // Browser globals: jQuery - factory(window.jQuery); - } -}(function ($) { - // template - var tmpl = $.summernote.renderer.getTemplate(); - - // core functions: range, dom - var range = $.summernote.core.range; - var dom = $.summernote.core.dom; - - /** - * createVideoNode - * - * @member plugin.video - * @private - * @param {String} url - * @return {Node} - */ - var createVideoNode = function (url) { - // video url patterns(youtube, instagram, vimeo, dailymotion, youku, mp4, ogg, webm) - var ytRegExp = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/; - var ytMatch = url.match(ytRegExp); - - var igRegExp = /\/\/instagram.com\/p\/(.[a-zA-Z0-9]*)/; - var igMatch = url.match(igRegExp); - - var vRegExp = /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/; - var vMatch = url.match(vRegExp); - - var vimRegExp = /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/; - var vimMatch = url.match(vimRegExp); - - var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/; - var dmMatch = url.match(dmRegExp); - - var youkuRegExp = /\/\/v\.youku\.com\/v_show\/id_(\w+)\.html/; - var youkuMatch = url.match(youkuRegExp); - - var mp4RegExp = /^.+.(mp4|m4v)$/; - var mp4Match = url.match(mp4RegExp); - - var oggRegExp = /^.+.(ogg|ogv)$/; - var oggMatch = url.match(oggRegExp); - - var webmRegExp = /^.+.(webm)$/; - var webmMatch = url.match(webmRegExp); - - var $video; - if (ytMatch && ytMatch[1].length === 11) { - var youtubeId = ytMatch[1]; - $video = $('
    ').addClass('note-specialchar-node'); + var $tr = (idx % COLUMN_LENGTH === 0) ? $('

    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�@D~c4hkeIfY_E`Sv?c@yM+3u({oLnkG*t!9j)+ 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ξ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